ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_host_client.cpp
Go to the documentation of this file.
1// Should not be needed, but it's required to pass CI clang-tidy checks
2#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
3 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
4#include "usb_host.h"
5#include "esphome/core/log.h"
6#include "esphome/core/hal.h"
9
10#include <cinttypes>
11#include <cstring>
12#include <atomic>
13#include <span>
14#include <string>
15
16namespace esphome::usb_host {
17
18#pragma GCC diagnostic ignored "-Wparentheses"
19
20using namespace bytebuffer;
21
22#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
23static void print_ep_desc(const usb_ep_desc_t *ep_desc) {
24 const char *ep_type_str;
25 int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK;
26
27 switch (type) {
28 case USB_BM_ATTRIBUTES_XFER_CONTROL:
29 ep_type_str = "CTRL";
30 break;
31 case USB_BM_ATTRIBUTES_XFER_ISOC:
32 ep_type_str = "ISOC";
33 break;
34 case USB_BM_ATTRIBUTES_XFER_BULK:
35 ep_type_str = "BULK";
36 break;
37 case USB_BM_ATTRIBUTES_XFER_INT:
38 ep_type_str = "INT";
39 break;
40 default:
41 ep_type_str = NULL;
42 break;
43 }
44
45 ESP_LOGV(TAG,
46 "\t\t*** Endpoint descriptor ***\n"
47 "\t\tbLength %d\n"
48 "\t\tbDescriptorType %d\n"
49 "\t\tbEndpointAddress 0x%x\tEP %d %s\n"
50 "\t\tbmAttributes 0x%x\t%s\n"
51 "\t\twMaxPacketSize %d\n"
52 "\t\tbInterval %d",
53 ep_desc->bLength, ep_desc->bDescriptorType, ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc),
54 USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT", ep_desc->bmAttributes, ep_type_str, ep_desc->wMaxPacketSize,
55 ep_desc->bInterval);
56}
57
58static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) {
59 ESP_LOGV(TAG,
60 "\t*** Interface descriptor ***\n"
61 "\tbLength %d\n"
62 "\tbDescriptorType %d\n"
63 "\tbInterfaceNumber %d\n"
64 "\tbAlternateSetting %d\n"
65 "\tbNumEndpoints %d\n"
66 "\tbInterfaceClass 0x%x\n"
67 "\tiInterface %d",
68 intf_desc->bLength, intf_desc->bDescriptorType, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting,
69 intf_desc->bNumEndpoints, intf_desc->bInterfaceProtocol, intf_desc->iInterface);
70}
71
72static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
73 ESP_LOGV(TAG,
74 "*** Configuration descriptor ***\n"
75 " bLength %d\n"
76 " bDescriptorType %d\n"
77 " wTotalLength %d\n"
78 " bNumInterfaces %d\n"
79 " bConfigurationValue %d\n"
80 " iConfiguration %d\n"
81 " bmAttributes 0x%x\n"
82 " bMaxPower %dmA",
83 cfg_desc->bLength, cfg_desc->bDescriptorType, cfg_desc->wTotalLength, cfg_desc->bNumInterfaces,
84 cfg_desc->bConfigurationValue, cfg_desc->iConfiguration, cfg_desc->bmAttributes, cfg_desc->bMaxPower * 2);
85}
86
87static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
88 if (devc_desc == NULL) {
89 return;
90 }
91
92 ESP_LOGV(TAG,
93 "*** Device descriptor ***\n"
94 " bLength %d\n"
95 " bDescriptorType %d\n"
96 " bcdUSB %d.%d0\n"
97 " bDeviceClass 0x%x\n"
98 " bDeviceSubClass 0x%x\n"
99 " bDeviceProtocol 0x%x\n"
100 " bMaxPacketSize0 %d\n"
101 " idVendor 0x%x\n"
102 " idProduct 0x%x\n"
103 " bcdDevice %d.%d0\n"
104 " iManufacturer %d\n"
105 " iProduct %d\n"
106 " iSerialNumber %d\n"
107 " bNumConfigurations %d",
108 devc_desc->bLength, devc_desc->bDescriptorType, ((devc_desc->bcdUSB >> 8) & 0xF),
109 ((devc_desc->bcdUSB >> 4) & 0xF), devc_desc->bDeviceClass, devc_desc->bDeviceSubClass,
110 devc_desc->bDeviceProtocol, devc_desc->bMaxPacketSize0, devc_desc->idVendor, devc_desc->idProduct,
111 ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF), devc_desc->iManufacturer,
112 devc_desc->iProduct, devc_desc->iSerialNumber, devc_desc->bNumConfigurations);
113}
114
115static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
116 print_class_descriptor_cb class_specific_cb) {
117 if (cfg_desc == nullptr) {
118 return;
119 }
120
121 int offset = 0;
122 uint16_t w_total_length = cfg_desc->wTotalLength;
123 const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *) cfg_desc;
124
125 do {
126 switch (next_desc->bDescriptorType) {
127 case USB_W_VALUE_DT_CONFIG:
128 usbh_print_cfg_desc((const usb_config_desc_t *) next_desc);
129 break;
130 case USB_W_VALUE_DT_INTERFACE:
131 usbh_print_intf_desc((const usb_intf_desc_t *) next_desc);
132 break;
133 case USB_W_VALUE_DT_ENDPOINT:
134 print_ep_desc((const usb_ep_desc_t *) next_desc);
135 break;
136 default:
137 if (class_specific_cb) {
138 class_specific_cb(next_desc);
139 }
140 break;
141 }
142
143 next_desc = usb_parse_next_descriptor(next_desc, w_total_length, &offset);
144
145 } while (next_desc != NULL);
146}
147#endif
148// bLength (uint8_t, max 255) includes the 2-byte header (bLength and bDescriptorType),
149// so character count = (bLength - 2) / 2.
150bool copy_descriptor_string(const usb_str_desc_t *desc, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
151 buffer[0] = '\0';
152 if (desc == nullptr || desc->bLength < 2)
153 return true;
154 int char_count = (desc->bLength - 2) / 2;
155 char *p = buffer.data();
156 char *end = p + buffer.size() - 1;
157 for (int i = 0; i != char_count && p < end; i++) {
158 auto c = desc->wData[i];
159 // TODO: encode non-ASCII code units as UTF-8 if a device with such descriptors turns up
160 if (c >= 0x80) {
161 buffer[0] = '\0';
162 return false;
163 }
164 *p++ = static_cast<char>(c);
165 }
166 *p = '\0';
167 return true;
168}
169
170// Folds UTF-16 to Latin-1 for logging, dropping anything that does not fit
171template<typename T>
172static const char *utf16_to_latin1(const T *data, size_t count, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
173 char *p = buffer.data();
174 char *end = p + buffer.size() - 1;
175 for (size_t i = 0; i != count && p < end; i++) {
176 if (data[i] < 0x100)
177 *p++ = static_cast<char>(data[i]);
178 }
179 *p = '\0';
180 return buffer.data();
181}
182
183static const char *get_descriptor_string(const usb_str_desc_t *desc, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
184 if (desc == nullptr || desc->bLength < 2)
185 return "(unspecified)";
186 return utf16_to_latin1(desc->wData, (desc->bLength - 2) / 2, buffer);
187}
188
189static const char *filter_string(const char16_t *filter, std::span<char, DESC_STRING_BUF_SIZE> buffer) {
190 return utf16_to_latin1(filter, std::char_traits<char16_t>::length(filter), buffer);
191}
192
193// Both sides are UTF-16: the descriptor by specification, the filter because code
194// generation emits it as a u"" literal
195static bool descriptor_string_equals(const usb_str_desc_t *desc, const char16_t *expected) {
196 const int char_count = (desc == nullptr || desc->bLength < 2) ? 0 : (desc->bLength - 2) / 2;
197 for (int i = 0; i != char_count; i++) {
198 if (expected[i] == u'\0' || desc->wData[i] != expected[i])
199 return false;
200 }
201 return expected[char_count] == u'\0';
202}
203
205 if (!this->is_connected())
206 return false;
207 const usb_device_desc_t *desc;
208 esp_err_t err = usb_host_get_device_descriptor(this->device_handle_, &desc);
209 if (err != ESP_OK) {
210 ESP_LOGW(TAG, "Device descriptor query failed: %s", esp_err_to_name(err));
211 return false;
212 }
213 info.vendor_id = desc->idVendor;
214 info.product_id = desc->idProduct;
215 info.bcd_device = desc->bcdDevice;
216 usb_device_info_t dev_info;
217 err = usb_host_device_info(this->device_handle_, &dev_info);
218 if (err != ESP_OK) {
219 ESP_LOGW(TAG, "Device info query failed: %s", esp_err_to_name(err));
220 return false;
221 }
222 if (!copy_descriptor_string(dev_info.str_desc_manufacturer, info.manufacturer)) {
223 ESP_LOGW(TAG, "Manufacturer string descriptor is not ASCII");
224 }
225 if (!copy_descriptor_string(dev_info.str_desc_product, info.product)) {
226 ESP_LOGW(TAG, "Product string descriptor is not ASCII");
227 }
228 if (!copy_descriptor_string(dev_info.str_desc_serial_num, info.serial_number)) {
229 ESP_LOGW(TAG, "Serial number string descriptor is not ASCII");
230 }
231 return true;
232}
233
234// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
235static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) {
236 auto *client = static_cast<USBClient *>(ptr);
237
238 // Allocate event from pool
239 UsbEvent *event = client->event_pool.allocate();
240 if (event == nullptr) {
241 // No events available - increment counter for periodic logging
242 client->event_queue.increment_dropped_count();
243 return;
244 }
245
246 // Queue events to be processed in main loop
247 switch (event_msg->event) {
248 case USB_HOST_CLIENT_EVENT_NEW_DEV: {
249 ESP_LOGD(TAG, "New device %d", event_msg->new_dev.address);
250 event->type = EVENT_DEVICE_NEW;
251 event->data.device_new.address = event_msg->new_dev.address;
252 break;
253 }
254 case USB_HOST_CLIENT_EVENT_DEV_GONE: {
255 ESP_LOGD(TAG, "Device gone");
256 event->type = EVENT_DEVICE_GONE;
257 event->data.device_gone.handle = event_msg->dev_gone.dev_hdl;
258 break;
259 }
260 default:
261 ESP_LOGD(TAG, "Unknown event %d", event_msg->event);
262 client->event_pool.release(event);
263 return;
264 }
265
266 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
267 // allocate() returned non-null, the queue cannot be full.
268 client->event_queue.push(event);
269
270 // Re-enable component loop to process the queued event
271 client->enable_loop_soon_any_context();
272
273 // Wake main loop immediately to process USB event
275}
277 usb_host_client_config_t config{.is_synchronous = false,
278 .max_num_event_msg = 5,
279 .async = {.client_event_callback = client_event_cb, .callback_arg = this}};
280 auto err = usb_host_client_register(&config, &this->handle_);
281 if (err != ESP_OK) {
282 ESP_LOGE(TAG, "client register failed: %s", esp_err_to_name(err));
283 this->status_set_error(LOG_STR("Client register failed"));
284 this->mark_failed();
285 return;
286 }
287 // Pre-allocate USB transfer buffers for all slots at startup
288 // This avoids any dynamic allocation during runtime
289 for (auto &request : this->requests_) {
290 usb_host_transfer_alloc(USB_MAX_PACKET_SIZE, 0, &request.transfer);
291 request.client = this; // Set once, never changes
292 }
293
294 // Create and start USB task
295 xTaskCreate(usb_task_fn, "usb_task",
296 USB_TASK_STACK_SIZE, // Stack size
297 this, // Task parameter
298 USB_TASK_PRIORITY, // Priority (higher than main loop)
299 &this->usb_task_handle_);
300
301 if (this->usb_task_handle_ == nullptr) {
302 ESP_LOGE(TAG, "Failed to create USB task");
303 this->mark_failed();
304 }
305}
306
307void USBClient::usb_task_fn(void *arg) {
308 auto *client = static_cast<USBClient *>(arg);
309 client->usb_task_loop_();
310}
312 while (true) {
313 usb_host_client_handle_events(this->handle_, portMAX_DELAY);
314 }
315}
316
318 bool had_work = false;
319
320 // Process any events from the USB task
321 UsbEvent *event;
322 while ((event = this->event_queue.pop()) != nullptr) {
323 had_work = true;
324 switch (event->type) {
325 case EVENT_DEVICE_NEW:
326 this->on_opened(event->data.device_new.address);
327 break;
329 this->on_removed(event->data.device_gone.handle);
330 break;
331 }
332 // Return event to pool for reuse
333 this->event_pool.release(event);
334 }
335
336 // Log dropped events periodically
337 uint16_t dropped = this->event_queue.get_and_reset_dropped_count();
338 if (dropped > 0) {
339 ESP_LOGW(TAG, "Dropped %u USB events due to queue overflow", dropped);
340 }
341
342 if (this->state_ == USB_CLIENT_OPEN) {
343 had_work = true;
344 this->handle_open_state_();
345 }
346
347 return had_work;
348}
349
351 if (!this->process_usb_events_()) {
352 this->disable_loop();
353 }
354}
355
357 int err;
358 ESP_LOGD(TAG, "Open device %d", this->device_addr_);
359 err = usb_host_device_open(this->handle_, this->device_addr_, &this->device_handle_);
360 if (err != ESP_OK) {
361 ESP_LOGW(TAG, "Device open failed: %s", esp_err_to_name(err));
362 this->state_ = USB_CLIENT_INIT;
363 return;
364 }
365 ESP_LOGD(TAG, "Get descriptor device %d", this->device_addr_);
366 const usb_device_desc_t *desc;
367 err = usb_host_get_device_descriptor(this->device_handle_, &desc);
368 if (err != ESP_OK) {
369 ESP_LOGW(TAG, "Device get_desc failed: %s", esp_err_to_name(err));
370 this->disconnect();
371 return;
372 }
373 ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
374 if ((this->vid_ != 0 && desc->idVendor != this->vid_) || (this->pid_ != 0 && desc->idProduct != this->pid_)) {
375 ESP_LOGD(TAG, "Not our device, closing");
376 this->disconnect();
377 return;
378 }
379 usb_device_info_t dev_info;
380 err = usb_host_device_info(this->device_handle_, &dev_info);
381 if (err != ESP_OK) {
382 ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
383 this->disconnect();
384 return;
385 }
386 char buf_manuf[DESC_STRING_BUF_SIZE];
387 char buf_product[DESC_STRING_BUF_SIZE];
388 const bool manufacturer_matches =
389 this->manufacturer_filter_ == nullptr ||
390 descriptor_string_equals(dev_info.str_desc_manufacturer, this->manufacturer_filter_);
391 const bool product_matches =
392 this->product_filter_ == nullptr || descriptor_string_equals(dev_info.str_desc_product, this->product_filter_);
393 if (!manufacturer_matches || !product_matches) {
394 ESP_LOGD(TAG, "Device does not match filter, closing. Manuf: %s; Prod: %s",
395 get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf),
396 get_descriptor_string(dev_info.str_desc_product, buf_product));
397 this->disconnect();
398 return;
399 }
401 char buf_serial[DESC_STRING_BUF_SIZE];
402 ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s",
403 get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf),
404 get_descriptor_string(dev_info.str_desc_product, buf_product),
405 get_descriptor_string(dev_info.str_desc_serial_num, buf_serial));
406
407#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
408 const usb_device_desc_t *device_desc;
409 err = usb_host_get_device_descriptor(this->device_handle_, &device_desc);
410 if (err == ESP_OK)
411 usb_client_print_device_descriptor(device_desc);
412 const usb_config_desc_t *config_desc;
413 err = usb_host_get_active_config_descriptor(this->device_handle_, &config_desc);
414 if (err == ESP_OK)
415 usb_client_print_config_descriptor(config_desc, nullptr);
416#endif
417 this->on_connected();
418 // on_connected() may have rejected the device (no usable interface, say) and closed it
419 if (this->state_ == USB_CLIENT_CONNECTED && !this->reports_connection_itself()) {
420 this->report_connected_();
421 }
422}
423
425 if (this->state_ != USB_CLIENT_CONNECTED || this->connection_reported_) {
426 return;
427 }
428 this->connection_reported_ = true;
429 this->connection_callback_.call(true);
430}
431
432void USBClient::on_opened(uint8_t addr) {
433 if (this->state_ == USB_CLIENT_INIT) {
434 this->device_addr_ = addr;
435 this->state_ = USB_CLIENT_OPEN;
436 }
437}
438void USBClient::on_removed(usb_device_handle_t handle) {
439 if (this->device_handle_ == handle) {
440 this->disconnect();
441 }
442}
443
444// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
445static void control_callback(const usb_transfer_t *xfer) {
446 auto *trq = static_cast<TransferRequest *>(xfer->context);
447 trq->status.error_code = xfer->status;
448 trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
449 trq->status.endpoint = xfer->bEndpointAddress;
450 trq->status.data = xfer->data_buffer;
451 trq->status.data_len = xfer->actual_num_bytes;
452
453 // Execute callback in USB task context
454 if (trq->callback != nullptr) {
455 trq->callback(trq->status);
456 }
457
458 // Release transfer slot immediately in USB task
459 // The release_trq() uses thread-safe atomic operations
460 trq->client->release_trq(trq);
461}
462
463// THREAD CONTEXT: Called from both USB task and main loop threads (multi-consumer)
464// - USB task: USB UART input callbacks restart transfers for immediate data reception
465// - Main loop: Output transfers and flow-controlled input restarts after consuming data
466//
467// THREAD SAFETY: Lock-free using atomic compare-and-swap on bitmask
468// This multi-threaded access is intentional for performance - USB task can
469// immediately restart transfers without waiting for main loop scheduling.
471 trq_bitmask_t mask = this->trq_in_use_.load(std::memory_order_acquire);
472
473 // Find first available slot (bit = 0) and try to claim it atomically
474 // We use a while loop to allow retrying the same slot after CAS failure
475 for (;;) {
476 if (mask == ALL_REQUESTS_IN_USE) {
477 ESP_LOGE(TAG, "All %zu transfer slots in use", MAX_REQUESTS);
478 return nullptr;
479 }
480 // find the least significant zero bit
481 trq_bitmask_t lsb = ~mask & (mask + 1);
482
483 // Slot i appears available, try to claim it atomically
484 trq_bitmask_t desired = mask | lsb;
485
486 if (this->trq_in_use_.compare_exchange_weak(mask, desired, std::memory_order::acquire)) {
487 auto i = __builtin_ctz(lsb); // count trailing zeroes
488 // Successfully claimed slot i - prepare the TransferRequest
489 auto *trq = &this->requests_[i];
490 trq->transfer->context = trq;
491 trq->transfer->device_handle = this->device_handle_;
492 return trq;
493 }
494 // CAS failed - another thread modified the bitmask
495 // mask was already updated by compare_exchange_weak with the current value
496 }
497}
498
500 // Also reached for a device this client opened and then declined, or lost before it was
501 // ready; neither was reported as connected, so neither is reported as removed
502 const bool was_reported = this->connection_reported_;
503 this->connection_reported_ = false;
504 this->on_disconnected();
505 auto err = usb_host_device_close(this->handle_, this->device_handle_);
506 if (err != ESP_OK) {
507 ESP_LOGE(TAG, "Device close failed: %s", esp_err_to_name(err));
508 }
509 this->state_ = USB_CLIENT_INIT;
510 this->device_handle_ = nullptr;
511 this->device_addr_ = -1;
512 if (was_reported) {
513 this->connection_callback_.call(false);
514 }
515}
516
517// THREAD CONTEXT: Called from main loop thread only
518// - Used for device configuration and control operations
519bool USBClient::control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
520 const transfer_cb_t &callback, const std::vector<uint8_t> &data) {
521 auto *trq = this->get_trq_();
522 if (trq == nullptr)
523 return false;
524 auto length = data.size();
525 if (length > trq->transfer->data_buffer_size - SETUP_PACKET_SIZE) {
526 ESP_LOGE(TAG, "Control transfer data size too large: %u > %u", length,
527 trq->transfer->data_buffer_size - SETUP_PACKET_SIZE);
528 this->release_trq(trq);
529 return false;
530 }
531 auto control_packet = ByteBuffer(SETUP_PACKET_SIZE, LITTLE);
532 control_packet.put_uint8(type);
533 control_packet.put_uint8(request);
534 control_packet.put_uint16(value);
535 control_packet.put_uint16(index);
536 control_packet.put_uint16(length);
537 memcpy(trq->transfer->data_buffer, control_packet.get_data().data(), SETUP_PACKET_SIZE);
538 if (length != 0 && !(type & USB_DIR_IN)) {
539 memcpy(trq->transfer->data_buffer + SETUP_PACKET_SIZE, data.data(), length);
540 }
541 trq->callback = callback;
542 trq->transfer->bEndpointAddress = type & USB_DIR_MASK;
543 trq->transfer->num_bytes = static_cast<int>(length + SETUP_PACKET_SIZE);
544 trq->transfer->callback = reinterpret_cast<usb_transfer_cb_t>(control_callback);
545 auto err = usb_host_transfer_submit_control(this->handle_, trq->transfer);
546 if (err != ESP_OK) {
547 ESP_LOGE(TAG, "Failed to submit control transfer, err=%s", esp_err_to_name(err));
548 this->release_trq(trq);
549 return false;
550 }
551 return true;
552}
553
554// CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task)
555static void transfer_callback(usb_transfer_t *xfer) {
556 auto *trq = static_cast<TransferRequest *>(xfer->context);
557 trq->status.error_code = xfer->status;
558 trq->status.success = xfer->status == USB_TRANSFER_STATUS_COMPLETED;
559 trq->status.endpoint = xfer->bEndpointAddress;
560 trq->status.data = xfer->data_buffer;
561 trq->status.data_len = xfer->actual_num_bytes;
562
563 // Always execute callback in USB task context
564 // Callbacks should be fast and non-blocking (e.g., copy data to queue)
565 if (trq->callback != nullptr) {
566 trq->callback(trq->status);
567 }
568
569 // Release transfer slot AFTER callback completes to prevent slot exhaustion
570 // This is critical for high-throughput transfers (e.g., USB UART at 115200 baud)
571 // The callback has finished accessing xfer->data_buffer, so it's safe to release
572 // The release_trq() uses thread-safe atomic operations
573 trq->client->release_trq(trq);
574}
587bool USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
588 auto *trq = this->get_trq_();
589 if (trq == nullptr) {
590 ESP_LOGE(TAG, "Too many requests queued");
591 return false;
592 }
593 if (length > trq->transfer->data_buffer_size) {
594 ESP_LOGE(TAG, "transfer_in: data length %u exceeds buffer size %u", length, trq->transfer->data_buffer_size);
595 this->release_trq(trq);
596 return false;
597 }
598 trq->callback = callback;
599 trq->transfer->callback = transfer_callback;
600 trq->transfer->bEndpointAddress = ep_address | USB_DIR_IN;
601 trq->transfer->num_bytes = length;
602 auto err = usb_host_transfer_submit(trq->transfer);
603 if (err != ESP_OK) {
604 ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
605 this->release_trq(trq);
606 return false;
607 }
608 return true;
609}
610
628bool USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
629 auto *trq = this->get_trq_();
630 if (trq == nullptr) {
631 ESP_LOGE(TAG, "Too many requests queued");
632 return false;
633 }
634 if (length > trq->transfer->data_buffer_size) {
635 ESP_LOGE(TAG, "transfer_out: data length %u exceeds buffer size %u", length, trq->transfer->data_buffer_size);
636 this->release_trq(trq);
637 return false;
638 }
639 trq->callback = callback;
640 trq->transfer->callback = transfer_callback;
641 trq->transfer->bEndpointAddress = ep_address | USB_DIR_OUT;
642 trq->transfer->num_bytes = length;
643 memcpy(trq->transfer->data_buffer, data, length);
644 auto err = usb_host_transfer_submit(trq->transfer);
645 if (err != ESP_OK) {
646 ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
647 this->release_trq(trq);
648 return false;
649 }
650 return true;
651}
653 ESP_LOGCONFIG(TAG,
654 "USBClient\n"
655 " Vendor id %04X\n"
656 " Product id %04X",
657 this->vid_, this->pid_);
658 char buf[DESC_STRING_BUF_SIZE];
659 if (this->manufacturer_filter_ != nullptr) {
660 ESP_LOGCONFIG(TAG, " Manufacturer %s", filter_string(this->manufacturer_filter_, buf));
661 }
662 if (this->product_filter_ != nullptr) {
663 ESP_LOGCONFIG(TAG, " Product %s", filter_string(this->product_filter_, buf));
664 }
665}
666// THREAD CONTEXT: Called from both USB task and main loop threads
667// - USB task: Immediately after transfer callback completes
668// - Main loop: When transfer submission fails
669//
670// THREAD SAFETY: Lock-free using atomic AND to clear bit
671// Thread-safe atomic operation allows multithreaded deallocation
673 if (trq == nullptr)
674 return;
675
676 // Calculate index from pointer arithmetic
677 size_t index = trq - this->requests_;
678 if (index >= MAX_REQUESTS) {
679 ESP_LOGE(TAG, "Invalid TransferRequest pointer");
680 return;
681 }
682
683 // Atomically clear the bit to mark slot as available
684 // fetch_and with inverted bitmask clears the bit atomically
685 trq_bitmask_t mask = ~(static_cast<trq_bitmask_t>(1) << index);
686 this->trq_in_use_.fetch_and(mask, std::memory_order_release);
687}
688
689} // namespace esphome::usb_host
690#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
691 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
void mark_failed()
Mark this component as failed.
void disable_loop()
Disable this component's loop.
A class modelled on the Java ByteBuffer class.
Definition bytebuffer.h:37
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
usb_host_client_handle_t handle_
Definition usb_host.h:224
TransferRequest requests_[MAX_REQUESTS]
Definition usb_host.h:222
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)
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 on_removed(usb_device_handle_t handle)
usb_device_handle_t device_handle_
Definition usb_host.h:225
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
uint8_t type
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
std::conditional<(MAX_REQUESTS<=16), uint16_t, uint32_t >::type trq_bitmask_t
Definition usb_host.h:70
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
Application App
Global storage of Application pointer - only one Application can exist.
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
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle