mirror of
https://github.com/esphome/esphome.git
synced 2026-02-24 10:18:23 -07:00
[usb_host] Implement disable_loop/enable_loop pattern for USB components (#14163)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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_() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user