|
| 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 | +} |
0 commit comments