diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c index 5a613913b7..0edcaa4791 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c @@ -548,7 +548,7 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des { esp_err_t ret; - // 1. Setup notification and control transfers if they are supported + // 1. Setup notification transfer if it is supported if (notif_ep_desc) { ESP_GOTO_ON_ERROR( usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, &cdc_dev->notif.xfer), @@ -558,24 +558,25 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des cdc_dev->notif.xfer->callback = notif_xfer_cb; cdc_dev->notif.xfer->context = cdc_dev; cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc); - - usb_device_info_t dev_info; - ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); - ESP_GOTO_ON_ERROR( - usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer), - err, TAG,); - cdc_dev->ctrl_transfer->timeout_ms = 1000; - cdc_dev->ctrl_transfer->bEndpointAddress = 0; - cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; - cdc_dev->ctrl_transfer->context = cdc_dev; - cdc_dev->ctrl_transfer->callback = out_xfer_cb; - cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); - ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG,); - cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); - ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG,); } - // 2. Setup IN data transfer + // 2. Setup control transfer + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer), + err, TAG,); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG,); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG,); + + // 3. Setup IN data transfer ESP_GOTO_ON_ERROR( usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(in_ep_desc), 0, &cdc_dev->data.in_xfer), err, TAG, @@ -587,7 +588,7 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; cdc_dev->data.in_xfer->context = cdc_dev; - // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + // 4. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) if (out_buf_len != 0) { ESP_GOTO_ON_ERROR( usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), @@ -771,8 +772,10 @@ esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t int desc_offset; ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, interface_num, 0, &desc_offset); + ESP_GOTO_ON_FALSE( + cdc_dev->data.intf_desc, + ESP_ERR_NOT_FOUND, err, TAG, "Required interfece no %d was not found.", interface_num); const int temp_offset = desc_offset; // Save this offset for later - assert(cdc_dev->data.intf_desc); // The interface can have 2-3 endpoints. 2 for data and 1 optional for notifications const usb_ep_desc_t *in_ep = NULL; @@ -1080,7 +1083,7 @@ unblock: esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding) { - CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); ESP_RETURN_ON_ERROR( send_cdc_request((cdc_dev_t *)cdc_hdl, true, USB_CDC_REQ_GET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), @@ -1092,7 +1095,7 @@ esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_c esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding) { - CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); ESP_RETURN_ON_ERROR( send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), @@ -1104,8 +1107,6 @@ esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_ esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts) { - CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); - const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1); ESP_RETURN_ON_ERROR( @@ -1117,8 +1118,6 @@ esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dt esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms) { - CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); - ESP_RETURN_ON_ERROR( send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SEND_BREAK, NULL, 0, duration_ms), TAG,); @@ -1128,37 +1127,42 @@ esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_m return ESP_OK; } -static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value) +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data) { + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + if (wLength > 0) { + CDC_ACM_CHECK(data, ESP_ERR_INVALID_ARG); + } + CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= wLength, ESP_ERR_INVALID_SIZE); + esp_err_t ret; - CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); - CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); // Take Mutex and fill the CTRL request - BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(5000)); if (!taken) { return ESP_ERR_TIMEOUT; } usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t); - req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; - req->bRequest = request; - req->wValue = value; - req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; - req->wLength = data_len; + req->bmRequestType = bmRequestType; + req->bRequest = bRequest; + req->wValue = wValue; + req->wIndex = wIndex; + req->wLength = wLength; - if (in_transfer) { - req->bmRequestType |= USB_BM_REQUEST_TYPE_DIR_IN; - } else { - memcpy(start_of_data, data, data_len); + // For IN transfers we must transfer data ownership to CDC driver + const bool in_transfer = bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; + if (!in_transfer) { + memcpy(start_of_data, data, wLength); } - cdc_dev->ctrl_transfer->num_bytes = data_len + sizeof(usb_setup_packet_t); + cdc_dev->ctrl_transfer->num_bytes = wLength + sizeof(usb_setup_packet_t); ESP_GOTO_ON_ERROR( usb_host_transfer_submit_control(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->ctrl_transfer), unblock, TAG, "CTRL transfer failed"); - taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(5000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 5 seconds if (!taken) { // Transfer was not finished, error in USB LIB. Reset the endpoint cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); @@ -1169,8 +1173,9 @@ static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_requ ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + // For OUT transfers, we must transfer data ownership to user if (in_transfer) { - memcpy(data, start_of_data, data_len); + memcpy(data, start_of_data, wLength); } ret = ESP_OK; @@ -1179,6 +1184,20 @@ unblock: return ret; } +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value) +{ + CDC_ACM_CHECK(cdc_dev, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->notif.intf_desc, ESP_ERR_NOT_SUPPORTED); + + uint8_t req_type = USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + if (in_transfer) { + req_type |= USB_BM_REQUEST_TYPE_DIR_IN; + } else { + req_type |= USB_BM_REQUEST_TYPE_DIR_OUT; + } + return cdc_acm_host_send_custom_request((cdc_acm_dev_hdl_t) cdc_dev, req_type, request, value, cdc_dev->notif.intf_desc->bInterfaceNumber, data_len, data); +} + 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) { CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h index 3724c919c6..d3143fecd2 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h @@ -238,6 +238,24 @@ 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 Send command to CTRL endpoint + * + * Sends Control transfer as described in USB specification chapter 9. + * This function can be used by device drivers that use custom/vendor specific commands. + * These commands can either extend or replace commands defined in USB CDC-PSTN specification rev. 1.2. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] bmRequestType Field of USB control request + * @param[in] bRequest Field of USB control request + * @param[in] wValue Field of USB control request + * @param[in] wIndex Field of USB control request + * @param[in] wLength Field of USB control request + * @param[inout] data Field of USB control request + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data); + #ifdef __cplusplus } class CdcAcmDevice @@ -294,6 +312,11 @@ public: return cdc_acm_host_send_break(this->cdc_hdl, duration_ms); } + inline esp_err_t send_custom_request(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data) + { + return cdc_acm_host_send_custom_request(this->cdc_hdl, bmRequestType, bRequest, wValue, wIndex, wLength, data); + } + private: CdcAcmDevice(const CdcAcmDevice &Copy); CdcAcmDevice &operator= (const CdcAcmDevice &Copy); diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c index a340ac8158..526624e720 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c @@ -373,11 +373,39 @@ TEST_CASE("error_handling", "[cdc_acm]") vTaskDelay(20); } +TEST_CASE("custom_command", "[cdc_acm]") +{ + test_install_cdc_driver(); + + // Open device with only CTRL endpoint (endpoint no 0) + cdc_acm_dev_hdl_t cdc_dev; + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 0, + .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); + + // Corresponds to command: Set Control Line State, DTR on, RTS off + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_custom_request(cdc_dev, 0x21, 34, 1, 0, 0, NULL)); + + // 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]") { run_usb_dual_cdc_device(); + while (1) { + vTaskDelay(10); + } } #endif