Skip to content

Commit

Permalink
feat(cdc): Add API for getting CDC functional descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
tore-espressif committed Feb 7, 2024
1 parent 803d027 commit 1caec88
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 55 deletions.
6 changes: 5 additions & 1 deletion host/class/cdc/usb_host_cdc_acm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
## [Unreleased]

- Added `cdc_acm_host_cdc_desc_get()` function that enables users to get CDC functional descriptors of the device

## 2.0.2

- Return an error if the selected interface does not have IN and OUT bulk endpoints

## 2.0.1

- Add support for USB "triple null" devices, which use USB interface association descriptors, but have Device Class, Device Subclass, and Device Protocol all set to 0x00, instead of 0xEF, 0x02, and 0x01 respectively. USB Standard reference: https://www.usb.org/defined-class-codes, Base Class 00h (Device) section.
- Added support for USB "triple null" devices, which use USB interface association descriptors, but have Device Class, Device Subclass, and Device Protocol all set to 0x00, instead of 0xEF, 0x02, and 0x01 respectively. USB Standard reference: https://www.usb.org/defined-class-codes, Base Class 00h (Device) section.

## 2.0.0

Expand Down
78 changes: 28 additions & 50 deletions host/class/cdc/usb_host_cdc_acm/cdc_acm_host.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -75,47 +75,6 @@ static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = {
.new_dev_cb = NULL,
};

/**
* @brief USB CDC PSTN Call Descriptor
*
* @see Table 3, USB CDC-PSTN specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType;
const cdc_desc_subtype_t bDescriptorSubtype;
union {
struct {
uint8_t call_management: 1; // Device handles call management itself
uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface
uint8_t reserved: 6;
};
uint8_t val;
} bmCapabilities;
uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management
} __attribute__((packed)) cdc_acm_call_desc_t;

/**
* @brief USB CDC PSTN Abstract Control Model Descriptor
*
* @see Table 4, USB CDC-PSTN specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType;
const cdc_desc_subtype_t bDescriptorSubtype;
union {
struct {
uint8_t feature: 1; // Device supports Set/Clear/Get_Comm_Feature requests
uint8_t serial: 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications
uint8_t send_break: 1; // Device supports Send_Break request
uint8_t network: 1; // Device supports Network_Connection notification
uint8_t reserved: 4;
};
uint8_t val;
} bmCapabilities;
} __attribute__((packed)) cdc_acm_acm_desc_t;

typedef struct cdc_dev_s cdc_dev_t;
struct cdc_dev_s {
usb_device_handle_t dev_hdl; // USB device handle
Expand All @@ -141,8 +100,8 @@ struct cdc_dev_s {
cdc_acm_uart_state_t serial_state; // Serial State
cdc_comm_protocol_t comm_protocol;
cdc_data_protocol_t data_protocol;
int num_cdc_intf_desc; // Number of CDC Interface descriptors in following array
const usb_standard_desc_t **cdc_intf_desc; // CDC Interface descriptors
int num_cdc_func_desc; // Number of CDC Functional descriptors in following array
const usb_standard_desc_t **cdc_func_desc; // CDC Functional descriptors
SLIST_ENTRY(cdc_dev_s) list_entry;
};

Expand Down Expand Up @@ -332,7 +291,7 @@ static void cdc_acm_device_remove(cdc_dev_t *cdc_dev)
{
assert(cdc_dev);
cdc_acm_transfers_free(cdc_dev);
free(cdc_dev->cdc_intf_desc);
free(cdc_dev->cdc_func_desc);
// We don't check the error code of usb_host_device_close, as the close might fail, if someone else is still using the device (not all interfaces are released)
usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl); // Gracefully continue on error
free(cdc_dev);
Expand Down Expand Up @@ -732,11 +691,11 @@ static esp_err_t cdc_acm_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_
if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE ))) {
break; // We found all CDC specific descriptors
}
cdc_dev->num_cdc_intf_desc++;
cdc_dev->cdc_intf_desc =
realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *)));
assert(cdc_dev->cdc_intf_desc);
cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc;
cdc_dev->num_cdc_func_desc++;
cdc_dev->cdc_func_desc =
realloc(cdc_dev->cdc_func_desc, cdc_dev->num_cdc_func_desc * (sizeof(usb_standard_desc_t *)));
assert(cdc_dev->cdc_func_desc);
cdc_dev->cdc_func_desc[cdc_dev->num_cdc_func_desc - 1] = cdc_desc;
} while (1);
*notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset);
assert(notif_ep);
Expand Down Expand Up @@ -1329,3 +1288,22 @@ esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protoco
}
return ESP_OK;
}

esp_err_t cdc_acm_host_cdc_desc_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_desc_subtype_t desc_type, const usb_standard_desc_t **desc_out)
{
CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG);
CDC_ACM_CHECK(desc_type < USB_CDC_DESC_SUBTYPE_CNT, ESP_ERR_INVALID_ARG);
cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl;
esp_err_t ret = ESP_ERR_NOT_FOUND;
*desc_out = NULL;

for (int i = 0; i < cdc_dev->num_cdc_func_desc; i++) {
cdc_header_desc_t *_desc = (cdc_header_desc_t *)(cdc_dev->cdc_func_desc[i]);
if (_desc->bDescriptorSubtype == desc_type) {
ret = ESP_OK;
*desc_out = cdc_dev->cdc_func_desc[i];
break;
}
}
return ret;
}
15 changes: 14 additions & 1 deletion host/class/cdc/usb_host_cdc_acm/include/usb/cdc_acm_host.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -270,6 +270,19 @@ void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl);
*/
esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data);

/**
* @brief Get CDC functional descriptor
*
* @param cdc_hdl CDC handle obtained from cdc_acm_host_open()
* @param[in] desc_type Type of functional descriptor
* @param[out] desc_out Pointer to the required descriptor
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: Invalid device or descriptor type
* - ESP_ERR_NOT_FOUND: The required descriptor is not present in the device
*/
esp_err_t cdc_acm_host_cdc_desc_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_desc_subtype_t desc_type, const usb_standard_desc_t **desc_out);

/**
* @brief Send command to CTRL endpoint
*
Expand Down
46 changes: 44 additions & 2 deletions host/class/cdc/usb_host_cdc_acm/include/usb/usb_types_cdc.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -39,7 +39,8 @@ typedef enum {
USB_CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor
USB_CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor
USB_CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor
USB_CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor
USB_CDC_DESC_SUBTYPE_NCM = 0x1A, // NCM Functional Descriptor
USB_CDC_DESC_SUBTYPE_CNT
} __attribute__((packed)) cdc_desc_subtype_t;

/**
Expand Down Expand Up @@ -204,3 +205,44 @@ typedef struct {
const uint8_t bControlInterface; // Master/controlling interface
uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces
} __attribute__((packed)) cdc_union_desc_t;

/**
* @brief USB CDC PSTN Call Descriptor
*
* @see Table 3, USB CDC-PSTN specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType;
const cdc_desc_subtype_t bDescriptorSubtype;
union {
struct {
uint8_t call_management: 1; // Device handles call management itself
uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface
uint8_t reserved: 6;
};
uint8_t val;
} bmCapabilities;
uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management
} __attribute__((packed)) cdc_acm_call_desc_t;

/**
* @brief USB CDC PSTN Abstract Control Model Descriptor
*
* @see Table 4, USB CDC-PSTN specification rev. 1.2
*/
typedef struct {
uint8_t bFunctionLength;
const uint8_t bDescriptorType;
const cdc_desc_subtype_t bDescriptorSubtype;
union {
struct {
uint8_t feature: 1; // Device supports Set/Clear/Get_Comm_Feature requests
uint8_t serial: 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications
uint8_t send_break: 1; // Device supports Send_Break request
uint8_t network: 1; // Device supports Network_Connection notification
uint8_t reserved: 4;
};
uint8_t val;
} bmCapabilities;
} __attribute__((packed)) cdc_acm_acm_desc_t;
48 changes: 47 additions & 1 deletion host/class/cdc/usb_host_cdc_acm/test/test_cdc_acm_host.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
Expand Down Expand Up @@ -520,6 +520,52 @@ TEST_CASE("rx_buffer", "[cdc_acm]")
vTaskDelay(20);
}

TEST_CASE("functional_descriptor", "[cdc_acm]")
{
test_install_cdc_driver();

cdc_acm_dev_hdl_t cdc_dev;
const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 500,
.out_buffer_size = 64,
.event_cb = notif_cb,
.data_cb = NULL
};

TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev));
TEST_ASSERT_NOT_NULL(cdc_dev);

// Request various CDC functional descriptors
// Following are present in the TinyUSB CDC device: Header, Call management, ACM, Union
const cdc_header_desc_t *header_desc;
const cdc_acm_call_desc_t *call_desc;
const cdc_acm_acm_desc_t *acm_desc;
const cdc_union_desc_t *union_desc;

TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_HEADER, (const usb_standard_desc_t **)&header_desc));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_CALL, (const usb_standard_desc_t **)&call_desc));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_ACM, (const usb_standard_desc_t **)&acm_desc));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_UNION, (const usb_standard_desc_t **)&union_desc));
TEST_ASSERT_NOT_NULL(header_desc);
TEST_ASSERT_NOT_NULL(call_desc);
TEST_ASSERT_NOT_NULL(acm_desc);
TEST_ASSERT_NOT_NULL(union_desc);
TEST_ASSERT_EQUAL(USB_CDC_DESC_SUBTYPE_HEADER, header_desc->bDescriptorSubtype);
TEST_ASSERT_EQUAL(USB_CDC_DESC_SUBTYPE_CALL, call_desc->bDescriptorSubtype);
TEST_ASSERT_EQUAL(USB_CDC_DESC_SUBTYPE_ACM, acm_desc->bDescriptorSubtype);
TEST_ASSERT_EQUAL(USB_CDC_DESC_SUBTYPE_UNION, union_desc->bDescriptorSubtype);

// Check few errors
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_OBEX, (const usb_standard_desc_t **)&header_desc));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_cdc_desc_get(cdc_dev, USB_CDC_DESC_SUBTYPE_CNT, (const usb_standard_desc_t **)&header_desc));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_cdc_desc_get(NULL, USB_CDC_DESC_SUBTYPE_HEADER, (const usb_standard_desc_t **)&header_desc));

// Clean-up
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev));
TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall());
vTaskDelay(20);
}

/* Following test case implements dual CDC-ACM USB device that can be used as mock device for CDC-ACM Host tests */
void run_usb_dual_cdc_device(void);
TEST_CASE("mock_device_app", "[cdc_acm_device][ignore]")
Expand Down

0 comments on commit 1caec88

Please sign in to comment.