ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
usb_host.h
Go to the documentation of this file.
1#pragma once
2
3// Should not be needed, but it's required to pass CI clang-tidy checks
4#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
7#include <vector>
8#include "usb/usb_host.h"
9#include <freertos/FreeRTOS.h>
10#include <freertos/task.h>
13#include <atomic>
14
16
17// THREADING MODEL:
18// This component uses a dedicated USB task for event processing to prevent data loss.
19// - USB Task (high priority): Handles USB events, executes transfer callbacks, releases transfer slots
20// - Main Loop Task: Initiates transfers, processes device connect/disconnect events
21//
22// Thread-safe communication:
23// - Lock-free queues for USB task -> main loop events (SPSC pattern)
24// - Lock-free TransferRequest pool using atomic bitmask (MCMP pattern - multi-consumer, multi-producer)
25//
26// TransferRequest pool access pattern:
27// - get_trq_() [allocate]: Called from BOTH USB task and main loop threads
28// * USB task: via USB UART input callbacks that restart transfers immediately
29// * Main loop: for output transfers and flow-controlled input restarts
30// - release_trq() [deallocate]: Called from BOTH USB task and main loop threads
31// * USB task: immediately after transfer callback completes (critical for preventing slot exhaustion)
32// * Main loop: when transfer submission fails
33//
34// The multi-threaded allocation/deallocation is intentional for performance:
35// - USB task can immediately restart input transfers and release slots without context switching
36// - Main loop controls backpressure by deciding when to restart after consuming data
37// The atomic bitmask ensures thread-safe allocation/deallocation without mutex blocking.
38
39static const char *const TAG = "usb_host";
40
41// Forward declarations
42struct TransferRequest;
43class USBClient;
44
45// constants for setup packet type
46static constexpr uint8_t USB_RECIP_DEVICE = 0;
47static constexpr uint8_t USB_RECIP_INTERFACE = 1;
48static constexpr uint8_t USB_RECIP_ENDPOINT = 2;
49static constexpr uint8_t USB_TYPE_STANDARD = 0 << 5;
50static constexpr uint8_t USB_TYPE_CLASS = 1 << 5;
51static constexpr uint8_t USB_TYPE_VENDOR = 2 << 5;
52static constexpr uint8_t USB_DIR_MASK = 1 << 7;
53static constexpr uint8_t USB_DIR_IN = 1 << 7;
54static constexpr uint8_t USB_DIR_OUT = 0;
55static constexpr size_t SETUP_PACKET_SIZE = 8;
56
57static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
58static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
59
60// Select appropriate bitmask type for tracking allocation of TransferRequest slots.
61// The bitmask must have at least as many bits as MAX_REQUESTS, so:
62// - Use uint16_t for up to 16 requests (MAX_REQUESTS <= 16)
63// - Use uint32_t for 17-32 requests (MAX_REQUESTS > 16)
64// This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
65// If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
66using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
67static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
68
69static constexpr size_t USB_MAX_PACKET_SIZE =
70 USB_HOST_MAX_PACKET_SIZE; // Max USB packet size (64 for FS, 512 for P4 HS)
71static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
72static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
73static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5)
74
75// used to report a transfer status
77 uint8_t *data;
78 size_t data_len;
79 void *user_data;
80 uint16_t error_code;
81 uint8_t endpoint;
82 bool success;
83};
84
85using transfer_cb_t = std::function<void(const TransferStatus &)>;
86
87class USBClient;
88
89// struct used to capture all data needed for a transfer
96
101
102struct UsbEvent {
104 union {
105 struct {
106 uint8_t address;
108 struct {
109 usb_device_handle_t handle;
112
113 // Required for EventPool - no cleanup needed for POD types
114 void release() {}
115};
116
117// callback function type.
118
127class USBClient : public Component {
128 friend class USBHost;
129
130 public:
131 USBClient(uint16_t vid, uint16_t pid) : trq_in_use_(0), vid_(vid), pid_(pid) {}
132 void setup() override;
133 void loop() override;
134 // setup must happen after the host bus has been setup
135 float get_setup_priority() const override { return setup_priority::IO; }
136 void on_opened(uint8_t addr);
137 void on_removed(usb_device_handle_t handle);
138 bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
139 bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
140 void dump_config() override;
141 void release_trq(TransferRequest *trq);
143 bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
144 const std::vector<uint8_t> &data = {});
145
146 // Lock-free event queue and pool for USB task to main loop communication
147 // Must be public for access from static callbacks
149 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
150 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
151 // before push() can fail, preventing a pool slot leak.
152 EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE - 1> event_pool;
153
154 protected:
155 // Process USB events from the queue. Returns true if any work was done.
156 // Subclasses should call this instead of USBClient::loop() to combine
157 // with their own work check for a single disable_loop() decision.
158 bool process_usb_events_();
159 void handle_open_state_();
160 TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
161 virtual void disconnect();
162 virtual void on_connected() {}
163 virtual void on_disconnected() {
164 // Reset all requests to available (all bits to 0)
165 this->trq_in_use_.store(0);
166 }
167
168 // USB task management
169 static void usb_task_fn(void *arg);
170 [[noreturn]] void usb_task_loop_() const;
171
172 // Members ordered to minimize struct padding on 32-bit platforms
173 TransferRequest requests_[MAX_REQUESTS]{};
174 TaskHandle_t usb_task_handle_{nullptr};
175 usb_host_client_handle_t handle_{};
176 usb_device_handle_t device_handle_{};
179 // Lock-free pool management using atomic bitmask (no dynamic allocation)
180 // Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available
181 // Supports multiple concurrent consumers and producers (both threads can allocate/deallocate)
182 std::atomic<trq_bitmask_t> trq_in_use_;
183 uint16_t vid_{};
184 uint16_t pid_{};
185};
186class USBHost : public Component {
187 public:
188 float get_setup_priority() const override { return setup_priority::BUS; }
189 void loop() override;
190 void setup() override;
191
192 protected:
193 std::vector<USBClient *> clients_{};
194};
195
196} // namespace esphome::usb_host
197
198#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
USBClient(uint16_t vid, uint16_t pid)
Definition usb_host.h:131
trq_bitmask_t get_trq_in_use() const
Definition usb_host.h:142
usb_host_client_handle_t handle_
Definition usb_host.h:175
TransferRequest requests_[MAX_REQUESTS]
Definition usb_host.h:173
float get_setup_priority() const override
Definition usb_host.h:135
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
void release_trq(TransferRequest *trq)
static void usb_task_fn(void *arg)
virtual void on_connected()
Definition usb_host.h:162
EventPool< UsbEvent, USB_EVENT_QUEUE_SIZE - 1 > event_pool
Definition usb_host.h:152
TaskHandle_t usb_task_handle_
Definition usb_host.h:174
virtual void on_disconnected()
Definition usb_host.h:163
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector< uint8_t > &data={})
bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length)
Performs a transfer input operation.
void on_removed(usb_device_handle_t handle)
usb_device_handle_t device_handle_
Definition usb_host.h:176
std::atomic< trq_bitmask_t > trq_in_use_
Definition usb_host.h:182
LockFreeQueue< UsbEvent, USB_EVENT_QUEUE_SIZE > event_queue
Definition usb_host.h:148
std::vector< USBClient * > clients_
Definition usb_host.h:193
float get_setup_priority() const override
Definition usb_host.h:188
uint16_t type
constexpr float BUS
For communication buses like i2c/spi.
Definition component.h:37
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:39
std::conditional<(MAX_REQUESTS<=16), uint16_t, uint32_t >::type trq_bitmask_t
Definition usb_host.h:66
std::function< void(const TransferStatus &)> transfer_cb_t
Definition usb_host.h:85
struct esphome::usb_host::UsbEvent::@177::@179 device_gone
usb_device_handle_t handle
Definition usb_host.h:109
union esphome::usb_host::UsbEvent::@177 data
struct esphome::usb_host::UsbEvent::@177::@178 device_new
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle