Skip to content

Commit 994a397

Browse files
refactor(cdc_acm): Separate descriptor parsing logic
Update CDC-ACM test_app to IDF v5.0
1 parent 787efe2 commit 994a397

File tree

13 files changed

+436
-317
lines changed

13 files changed

+436
-317
lines changed

host/class/cdc/usb_host_cdc_acm/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [Unreleased]
2+
3+
- Fixed CDC descriptor parsing logic, when some CDC devices could not be opened
4+
15
## 2.0.4
26

37
- Fixed Control transfer allocation size for too small EP0 Max Packet Size (https://github.com/espressif/esp-idf/issues/14345)
Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
1-
set(srcs)
2-
set(include)
3-
# As CONFIG_USB_OTG_SUPPORTED comes from Kconfig, it is not evaluated yet
4-
# when components are being registered.
5-
set(require usb)
6-
7-
if(CONFIG_USB_OTG_SUPPORTED)
8-
list(APPEND srcs "cdc_acm_host.c")
9-
list(APPEND include "include")
10-
endif()
11-
12-
idf_component_register(SRCS ${srcs}
13-
INCLUDE_DIRS ${include}
14-
REQUIRES ${require}
1+
idf_component_register(SRCS "cdc_acm_host.c" "cdc_host_parsing.c"
2+
INCLUDE_DIRS "include"
3+
PRIV_INCLUDE_DIRS "private_include"
4+
REQUIRES usb
155
)

host/class/cdc/usb_host_cdc_acm/cdc_acm_host.c

Lines changed: 32 additions & 246 deletions
Large diffs are not rendered by default.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdbool.h>
8+
#include <string.h>
9+
#include "esp_check.h"
10+
#include "esp_log.h"
11+
#include "usb/usb_helpers.h"
12+
#include "usb/usb_types_cdc.h"
13+
#include "usb_cdc_acm_host_parsing.h"
14+
15+
// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD only when
16+
// bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association Descriptor),
17+
// or when bDeviceClass, bDeviceSubClass, and bDeviceProtocol are 0x00 (Null class code triple), as per https://www.usb.org/defined-class-codes, "Base Class 00h (Device)" section
18+
// @see USB Interface Association Descriptor: Device Class Code and Use Model rev 1.0, Table 1-1
19+
#define USB_SUBCLASS_NULL 0x00
20+
#define USB_SUBCLASS_COMMON 0x02
21+
#define USB_PROTOCOL_NULL 0x00
22+
#define USB_DEVICE_PROTOCOL_IAD 0x01
23+
24+
static const char *TAG = "cdc_acm_parsing";
25+
26+
/**
27+
* @brief Check if the required interface is CDC compliant
28+
*
29+
* @param[in] device_desc Pointer to Device descriptor
30+
* @param[in] config_desc Pointer do Configuration descriptor
31+
* @param[in] intf_idx Index of the required interface
32+
* @return true The required interface is CDC compliant
33+
* @return false The required interface is NOT CDC compliant
34+
*/
35+
static bool cdc_parse_is_cdc_compliant(const usb_device_desc_t *device_desc, const usb_config_desc_t *config_desc, uint8_t intf_idx)
36+
{
37+
int desc_offset = 0;
38+
if (device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) {
39+
const usb_intf_desc_t *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx, 0, NULL);
40+
if (intf_desc->bInterfaceClass == USB_CLASS_COMM) {
41+
// 1. This is a Communication Device Class: Class defined in Interface descriptor
42+
return true;
43+
}
44+
} else if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
45+
(device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
46+
((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
47+
(device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
48+
const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc;
49+
while ((this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset))) {
50+
const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc;
51+
if ((iad_desc->bFirstInterface == intf_idx) &&
52+
(iad_desc->bInterfaceCount == 2) &&
53+
(iad_desc->bFunctionClass == USB_CLASS_COMM)) {
54+
// 2. This is a composite device, that uses Interface Association Descriptor
55+
return true;
56+
}
57+
};
58+
}
59+
return false;
60+
}
61+
62+
/**
63+
* @brief Parse CDC functional descriptors
64+
*
65+
* @attention The driver must take care of memory freeing
66+
* @param[in] intf_desc Pointer to Notification interface descriptor
67+
* @param[in] total_len wTotalLength of the Configuration descriptor
68+
* @param[in] desc_offset Offset of the intf_desc in the Configuration descriptor in bytes
69+
* @param[out] desc_cnt Number of Functional descriptors found
70+
* @return Pointer to array of pointers to Functional descriptors
71+
*/
72+
static cdc_func_array_t *cdc_parse_functional_descriptors(const usb_intf_desc_t *intf_desc, uint16_t total_len, int desc_offset, int *desc_cnt)
73+
{
74+
// CDC specific descriptors should be right after CDC-Communication interface descriptor
75+
// Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type.
76+
// The latter could return CDC specific descriptors that don't belong to this interface
77+
int func_desc_cnt = 0;
78+
int intf_offset = desc_offset;
79+
const usb_standard_desc_t *cdc_desc = (const usb_standard_desc_t *)intf_desc;
80+
while ((cdc_desc = usb_parse_next_descriptor(cdc_desc, total_len, &intf_offset))) {
81+
if (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE )) {
82+
if (func_desc_cnt == 0) {
83+
return NULL; // There are no CDC specific descriptors
84+
} else {
85+
break; // We found all CDC specific descriptors
86+
}
87+
}
88+
func_desc_cnt++;
89+
}
90+
91+
// Allocate memory for the functional descriptors pointers
92+
cdc_func_array_t *func_desc = malloc(func_desc_cnt * (sizeof(usb_standard_desc_t *)));
93+
if (!func_desc) {
94+
ESP_LOGD(TAG, "Out of mem for functional descriptors");
95+
return NULL;
96+
}
97+
98+
// Save the descriptors
99+
intf_offset = desc_offset; // Reset the offset counter
100+
cdc_desc = (const usb_standard_desc_t *)intf_desc;
101+
for (int i = 0; i < func_desc_cnt; i++) {
102+
cdc_desc = (const usb_standard_desc_t *)usb_parse_next_descriptor(cdc_desc, total_len, &intf_offset);
103+
(*func_desc)[i] = cdc_desc;
104+
}
105+
*desc_cnt = func_desc_cnt;
106+
return func_desc;
107+
}
108+
109+
esp_err_t cdc_parse_interface(const usb_device_desc_t *device_desc, const usb_config_desc_t *config_desc, uint8_t intf_idx, cdc_parsed_info_t *info_ret)
110+
{
111+
int desc_offset = 0;
112+
113+
memset(info_ret, 0, sizeof(cdc_parsed_info_t));
114+
const usb_intf_desc_t *first_intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx, 0, &desc_offset);
115+
ESP_RETURN_ON_FALSE(
116+
first_intf_desc,
117+
ESP_ERR_NOT_FOUND, TAG, "Required interface no %d was not found.", intf_idx);
118+
119+
int temp_offset = desc_offset;
120+
for (int i = 0; i < first_intf_desc->bNumEndpoints; i++) {
121+
const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(first_intf_desc, i, config_desc->wTotalLength, &desc_offset);
122+
assert(this_ep);
123+
124+
if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_INTR) {
125+
info_ret->notif_intf = first_intf_desc;
126+
info_ret->notif_ep = this_ep;
127+
} else if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_BULK) {
128+
info_ret->data_intf = first_intf_desc;
129+
if (USB_EP_DESC_GET_EP_DIR(this_ep)) {
130+
info_ret->in_ep = this_ep;
131+
} else {
132+
info_ret->out_ep = this_ep;
133+
}
134+
}
135+
desc_offset = temp_offset;
136+
}
137+
138+
const bool cdc_compliant = cdc_parse_is_cdc_compliant(device_desc, config_desc, intf_idx);
139+
if (cdc_compliant) {
140+
info_ret->notif_intf = first_intf_desc; // We make sure that intf_desc is set for CDC compliant devices that use EP0 as notification element
141+
info_ret->func = cdc_parse_functional_descriptors(first_intf_desc, config_desc->wTotalLength, desc_offset, &info_ret->func_cnt);
142+
}
143+
144+
if (!info_ret->data_intf && cdc_compliant) {
145+
// CDC compliant devices have data endpoints in the second interface
146+
// Some devices offer alternate settings for data interface:
147+
// First interface with 0 endpoints (default control pipe only) and second with standard 2 endpoints for full-duplex data
148+
// We always select interface with 2 bulk endpoints
149+
const int num_of_alternate = usb_parse_interface_number_of_alternate(config_desc, intf_idx + 1);
150+
for (int i = 0; i < num_of_alternate + 1; i++) {
151+
const usb_intf_desc_t *second_intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx + 1, i, &desc_offset);
152+
temp_offset = desc_offset;
153+
if (second_intf_desc && second_intf_desc->bNumEndpoints == 2) {
154+
for (int i = 0; i < second_intf_desc->bNumEndpoints; i++) {
155+
const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(second_intf_desc, i, config_desc->wTotalLength, &desc_offset);
156+
assert(this_ep);
157+
if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_BULK) {
158+
info_ret->data_intf = second_intf_desc;
159+
if (USB_EP_DESC_GET_EP_DIR(this_ep)) {
160+
info_ret->in_ep = this_ep;
161+
} else {
162+
info_ret->out_ep = this_ep;
163+
}
164+
}
165+
desc_offset = temp_offset;
166+
}
167+
break;
168+
}
169+
}
170+
}
171+
172+
// If we did not find IN and OUT data endpoints, the device cannot be used
173+
return (info_ret->in_ep && info_ret->out_ep) ? ESP_OK : ESP_ERR_NOT_FOUND;
174+
}
175+
176+
void cdc_print_desc(const usb_standard_desc_t *_desc)
177+
{
178+
if (_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE )) {
179+
// Quietly return in case that this descriptor is not CDC interface descriptor
180+
return;
181+
}
182+
183+
switch (((cdc_header_desc_t *)_desc)->bDescriptorSubtype) {
184+
case USB_CDC_DESC_SUBTYPE_HEADER: {
185+
cdc_header_desc_t *desc = (cdc_header_desc_t *)_desc;
186+
printf("\t*** CDC Header Descriptor ***\n");
187+
printf("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), ((desc->bcdCDC >> 4) & 0xF));
188+
break;
189+
}
190+
case USB_CDC_DESC_SUBTYPE_CALL: {
191+
cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)_desc;
192+
printf("\t*** CDC Call Descriptor ***\n");
193+
printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val);
194+
printf("\tbDataInterface: %d\n", desc->bDataInterface);
195+
break;
196+
}
197+
case USB_CDC_DESC_SUBTYPE_ACM: {
198+
cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)_desc;
199+
printf("\t*** CDC ACM Descriptor ***\n");
200+
printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val);
201+
break;
202+
}
203+
case USB_CDC_DESC_SUBTYPE_UNION: {
204+
cdc_union_desc_t *desc = (cdc_union_desc_t *)_desc;
205+
printf("\t*** CDC Union Descriptor ***\n");
206+
printf("\tbControlInterface: %d\n", desc->bControlInterface);
207+
printf("\tbSubordinateInterface[0]: %d\n", desc->bSubordinateInterface[0]);
208+
break;
209+
}
210+
default:
211+
ESP_LOGW(TAG, "Unsupported CDC specific descriptor");
212+
break;
213+
}
214+
}

