2#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
18static optional<CdcEps> get_cdc(
const usb_config_desc_t *config_desc, uint8_t intf_idx) {
19 int conf_offset, ep_offset;
22 eps.bulk_interface_number = 0xFF;
24 const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
26 ESP_LOGE(TAG,
"usb_parse_interface_descriptor failed");
29 ESP_LOGD(TAG,
"intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
30 intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
31 intf_desc->bNumEndpoints);
32 for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
33 ep_offset = conf_offset;
34 const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
36 ESP_LOGE(TAG,
"Ran out of interfaces at %d before finding all endpoints", i);
39 ESP_LOGD(TAG,
"ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
40 if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
42 eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
43 }
else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
44 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
46 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
47 }
else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
48 (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
50 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
52 ESP_LOGE(TAG,
"Unexpected endpoint attributes: %02X", ep->bmAttributes);
56 if (eps.in_ep !=
nullptr && eps.out_ep !=
nullptr && eps.notify_ep !=
nullptr)
62 const usb_config_desc_t *config_desc;
63 const usb_device_desc_t *device_desc;
65 std::vector<CdcEps> cdc_devs{};
68 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
69 ESP_LOGE(TAG,
"get_device_descriptor failed");
72 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
73 ESP_LOGE(TAG,
"get_active_config_descriptor failed");
76 if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
78 if (
auto eps = get_cdc(config_desc, 0)) {
79 ESP_LOGV(TAG,
"Found CDC-ACM device");
80 cdc_devs.push_back(*eps);
84 if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
85 (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
86 ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
87 (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
89 const auto *this_desc =
reinterpret_cast<const usb_standard_desc_t *
>(config_desc);
91 this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
92 USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
95 const auto *iad_desc =
reinterpret_cast<const usb_iad_desc_t *
>(this_desc);
97 if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
98 ESP_LOGV(TAG,
"Found CDC-ACM device in composite device");
99 if (
auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
100 cdc_devs.push_back(*eps);
112 for (
size_t i = 0; i !=
len; i++) {
125 for (
size_t i = 0; i !=
len; i++) {
133 ESP_LOGV(TAG,
"Channel not initialised - write ignored");
136#ifdef USE_UART_DEBUGGER
138 constexpr size_t BATCH = 16;
140 for (
size_t off = 0; off <
len; off += BATCH) {
141 size_t n = std::min(
len - off, BATCH);
142 memcpy(buf,
">>> ", 4);
144 ESP_LOGD(TAG,
"%s", buf);
150 if (chunk ==
nullptr) {
151 ESP_LOGE(TAG,
"Output pool full - lost %zu bytes",
len);
155 memcpy(chunk->
data, data, chunk_len);
156 chunk->
length =
static_cast<uint8_t
>(chunk_len);
159 ESP_LOGE(TAG,
"Output queue full - lost %zu bytes",
len);
165 this->
parent_->start_output(
this);
173 uint32_t deadline =
millis() + 100;
174 while ((!this->
output_queue_.empty() || this->output_started_.load()) &&
millis() < deadline) {
176 this->
parent_->start_output(
this);
190 ESP_LOGV(TAG,
"Channel not initialised - read ignored");
196 ESP_LOGV(TAG,
"underflow: requested %zu but returned %d, bytes",
len,
available);
200 for (
size_t i = 0; i !=
len; i++) {
203 this->
parent_->start_input(
this);
214 auto *channel = chunk->
channel;
216#ifdef USE_UART_DEBUGGER
217 if (channel->debug_) {
219 memcpy(buf,
"<<< ", 4);
221 ESP_LOGD(TAG,
"%s", buf);
234 if (channel->rx_callback_) {
235 channel->rx_callback_();
240 uint16_t dropped = this->
usb_data_queue_.get_and_reset_dropped_count();
242 ESP_LOGW(TAG,
"Dropped %u USB data chunks due to buffer overflow", dropped);
251 USBClient::dump_config();
255 " Baud Rate: %" PRIu32
" baud\n"
260 " Dummy receiver: %s",
261 channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
262 STOP_BITS_NAMES[channel->stop_bits_], YESNO(channel->debug_), YESNO(channel->dummy_receiver_));
281 auto started =
false;
282 if (!channel->
input_started_.compare_exchange_strong(started,
true))
287 ESP_LOGV(TAG,
"Transfer result: length: %u; status %X",
status.data_len,
status.error_code);
289 ESP_LOGE(TAG,
"Input transfer failed, status=%s", esp_err_to_name(
status.error_code));
298 if (chunk ==
nullptr) {
319#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
329 if (!this->
transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize)) {
330 ESP_LOGE(TAG,
"IN transfer submission failed for ep=0x%02X", ep->bEndpointAddress);
343 bool expected =
false;
344 if (!channel->
output_started_.compare_exchange_strong(expected,
true, std::memory_order_acq_rel)) {
349 if (chunk ==
nullptr) {
361 ESP_LOGW(TAG,
"Output transfer failed: status %X",
status.error_code);
363 ESP_LOGV(TAG,
"Output Transfer result: length: %u; status %X",
status.data_len,
status.error_code);
379 ESP_LOGV(TAG,
"Output %u bytes started",
len);
386static void fix_mps(
const usb_ep_desc_t *ep) {
388 auto *ep_mutable =
const_cast<usb_ep_desc_t *
>(ep);
389 if (ep->wMaxPacketSize > 64) {
390 ESP_LOGW(TAG,
"Corrected MPS of EP 0x%02X from %u to 64",
static_cast<uint8_t
>(ep->bEndpointAddress & 0xFF),
392 ep_mutable->wMaxPacketSize = 64;
398 if (cdc_devs.empty()) {
403 ESP_LOGD(TAG,
"Found %zu CDC-ACM devices", cdc_devs.size());
406 if (i == cdc_devs.size()) {
407 ESP_LOGE(TAG,
"No configuration found for channel %d", channel->index_);
411 channel->cdc_dev_ = cdc_devs[i++];
412 fix_mps(channel->cdc_dev_.in_ep);
413 fix_mps(channel->cdc_dev_.out_ep);
414 channel->initialised_.store(
true);
418 if (channel->cdc_dev_.interrupt_interface_number != channel->cdc_dev_.bulk_interface_number) {
420 channel->cdc_dev_.interrupt_interface_number, 0);
421 if (err_comm != ESP_OK) {
422 ESP_LOGW(TAG,
"Could not claim comm interface %d: %s", channel->cdc_dev_.interrupt_interface_number,
423 esp_err_to_name(err_comm));
425 channel->cdc_dev_.comm_interface_claimed =
true;
426 ESP_LOGD(TAG,
"Claimed comm interface %d", channel->cdc_dev_.interrupt_interface_number);
430 usb_host_interface_claim(this->
handle_, this->
device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
432 ESP_LOGE(TAG,
"usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
433 channel->cdc_dev_.bulk_interface_number);
444 if (channel->cdc_dev_.in_ep !=
nullptr) {
445 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
446 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
448 if (channel->cdc_dev_.out_ep !=
nullptr) {
449 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
450 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
452 if (channel->cdc_dev_.notify_ep !=
nullptr) {
453 usb_host_endpoint_halt(this->
device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
454 usb_host_endpoint_flush(this->
device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
456 if (channel->cdc_dev_.comm_interface_claimed) {
457 usb_host_interface_release(this->
handle_, this->
device_handle_, channel->cdc_dev_.interrupt_interface_number);
458 channel->cdc_dev_.comm_interface_claimed =
false;
460 usb_host_interface_release(this->
handle_, this->
device_handle_, channel->cdc_dev_.bulk_interface_number);
462 channel->input_started_.store(
true);
463 channel->output_started_.store(
true);
464 channel->input_buffer_.clear();
468 while ((chunk = channel->output_queue_.pop()) !=
nullptr) {
469 channel->output_pool_.release(chunk);
472 channel->initialised_.store(
false);
474 USBClient::on_disconnected();
478 static constexpr uint8_t CDC_REQUEST_TYPE = usb_host::USB_TYPE_CLASS | usb_host::USB_RECIP_INTERFACE;
479 static constexpr uint8_t CDC_SET_LINE_CODING = 0x20;
480 static constexpr uint8_t CDC_SET_CONTROL_LINE_STATE = 0x22;
481 static constexpr uint16_t CDC_DTR_RTS = 0x0003;
484 if (!channel->initialised_.load())
490 uint32_t baud = channel->baud_rate_;
491 std::vector<uint8_t> line_coding = {
492 static_cast<uint8_t
>(baud & 0xFF),
static_cast<uint8_t
>((baud >> 8) & 0xFF),
493 static_cast<uint8_t
>((baud >> 16) & 0xFF),
static_cast<uint8_t
>((baud >> 24) & 0xFF),
494 static_cast<uint8_t
>(channel->stop_bits_),
495 static_cast<uint8_t
>(channel->parity_),
496 static_cast<uint8_t
>(channel->data_bits_),
498 ESP_LOGD(TAG,
"SET_LINE_CODING: baud=%u stop=%u parity=%u data=%u", (
unsigned) baud, channel->stop_bits_,
499 (
unsigned) channel->parity_, channel->data_bits_);
501 CDC_REQUEST_TYPE, CDC_SET_LINE_CODING, 0, channel->cdc_dev_.interrupt_interface_number,
503 if (!status.success) {
504 ESP_LOGW(TAG,
"SET_LINE_CODING failed: %X", status.error_code);
506 ESP_LOGD(TAG,
"SET_LINE_CODING OK");
511 this->
control_transfer(CDC_REQUEST_TYPE, CDC_SET_CONTROL_LINE_STATE, CDC_DTR_RTS,
513 if (!status.success) {
514 ESP_LOGW(TAG,
"SET_CONTROL_LINE_STATE failed: %X", status.error_code);
516 ESP_LOGD(TAG,
"SET_CONTROL_LINE_STATE (DTR+RTS) OK");
520 this->start_channels();
523void USBUartTypeCdcAcm::start_channels() {
524 for (
auto *channel : this->channels_) {
525 if (!channel->initialised_.load())
527 channel->input_started_.store(
false);
528 channel->output_started_.store(
false);
529 this->start_input(channel);
void wake_loop_threadsafe()
Wake the main event loop from another FreeRTOS task.
void status_set_warning(const char *message=nullptr)
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
USBUartComponent * parent_
usb_host_client_handle_t handle_
bool transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length)
Performs an output transfer operation.
virtual void disconnect()
bool process_usb_events_()
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.
usb_device_handle_t device_handle_
size_t get_available() const
std::atomic< bool > input_started_
std::atomic< bool > initialised_
LockFreeQueue< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_queue_
size_t available() override
bool peek_byte(uint8_t *data) override
void write_array(const uint8_t *data, size_t len) override
EventPool< UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT > output_pool_
bool read_array(uint8_t *data, size_t len) override
std::atomic< bool > output_started_
EventPool< UsbDataChunk, USB_DATA_QUEUE_SIZE > chunk_pool_
std::vector< USBUartChannel * > channels_
LockFreeQueue< UsbDataChunk, USB_DATA_QUEUE_SIZE > usb_data_queue_
void start_output(USBUartChannel *channel)
void dump_config() override
void start_input(USBUartChannel *channel)
void on_disconnected() override
void on_connected() override
virtual std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl)
virtual void enable_channels()
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
const nullopt_t nullopt((nullopt_t::init()))
const usb_ep_desc_t * out_ep
const usb_ep_desc_t * in_ep
uint8_t data[MAX_CHUNK_SIZE]
static constexpr size_t MAX_CHUNK_SIZE
uint8_t data[MAX_CHUNK_SIZE]
static constexpr size_t MAX_CHUNK_SIZE