[usb_uart] Be flexible about descriptor layout for CDC-ACM devices (#9425)

This commit is contained in:
Clyde Stubbs 2025-07-13 08:59:49 +10:00 committed by Jesse Hills
parent bc7cfeb9cd
commit 01b4e214b9
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
4 changed files with 56 additions and 58 deletions

View File

@ -70,7 +70,7 @@ static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2); ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2);
} }
void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
if (devc_desc == NULL) { if (devc_desc == NULL) {
return; return;
} }
@ -92,7 +92,7 @@ void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations); ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations);
} }
void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
print_class_descriptor_cb class_specific_cb) { print_class_descriptor_cb class_specific_cb) {
if (cfg_desc == nullptr) { if (cfg_desc == nullptr) {
return; return;
@ -128,9 +128,9 @@ void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
static std::string get_descriptor_string(const usb_str_desc_t *desc) { static std::string get_descriptor_string(const usb_str_desc_t *desc) {
char buffer[256]; char buffer[256];
if (desc == nullptr) if (desc == nullptr)
return "(unknown)"; return "(unspecified)";
char *p = buffer; char *p = buffer;
for (size_t i = 0; i != desc->bLength / 2; i++) { for (int i = 0; i != desc->bLength / 2; i++) {
auto c = desc->wData[i]; auto c = desc->wData[i];
if (c < 0x100) if (c < 0x100)
*p++ = static_cast<char>(c); *p++ = static_cast<char>(c);
@ -169,7 +169,7 @@ void USBClient::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
for (auto trq : this->trq_pool_) { for (auto *trq : this->trq_pool_) {
usb_host_transfer_alloc(64, 0, &trq->transfer); usb_host_transfer_alloc(64, 0, &trq->transfer);
trq->client = this; trq->client = this;
} }
@ -197,7 +197,8 @@ void USBClient::loop() {
ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct); ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) { if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) {
usb_device_info_t dev_info; usb_device_info_t dev_info;
if ((err = usb_host_device_info(this->device_handle_, &dev_info)) != ESP_OK) { err = usb_host_device_info(this->device_handle_, &dev_info);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
this->disconnect(); this->disconnect();
break; break;
@ -336,7 +337,7 @@ static void transfer_callback(usb_transfer_t *xfer) {
* @throws None. * @throws None.
*/ */
void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
auto trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return;
@ -349,7 +350,6 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
this->release_trq(trq); this->release_trq(trq);
this->disconnect();
} }
} }
@ -364,7 +364,7 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
* @throws None. * @throws None.
*/ */
void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
auto trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return;

View File

@ -43,7 +43,7 @@ static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
static constexpr uint8_t SET_CHARS = 0x19; // Set special characters. static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command. static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) { std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc; const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc; const usb_device_desc_t *device_desc;
int conf_offset = 0, ep_offset; int conf_offset = 0, ep_offset;

View File

@ -18,52 +18,48 @@ namespace usb_uart {
*/ */
static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) { static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
int conf_offset, ep_offset; int conf_offset, ep_offset;
const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{}; // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out)
uint8_t interface_number = 0; CdcEps eps{};
// look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out) eps.bulk_interface_number = 0xFF;
for (;;) { for (;;) {
auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset); const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
if (!intf_desc) { if (!intf_desc) {
ESP_LOGE(TAG, "usb_parse_interface_descriptor failed"); ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
return nullopt; return nullopt;
} }
if (intf_desc->bNumEndpoints == 1) { ESP_LOGD(TAG, "intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
intf_desc->bNumEndpoints);
for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
ep_offset = conf_offset; ep_offset = conf_offset;
notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
if (!notify_ep) { if (!ep) {
ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed"); ESP_LOGE(TAG, "Ran out of interfaces at %d before finding all endpoints", i);
return nullopt; return nullopt;
} }
if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT) ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
notify_ep = nullptr; if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
} else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) { eps.notify_ep = ep;
interface_number = intf_desc->bInterfaceNumber; eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
ep_offset = conf_offset; } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
if (!out_ep) { eps.in_ep = ep;
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed"); eps.bulk_interface_number = intf_desc->bInterfaceNumber;
return nullopt; } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
(eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
eps.out_ep = ep;
eps.bulk_interface_number = intf_desc->bInterfaceNumber;
} else {
ESP_LOGE(TAG, "Unexpected endpoint attributes: %02X", ep->bmAttributes);
continue;
} }
if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
out_ep = nullptr;
ep_offset = conf_offset;
in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
if (!in_ep) {
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
return nullopt;
} }
if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK) if (eps.in_ep != nullptr && eps.out_ep != nullptr && eps.notify_ep != nullptr)
in_ep = nullptr; return eps;
} }
if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr)
break;
}
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
return CdcEps{notify_ep, in_ep, out_ep, interface_number};
return CdcEps{notify_ep, out_ep, in_ep, interface_number};
} }
std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) { std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc; const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc; const usb_device_desc_t *device_desc;
int desc_offset = 0; int desc_offset = 0;
@ -78,7 +74,7 @@ std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t de
ESP_LOGE(TAG, "get_active_config_descriptor failed"); ESP_LOGE(TAG, "get_active_config_descriptor failed");
return {}; return {};
} }
if (device_desc->bDeviceClass == USB_CLASS_COMM) { if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
// single CDC-ACM device // single CDC-ACM device
if (auto eps = get_cdc(config_desc, 0)) { if (auto eps = get_cdc(config_desc, 0)) {
ESP_LOGV(TAG, "Found CDC-ACM device"); ESP_LOGV(TAG, "Found CDC-ACM device");
@ -194,7 +190,7 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
if (!channel->initialised_ || channel->input_started_ || if (!channel->initialised_ || channel->input_started_ ||
channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize) channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
return; return;
auto ep = channel->cdc_dev_.in_ep; const auto *ep = channel->cdc_dev_.in_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) { auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
if (!status.success) { if (!status.success) {
@ -227,7 +223,7 @@ void USBUartComponent::start_output(USBUartChannel *channel) {
if (channel->output_buffer_.is_empty()) { if (channel->output_buffer_.is_empty()) {
return; return;
} }
auto ep = channel->cdc_dev_.out_ep; const auto *ep = channel->cdc_dev_.out_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) { auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code); ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
channel->output_started_ = false; channel->output_started_ = false;
@ -259,15 +255,15 @@ static void fix_mps(const usb_ep_desc_t *ep) {
} }
} }
void USBUartTypeCdcAcm::on_connected() { void USBUartTypeCdcAcm::on_connected() {
auto cdc_devs = this->parse_descriptors_(this->device_handle_); auto cdc_devs = this->parse_descriptors(this->device_handle_);
if (cdc_devs.empty()) { if (cdc_devs.empty()) {
this->status_set_error("No CDC-ACM device found"); this->status_set_error("No CDC-ACM device found");
this->disconnect(); this->disconnect();
return; return;
} }
ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size()); ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
auto i = 0; size_t i = 0;
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (i == cdc_devs.size()) { if (i == cdc_devs.size()) {
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_); ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
this->status_set_warning("No configuration found for channel"); this->status_set_warning("No configuration found for channel");
@ -277,10 +273,11 @@ void USBUartTypeCdcAcm::on_connected() {
fix_mps(channel->cdc_dev_.in_ep); fix_mps(channel->cdc_dev_.in_ep);
fix_mps(channel->cdc_dev_.out_ep); fix_mps(channel->cdc_dev_.out_ep);
channel->initialised_ = true; channel->initialised_ = true;
auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0); auto err =
usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_, ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
channel->cdc_dev_.interface_number); channel->cdc_dev_.bulk_interface_number);
this->status_set_error("usb_host_interface_claim failed"); this->status_set_error("usb_host_interface_claim failed");
this->disconnect(); this->disconnect();
return; return;
@ -290,7 +287,7 @@ void USBUartTypeCdcAcm::on_connected() {
} }
void USBUartTypeCdcAcm::on_disconnected() { void USBUartTypeCdcAcm::on_disconnected() {
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (channel->cdc_dev_.in_ep != nullptr) { if (channel->cdc_dev_.in_ep != nullptr) {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
@ -303,7 +300,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
} }
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number); usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
channel->initialised_ = false; channel->initialised_ = false;
channel->input_started_ = false; channel->input_started_ = false;
channel->output_started_ = false; channel->output_started_ = false;
@ -314,7 +311,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
} }
void USBUartTypeCdcAcm::enable_channels() { void USBUartTypeCdcAcm::enable_channels() {
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (!channel->initialised_) if (!channel->initialised_)
continue; continue;
channel->input_started_ = false; channel->input_started_ = false;

View File

@ -25,7 +25,8 @@ struct CdcEps {
const usb_ep_desc_t *notify_ep; const usb_ep_desc_t *notify_ep;
const usb_ep_desc_t *in_ep; const usb_ep_desc_t *in_ep;
const usb_ep_desc_t *out_ep; const usb_ep_desc_t *out_ep;
uint8_t interface_number; uint8_t bulk_interface_number;
uint8_t interrupt_interface_number;
}; };
enum UARTParityOptions { enum UARTParityOptions {
@ -123,7 +124,7 @@ class USBUartTypeCdcAcm : public USBUartComponent {
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {} USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
protected: protected:
virtual std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl); virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
void on_connected() override; void on_connected() override;
virtual void enable_channels(); virtual void enable_channels();
void on_disconnected() override; void on_disconnected() override;
@ -134,7 +135,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm {
USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {} USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
protected: protected:
std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl) override; std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
void enable_channels() override; void enable_channels() override;
}; };
class USBUartTypeCH34X : public USBUartTypeCdcAcm { class USBUartTypeCH34X : public USBUartTypeCdcAcm {