From 9f2f33fc89918586045c5a457dcf39bbed602b89 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Sep 2025 22:36:48 -0500 Subject: [PATCH] lock free --- esphome/components/usb_host/usb_host.h | 14 ++-- .../components/usb_host/usb_host_client.cpp | 72 ++++++++++++------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 183ee73a14..525457fd99 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -7,8 +7,8 @@ #include "usb/usb_host.h" #include #include -#include -#include +#include "esphome/core/lock_free_queue.h" +#include "esphome/core/event_pool.h" #include namespace esphome { @@ -79,6 +79,9 @@ struct UsbEvent { TransferRequest *trq; } transfer; } data; + + // Required for EventPool - no cleanup needed for POD types + void release() {} }; // callback function type. @@ -115,7 +118,11 @@ class USBClient : public Component { void release_trq(TransferRequest *trq); bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector &data = {}); - QueueHandle_t get_event_queue() { return event_queue_; } + + // Lock-free event queue and pool for USB task to main loop communication + // Must be public for access from static callbacks + LockFreeQueue event_queue; + EventPool event_pool; protected: bool register_(); @@ -129,7 +136,6 @@ class USBClient : public Component { void usb_task_loop(); TaskHandle_t usb_task_handle_{nullptr}; - QueueHandle_t event_queue_{nullptr}; // Queue of UsbEvent structs usb_host_client_handle_t handle_{}; usb_device_handle_t device_handle_{}; diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 7a3a53cb75..77599cb263 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -142,28 +142,37 @@ static std::string get_descriptor_string(const usb_str_desc_t *desc) { // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task) static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) { auto *client = static_cast(ptr); - UsbEvent event; + + // Allocate event from pool + UsbEvent *event = client->event_pool.allocate(); + if (event == nullptr) { + // No events available - increment counter for periodic logging + client->event_queue.increment_dropped_count(); + return; + } // Queue events to be processed in main loop switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: { ESP_LOGD(TAG, "New device %d", event_msg->new_dev.address); - event.type = EVENT_DEVICE_NEW; - event.data.device_new.address = event_msg->new_dev.address; - xQueueSend(client->get_event_queue(), &event, portMAX_DELAY); + event->type = EVENT_DEVICE_NEW; + event->data.device_new.address = event_msg->new_dev.address; break; } case USB_HOST_CLIENT_EVENT_DEV_GONE: { ESP_LOGD(TAG, "Device gone"); - event.type = EVENT_DEVICE_GONE; - event.data.device_gone.handle = event_msg->dev_gone.dev_hdl; - xQueueSend(client->get_event_queue(), &event, portMAX_DELAY); + event->type = EVENT_DEVICE_GONE; + event->data.device_gone.handle = event_msg->dev_gone.dev_hdl; break; } default: ESP_LOGD(TAG, "Unknown event %d", event_msg->event); - break; + client->event_pool.release(event); + return; } + + // Push to lock-free queue (always succeeds since pool size == queue size) + client->event_queue.push(event); } void USBClient::setup() { usb_host_client_config_t config{.is_synchronous = false, @@ -181,14 +190,6 @@ void USBClient::setup() { trq->client = this; } - // Create event queue for communication between USB task and main loop - this->event_queue_ = xQueueCreate(USB_EVENT_QUEUE_SIZE, sizeof(UsbEvent)); - if (this->event_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create event queue"); - this->mark_failed(); - return; - } - // Create and start USB task xTaskCreatePinnedToCore(usb_task_fn, "usb_task", USB_TASK_STACK_SIZE, // Stack size @@ -219,23 +220,31 @@ void USBClient::usb_task_loop() { void USBClient::loop() { // Process any events from the USB task - UsbEvent event; - while (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) { - switch (event.type) { + UsbEvent *event; + while ((event = this->event_queue.pop()) != nullptr) { + switch (event->type) { case EVENT_DEVICE_NEW: - this->on_opened(event.data.device_new.address); + this->on_opened(event->data.device_new.address); break; case EVENT_DEVICE_GONE: - this->on_removed(event.data.device_gone.handle); + this->on_removed(event->data.device_gone.handle); break; case EVENT_TRANSFER_COMPLETE: case EVENT_CONTROL_COMPLETE: { - auto *trq = event.data.transfer.trq; + auto *trq = event->data.transfer.trq; // Callback was already executed in USB task, just cleanup this->release_trq(trq); break; } } + // Return event to pool for reuse + this->event_pool.release(event); + } + + // Log dropped events periodically + uint16_t dropped = this->event_queue.get_and_reset_dropped_count(); + if (dropped > 0) { + ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped); } switch (this->state_) { @@ -309,10 +318,21 @@ void USBClient::on_removed(usb_device_handle_t handle) { // Helper to queue transfer cleanup to main loop static void queue_transfer_cleanup(TransferRequest *trq, EventType type) { - UsbEvent event; - event.type = type; - event.data.transfer.trq = trq; - xQueueSend(trq->client->get_event_queue(), &event, portMAX_DELAY); + auto *client = trq->client; + + // Allocate event from pool + UsbEvent *event = client->event_pool.allocate(); + if (event == nullptr) { + // No events available - increment counter for periodic logging + client->event_queue.increment_dropped_count(); + return; + } + + event->type = type; + event->data.transfer.trq = trq; + + // Push to lock-free queue (always succeeds since pool size == queue size) + client->event_queue.push(event); } // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)