host/class/cdc/usb_host_cdc_acm/idf_component.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,3 @@ tags:
88
url: https://github.com/espressif/esp-usb/tree/master/host/class/cdc/usb_host_cdc_acm
99
dependencies:
1010
idf: ">=4.4"
11-
targets:
12-
- esp32s2
13-
- esp32s3
14-
- esp32p4

host/class/cdc/usb_host_cdc_acm/include/usb/cdc_acm_host.h

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#include <stdbool.h>
1010
#include "usb/usb_host.h"
11-
#include "usb_types_cdc.h"
11+
#include "usb/usb_types_cdc.h"
1212
#include "esp_err.h"
1313

1414
#ifdef __cplusplus
@@ -17,47 +17,6 @@ extern "C" {
1717

1818
typedef struct cdc_dev_s *cdc_acm_dev_hdl_t;
1919

20-
/**
21-
* @brief USB CDC PSTN Call Descriptor
22-
*
23-
* @see Table 3, USB CDC-PSTN specification rev. 1.2
24-
*/
25-
typedef struct {
26-
uint8_t bFunctionLength;
27-
const uint8_t bDescriptorType;
28-
const cdc_desc_subtype_t bDescriptorSubtype;
29-
union {
30-
struct {
31-
uint8_t call_management: 1; // Device handles call management itself
32-
uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface
33-
uint8_t reserved: 6;
34-
};
35-
uint8_t val;
36-
} bmCapabilities;
37-
uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management
38-
} __attribute__((packed)) cdc_acm_call_desc_t;
39-
40-
/**
41-
* @brief USB CDC PSTN Abstract Control Model Descriptor
42-
*
43-
* @see Table 4, USB CDC-PSTN specification rev. 1.2
44-
*/
45-
typedef struct {
46-
uint8_t bFunctionLength;
47-
const uint8_t bDescriptorType;
48-
const cdc_desc_subtype_t bDescriptorSubtype;
49-
union {
50-
struct {
51-
uint8_t feature: 1; // Device supports Set/Clear/Get_Comm_Feature requests
52-
uint8_t serial: 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications
53-
uint8_t send_break: 1; // Device supports Send_Break request
54-
uint8_t network: 1; // Device supports Network_Connection notification
55-
uint8_t reserved: 4;
56-
};
57-
uint8_t val;
58-
} bmCapabilities;
59-
} __attribute__((packed)) cdc_acm_acm_desc_t;
60-
6120
/**
6221
* @brief Line Coding structure
6322
* @see Table 17, USB CDC-PSTN specification rev. 1.2

host/class/cdc/usb_host_cdc_acm/include/usb/usb_types_cdc.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,44 @@ typedef struct {
205205
const uint8_t bControlInterface; // Master/controlling interface
206206
uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces
207207
} __attribute__((packed)) cdc_union_desc_t;
208+
209+
/**
210+
* @brief USB CDC PSTN Call Descriptor
211+
*
212+
* @see Table 3, USB CDC-PSTN specification rev. 1.2
213+
*/
214+
typedef struct {
215+
uint8_t bFunctionLength;
216+
const uint8_t bDescriptorType;
217+
const cdc_desc_subtype_t bDescriptorSubtype;
218+
union {
219+
struct {
220+
uint8_t call_management: 1; // Device handles call management itself
221+
uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface
222+
uint8_t reserved: 6;
223+
};
224+
uint8_t val;
225+
} bmCapabilities;
226+
uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management
227+
} __attribute__((packed)) cdc_acm_call_desc_t;
228+
229+
/**
230+
* @brief USB CDC PSTN Abstract Control Model Descriptor
231+
*
232+
* @see Table 4, USB CDC-PSTN specification rev. 1.2
233+
*/
234+
typedef struct {
235+
uint8_t bFunctionLength;
236+
const uint8_t bDescriptorType;
237+
const cdc_desc_subtype_t bDescriptorSubtype;
238+
union {
239+
struct {
240+
uint8_t feature: 1; // Device supports Set/Clear/Get_Comm_Feature requests
241+
uint8_t serial: 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications
242+
uint8_t send_break: 1; // Device supports Send_Break request
243+
uint8_t network: 1; // Device supports Network_Connection notification
244+
uint8_t reserved: 4;
245+
};
246+
uint8_t val;
247+
} bmCapabilities;
248+
} __attribute__((packed)) cdc_acm_acm_desc_t;

0 commit comments

Comments
 (0)