mirror of
https://github.com/esphome/esphome.git
synced 2026-03-01 18:34:21 -07:00
Merge branch 'usb_host_disable_loop' into integration
This commit is contained in:
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.1
|
||||
rev: v0.15.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -104,7 +104,7 @@ bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t en
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_boot_is_good_after_ = boot_is_good_after;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(RTC_KEY, false);
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||
// Check partition state to detect if bootloader supports rollback
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
namespace esphome::safe_mode {
|
||||
|
||||
/// RTC key for storing boot loop counter - used by safe_mode and preferences backends
|
||||
constexpr uint32_t RTC_KEY = 233825507UL;
|
||||
|
||||
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||
class SafeModeComponent : public Component {
|
||||
public:
|
||||
|
||||
@@ -73,12 +73,12 @@ static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than mai
|
||||
|
||||
// used to report a transfer status
|
||||
struct TransferStatus {
|
||||
bool success;
|
||||
uint16_t error_code;
|
||||
uint8_t *data;
|
||||
size_t data_len;
|
||||
uint8_t endpoint;
|
||||
void *user_data;
|
||||
uint16_t error_code;
|
||||
uint8_t endpoint;
|
||||
bool success;
|
||||
};
|
||||
|
||||
using transfer_cb_t = std::function<void(const TransferStatus &)>;
|
||||
@@ -127,7 +127,7 @@ class USBClient : public Component {
|
||||
friend class USBHost;
|
||||
|
||||
public:
|
||||
USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid), trq_in_use_(0) {}
|
||||
USBClient(uint16_t vid, uint16_t pid) : trq_in_use_(0), vid_(vid), pid_(pid) {}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
// setup must happen after the host bus has been setup
|
||||
@@ -148,6 +148,10 @@ class USBClient : public Component {
|
||||
EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE> event_pool;
|
||||
|
||||
protected:
|
||||
// Process USB events from the queue. Returns true if any work was done.
|
||||
// Subclasses should call this instead of USBClient::loop() to combine
|
||||
// with their own work check for a single disable_loop() decision.
|
||||
bool process_usb_events_();
|
||||
void handle_open_state_();
|
||||
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
|
||||
virtual void disconnect();
|
||||
@@ -161,20 +165,17 @@ class USBClient : public Component {
|
||||
static void usb_task_fn(void *arg);
|
||||
[[noreturn]] void usb_task_loop() const;
|
||||
|
||||
// Members ordered to minimize struct padding on 32-bit platforms
|
||||
TransferRequest requests_[MAX_REQUESTS]{};
|
||||
TaskHandle_t usb_task_handle_{nullptr};
|
||||
|
||||
usb_host_client_handle_t handle_{};
|
||||
usb_device_handle_t device_handle_{};
|
||||
int device_addr_{-1};
|
||||
int state_{USB_CLIENT_INIT};
|
||||
// 2-byte members packed together; trq_in_use_ is uint16_t when MAX_REQUESTS <= 16
|
||||
std::atomic<trq_bitmask_t> trq_in_use_;
|
||||
uint16_t vid_{};
|
||||
uint16_t pid_{};
|
||||
// Lock-free pool management using atomic bitmask (no dynamic allocation)
|
||||
// Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available
|
||||
// Supports multiple concurrent consumers and producers (both threads can allocate/deallocate)
|
||||
// Bitmask type automatically selected: uint16_t for <= 16 slots, uint32_t for 17-32 slots
|
||||
std::atomic<trq_bitmask_t> trq_in_use_;
|
||||
TransferRequest requests_[MAX_REQUESTS]{};
|
||||
};
|
||||
class USBHost : public Component {
|
||||
public:
|
||||
|
||||
@@ -197,6 +197,9 @@ static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *
|
||||
// Push to lock-free queue (always succeeds since pool size == queue size)
|
||||
client->event_queue.push(event);
|
||||
|
||||
// Re-enable component loop to process the queued event
|
||||
client->enable_loop_soon_any_context();
|
||||
|
||||
// Wake main loop immediately to process USB event instead of waiting for select() timeout
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
App.wake_loop_threadsafe();
|
||||
@@ -230,7 +233,11 @@ void USBClient::setup() {
|
||||
if (this->usb_task_handle_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create USB task");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with loop disabled - it will be enabled by client_event_cb when events arrive
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void USBClient::usb_task_fn(void *arg) {
|
||||
@@ -243,10 +250,13 @@ void USBClient::usb_task_loop() const {
|
||||
}
|
||||
}
|
||||
|
||||
void USBClient::loop() {
|
||||
bool USBClient::process_usb_events_() {
|
||||
bool had_work = false;
|
||||
|
||||
// Process any events from the USB task
|
||||
UsbEvent *event;
|
||||
while ((event = this->event_queue.pop()) != nullptr) {
|
||||
had_work = true;
|
||||
switch (event->type) {
|
||||
case EVENT_DEVICE_NEW:
|
||||
this->on_opened(event->data.device_new.address);
|
||||
@@ -266,8 +276,17 @@ void USBClient::loop() {
|
||||
}
|
||||
|
||||
if (this->state_ == USB_CLIENT_OPEN) {
|
||||
had_work = true;
|
||||
this->handle_open_state_();
|
||||
}
|
||||
|
||||
return had_work;
|
||||
}
|
||||
|
||||
void USBClient::loop() {
|
||||
if (!this->process_usb_events_()) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void USBClient::handle_open_state_() {
|
||||
|
||||
@@ -172,11 +172,12 @@ bool USBUartChannel::read_array(uint8_t *data, size_t len) {
|
||||
}
|
||||
void USBUartComponent::setup() { USBClient::setup(); }
|
||||
void USBUartComponent::loop() {
|
||||
USBClient::loop();
|
||||
bool had_work = this->process_usb_events_();
|
||||
|
||||
// Process USB data from the lock-free queue
|
||||
UsbDataChunk *chunk;
|
||||
while ((chunk = this->usb_data_queue_.pop()) != nullptr) {
|
||||
had_work = true;
|
||||
auto *channel = chunk->channel;
|
||||
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
@@ -198,6 +199,11 @@ void USBUartComponent::loop() {
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %u USB data chunks due to buffer overflow", dropped);
|
||||
}
|
||||
|
||||
// Disable loop when idle. Callbacks re-enable via enable_loop_soon_any_context().
|
||||
if (!had_work) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
void USBUartComponent::dump_config() {
|
||||
USBClient::dump_config();
|
||||
@@ -264,6 +270,9 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
|
||||
// Push always succeeds because pool size == queue size
|
||||
this->usb_data_queue_.push(chunk);
|
||||
|
||||
// Re-enable component loop to process the queued data
|
||||
this->enable_loop_soon_any_context();
|
||||
|
||||
// Wake main loop immediately to process USB data instead of waiting for select() timeout
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
App.wake_loop_threadsafe();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.4
|
||||
pylint==4.0.5
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.15.1 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.15.2 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
||||
Reference in New Issue
Block a user