[usb_host] Implement disable_loop/enable_loop pattern for USB components (#14163)

This commit is contained in:
J. Nick Koston
2026-02-20 19:21:14 -06:00
committed by GitHub
parent 35037d1a5b
commit a3f279c1cf
3 changed files with 38 additions and 11 deletions

View File

@@ -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,19 @@ 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};
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]{};
uint16_t vid_{};
uint16_t pid_{};
};
class USBHost : public Component {
public:

View File

@@ -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();
@@ -243,10 +246,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 +272,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_() {

View File

@@ -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();