mirror of
https://github.com/espressif/esp-idf
synced 2025-03-10 01:29:21 -04:00
usb_host: Enable custom CDC commands
This new API allows further extension for vendor specific commands
This commit is contained in:
parent
dd27d351ee
commit
0ab17ec695
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user