ESPHome 2026.11.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) || \
5 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
9#include <vector>
10#include "usb/usb_host.h"
11#include <freertos/FreeRTOS.h>
12#include <freertos/task.h>
15#include "esphome/core/log.h"
16#include <atomic>
17#include <span>
18
19namespace esphome::usb_host {
20
21// THREADING MODEL:
22// This component uses a dedicated USB task for event processing to prevent data loss.
23// - USB Task (high priority): Handles USB events, executes transfer callbacks, releases transfer slots
24// - Main Loop Task: Initiates transfers, processes device connect/disconnect events
25//
26// Thread-safe communication:
27// - Lock-free queues for USB task -> main loop events (SPSC pattern)
28// - Lock-free TransferRequest pool using atomic bitmask (MCMP pattern - multi-consumer, multi-producer)
29//
30// TransferRequest pool access pattern:
31// - get_trq_() [allocate]: Called from BOTH USB task and main loop threads
32// * USB task: via USB UART input callbacks that restart transfers immediately
33// * Main loop: for output transfers and flow-controlled input restarts
34// - release_trq() [deallocate]: Called from BOTH USB task and main loop threads
35// * USB task: immediately after transfer callback completes (critical for preventing slot exhaustion)
36// * Main loop: when transfer submission fails
37//
38// The multi-threaded allocation/deallocation is intentional for performance:
39// - USB task can immediately restart input transfers and release slots without context switching
40// - Main loop controls backpressure by deciding when to restart after consuming data
41// The atomic bitmask ensures thread-safe allocation/deallocation without mutex blocking.
42
43ESPHOME_LOG_TAG(TAG, "usb_host");
44
45// Forward declarations
46struct TransferRequest;
47class USBClient;
48
49// constants for setup packet type
50static constexpr uint8_t USB_RECIP_DEVICE = 0;
51static constexpr uint8_t USB_RECIP_INTERFACE = 1;
52static constexpr uint8_t USB_RECIP_ENDPOINT = 2;
53static constexpr uint8_t USB_TYPE_STANDARD = 0 << 5;
54static constexpr uint8_t USB_TYPE_CLASS = 1 << 5;
55static constexpr uint8_t USB_TYPE_VENDOR = 2 << 5;
56static constexpr uint8_t USB_DIR_MASK = 1 << 7;
57static constexpr uint8_t USB_DIR_IN = 1 << 7;
58static constexpr uint8_t USB_DIR_OUT = 0;
59static constexpr size_t SETUP_PACKET_SIZE = 8;
60
61static constexpr size_t MAX_REQUESTS = USB_HOST_MAX_REQUESTS; // maximum number of outstanding requests possible.
62static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be between 1 and 32");
63
64// Select appropriate bitmask type for tracking allocation of TransferRequest slots.
65// The bitmask must have at least as many bits as MAX_REQUESTS, so:
66// - Use uint16_t for up to 16 requests (MAX_REQUESTS <= 16)
67// - Use uint32_t for 17-32 requests (MAX_REQUESTS > 16)
68// This is tied to the static_assert above, which enforces MAX_REQUESTS is between 1 and 32.
69// If MAX_REQUESTS is increased above 32, this logic and the static_assert must be updated.
70using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
71static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
72
73static constexpr size_t USB_MAX_PACKET_SIZE =
74 USB_HOST_MAX_PACKET_SIZE; // Max USB packet size (64 for FS, 512 for P4 HS)
75static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
76static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
77static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5)
78
79// used to report a transfer status
81 uint8_t *data;
82 size_t data_len;
83 void *user_data;
84 uint16_t error_code;
85 uint8_t endpoint;
86 bool success;
87};
88
89using transfer_cb_t = std::function<void(const TransferStatus &)>;
90
91class USBClient;
92
93// struct used to capture all data needed for a transfer
100
105
106struct UsbEvent {
108 union {
109 struct {
110 uint8_t address;
112 struct {
113 usb_device_handle_t handle;
116
117 // Required for EventPool - no cleanup needed for POD types
118 void release() {}
119};
120
121// callback function type.
122
123// USB string descriptors hold at most 126 characters; one more for the terminator
124static constexpr size_t DESC_STRING_BUF_SIZE = 128;
125
129 uint16_t vendor_id;
130 uint16_t product_id;
131 uint16_t bcd_device;
132 char manufacturer[DESC_STRING_BUF_SIZE];
133 char product[DESC_STRING_BUF_SIZE];
134 char serial_number[DESC_STRING_BUF_SIZE];
135};
136
140bool copy_descriptor_string(const usb_str_desc_t *desc, std::span<char, DESC_STRING_BUF_SIZE> buffer);
141
150class USBClient : public Component {
151 friend class USBHost;
152
153 public:
154 USBClient(uint16_t vid, uint16_t pid) : trq_in_use_(0), vid_(vid), pid_(pid) {}
155 void setup() override;
156 void loop() override;
157 // setup must happen after the host bus has been setup
158 float get_setup_priority() const override { return setup_priority::IO; }
159 void on_opened(uint8_t addr);
160 void on_removed(usb_device_handle_t handle);
161 bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length);
162 bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length);
163 void dump_config() override;
164 void release_trq(TransferRequest *trq);
166 bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback,
167 const std::vector<uint8_t> &data = {});
168 void set_manufacturer_filter(const char16_t *manufacturer) { this->manufacturer_filter_ = manufacturer; }
169 void set_product_filter(const char16_t *product) { this->product_filter_ = product; }
170
172 bool is_connected() const { return this->connection_reported_; }
173
176 bool get_device_info(UsbDeviceInfo &info) const;
177
184 template<typename F> void add_on_connection_callback(F &&callback) {
185 this->connection_callback_.add(std::forward<F>(callback));
186 }
187
188 // Lock-free event queue and pool for USB task to main loop communication
189 // Must be public for access from static callbacks
191 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
192 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
193 // before push() can fail, preventing a pool slot leak.
194 EventPool<UsbEvent, USB_EVENT_QUEUE_SIZE - 1> event_pool;
195
196 protected:
197 // Process USB events from the queue. Returns true if any work was done.
198 // Subclasses should call this instead of USBClient::loop() to combine
199 // with their own work check for a single disable_loop() decision.
200 bool process_usb_events_();
201 void handle_open_state_();
202 TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
203 virtual void disconnect();
204 virtual void on_connected() {}
205
208 virtual bool reports_connection_itself() const { return false; }
211 void report_connected_();
212 virtual void on_disconnected() {
213 // Reset all requests to available (all bits to 0)
214 this->trq_in_use_.store(0);
215 }
216
217 // USB task management
218 static void usb_task_fn(void *arg);
219 [[noreturn]] void usb_task_loop_() const;
220
221 // Members ordered to minimize struct padding on 32-bit platforms
222 TransferRequest requests_[MAX_REQUESTS]{};
223 TaskHandle_t usb_task_handle_{nullptr};
224 usb_host_client_handle_t handle_{};
225 usb_device_handle_t device_handle_{};
229 // Lock-free pool management using atomic bitmask (no dynamic allocation)
230 // Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available
231 // Supports multiple concurrent consumers and producers (both threads can allocate/deallocate)
232 std::atomic<trq_bitmask_t> trq_in_use_;
233 const char16_t *manufacturer_filter_{nullptr};
234 const char16_t *product_filter_{nullptr};
235 uint16_t vid_{};
236 uint16_t pid_{};
237 // Whether the connection callbacks were told about the current device, so a removal is
238 // only ever reported for a device that was reported connected
240};
241class USBHost final : public Component {
242 public:
243 float get_setup_priority() const override { return setup_priority::BUS; }
244 void loop() override;
245 void setup() override;
246
247 protected:
248 std::vector<USBClient *> clients_{};
249};
250
251} // namespace esphome::usb_host
252
253#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
254 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
USBClient(uint16_t vid, uint16_t pid)
Definition usb_host.h:154
LazyCallbackManager< void(bool)> connection_callback_
Definition usb_host.h:228
virtual bool reports_connection_itself() const
Whether the subclass reports the device as connected itself, once its own setup of the device has fin...
Definition usb_host.h:208
trq_bitmask_t get_trq_in_use() const
Definition usb_host.h:165
usb_host_client_handle_t handle_
Definition usb_host.h:224
TransferRequest requests_[MAX_REQUESTS]
Definition usb_host.h:222
float get_setup_priority() const override
Definition usb_host.h:158
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)
const char16_t * product_filter_
Definition usb_host.h:234
static void usb_task_fn(void *arg)
void set_product_filter(const char16_t *product)
Definition usb_host.h:169
virtual void on_connected()
Definition usb_host.h:204
EventPool< UsbEvent, USB_EVENT_QUEUE_SIZE - 1 > event_pool
Definition usb_host.h:194
bool is_connected() const
Whether a device has been opened and its setup by the subclass has finished.
Definition usb_host.h:172
TaskHandle_t usb_task_handle_
Definition usb_host.h:223
const char16_t * manufacturer_filter_
Definition usb_host.h:233
bool get_device_info(UsbDeviceInfo &info) const
Copy the connected device's identity out of the cached USB descriptors.
virtual void on_disconnected()
Definition usb_host.h:212
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 add_on_connection_callback(F &&callback)
Register a callback for the device this client claims being connected (true) or removed (false).
Definition usb_host.h:184
void on_removed(usb_device_handle_t handle)
usb_device_handle_t device_handle_
Definition usb_host.h:225
void set_manufacturer_filter(const char16_t *manufacturer)
Definition usb_host.h:168
std::atomic< trq_bitmask_t > trq_in_use_
Definition usb_host.h:232
void report_connected_()
Report the claimed device to the connection callbacks.
LockFreeQueue< UsbEvent, USB_EVENT_QUEUE_SIZE > event_queue
Definition usb_host.h:190
std::vector< USBClient * > clients_
Definition usb_host.h:248
float get_setup_priority() const override
Definition usb_host.h:243
uint8_t type
uint32_t buffer
Definition gif.cpp:50
constexpr float BUS
For communication buses like i2c/spi.
Definition component.h:39
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:41
std::conditional<(MAX_REQUESTS<=16), uint16_t, uint32_t >::type trq_bitmask_t
Definition usb_host.h:70
ESPHOME_LOG_TAG(TAG, "usb_host")
bool copy_descriptor_string(const usb_str_desc_t *desc, std::span< char, DESC_STRING_BUF_SIZE > buffer)
Copy a USB string descriptor into a NUL-terminated buffer.
std::function< void(const TransferStatus &)> transfer_cb_t
Definition usb_host.h:89
Identity of a connected USB device, copied out of the descriptors the USB host stack caches for the l...
Definition usb_host.h:128
char serial_number[DESC_STRING_BUF_SIZE]
Definition usb_host.h:134
char product[DESC_STRING_BUF_SIZE]
Definition usb_host.h:133
char manufacturer[DESC_STRING_BUF_SIZE]
Definition usb_host.h:132
union esphome::usb_host::UsbEvent::@181 data
struct esphome::usb_host::UsbEvent::@181::@182 device_new
usb_device_handle_t handle
Definition usb_host.h:113
struct esphome::usb_host::UsbEvent::@181::@183 device_gone
spi_device_handle_t handle