From 42076af4c4c20bf77517e1f187b7d849690fb1bb Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Fri, 23 Feb 2024 04:25:10 +0800 Subject: [PATCH 01/14] refactor(usb): Update USBH event callback arguments This commit does the following: - Updates the USBH event callback arguments to now pass a usbh_event_data_t which can contain different data for each event - Updated event names --- components/usb/private_include/usbh.h | 30 +++++++++++++++++++++------ components/usb/usb_host.c | 21 +++++++------------ components/usb/usbh.c | 28 +++++++++++++++++++------ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 0fafea2974..c14eee8658 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,12 +29,31 @@ typedef struct usbh_ep_handle_s *usbh_ep_handle_t; // ----------------------- Events -------------------------- +/** + * @brief Enumerator for various USBH events + */ typedef enum { - USBH_EVENT_DEV_NEW, /**< A new device has been enumerated and added to the device pool */ + USBH_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the device pool */ USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ - USBH_EVENT_DEV_ALL_FREE, /**< All devices have been freed */ + USBH_EVENT_ALL_FREE, /**< All devices have been freed */ } usbh_event_t; +/** + * @brief Event data object for USBH events + */ +typedef struct { + usbh_event_t event; + union { + struct { + uint8_t dev_addr; + } new_dev_data; + struct { + uint8_t dev_addr; + usb_device_handle_t dev_hdl; + } dev_gone_data; + }; +} usbh_event_data_t; + /** * @brief Endpoint events * @@ -109,9 +128,8 @@ typedef void (*usbh_ctrl_xfer_cb_t)(usb_device_handle_t dev_hdl, urb_t *urb, voi * @brief Callback used to indicate that the USBH has an event * * @note This callback is called from within usbh_process() - * @note On a USBH_EVENT_DEV_ALL_FREE event, the dev_hdl argument is set to NULL */ -typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg); +typedef void (*usbh_event_cb_t)(usbh_event_data_t *event_data, void *arg); /** * @brief Callback used by the USBH to request actions from the Hub driver @@ -497,7 +515,7 @@ esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str /** * @brief Indicate the device enumeration is completed * - * This will all the device to be opened by clients, and also trigger a USBH_EVENT_DEV_NEW event. + * This will allow the device to be opened by clients, and also trigger a USBH_EVENT_NEW_DEV event. * * @note Hub Driver only * @note Must call in sequence diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 8328927952..080fd8a36d 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -275,33 +275,28 @@ static void ctrl_xfer_callback(usb_device_handle_t dev_hdl, urb_t *urb, void *ar HOST_EXIT_CRITICAL(); } -static void dev_event_callback(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg) +static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) { - // Check usbh_event. The data type of event_arg depends on the type of event - switch (usbh_event) { - case USBH_EVENT_DEV_NEW: { + switch (event_data->event) { + case USBH_EVENT_NEW_DEV: { // Prepare a NEW_DEV client event message, the send it to all clients - uint8_t dev_addr; - ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); usb_host_client_event_msg_t event_msg = { .event = USB_HOST_CLIENT_EVENT_NEW_DEV, - .new_dev.address = dev_addr, + .new_dev.address = event_data->new_dev_data.dev_addr, }; send_event_msg_to_clients(&event_msg, true, 0); break; } case USBH_EVENT_DEV_GONE: { // Prepare event msg, send only to clients that have opened the device - uint8_t dev_addr; - ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); usb_host_client_event_msg_t event_msg = { .event = USB_HOST_CLIENT_EVENT_DEV_GONE, - .dev_gone.dev_hdl = dev_hdl, + .dev_gone.dev_hdl = event_data->dev_gone_data.dev_hdl, }; - send_event_msg_to_clients(&event_msg, false, dev_addr); + send_event_msg_to_clients(&event_msg, false, event_data->dev_gone_data.dev_addr); break; } - case USBH_EVENT_DEV_ALL_FREE: { + case USBH_EVENT_ALL_FREE: { // Notify the lib handler that all devices are free HOST_ENTER_CRITICAL(); p_host_lib_obj->dynamic.lib_event_flags |= USB_HOST_LIB_EVENT_FLAGS_ALL_FREE; @@ -401,7 +396,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config) .proc_req_cb_arg = NULL, .ctrl_xfer_cb = ctrl_xfer_callback, .ctrl_xfer_cb_arg = NULL, - .event_cb = dev_event_callback, + .event_cb = usbh_event_callback, .event_cb_arg = NULL, }; ret = usbh_install(&usbh_config); diff --git a/components/usb/usbh.c b/components/usb/usbh.c index faceb904bd..b4297bbd88 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -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: Apache-2.0 */ @@ -35,7 +35,7 @@ typedef enum { DEV_ACTION_FREE_AND_RECOVER = (1 << 5), // Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. DEV_ACTION_FREE = (1 << 6), // Free the device object DEV_ACTION_PORT_DISABLE = (1 << 7), // Request the hub driver to disable the port of the device - DEV_ACTION_PROP_NEW = (1 << 8), // Propagate a USBH_EVENT_DEV_NEW event + DEV_ACTION_PROP_NEW = (1 << 8), // Propagate a USBH_EVENT_NEW_DEV event } dev_action_t; typedef struct device_s device_t; @@ -500,7 +500,14 @@ static inline void handle_prop_gone_evt(device_t *dev_obj) { // Flush EP0's pipe. Then propagate a USBH_EVENT_DEV_GONE event ESP_LOGE(USBH_TAG, "Device %d gone", dev_obj->constant.address); - p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_GONE, p_usbh_obj->constant.event_cb_arg); + usbh_event_data_t event_data = { + .event = USBH_EVENT_DEV_GONE, + .dev_gone_data = { + .dev_addr = dev_obj->constant.address, + .dev_hdl = (usb_device_handle_t)dev_obj, + }, + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } static void handle_free_and_recover(device_t *dev_obj, bool recover_port) @@ -526,10 +533,13 @@ static void handle_free_and_recover(device_t *dev_obj, bool recover_port) xSemaphoreGive(p_usbh_obj->constant.mux_lock); device_free(dev_obj); - // If all devices have been freed, propagate a USBH_EVENT_DEV_ALL_FREE event + // If all devices have been freed, propagate a USBH_EVENT_ALL_FREE event if (all_free) { ESP_LOGD(USBH_TAG, "Device all free"); - p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg); + usbh_event_data_t event_data = { + .event = USBH_EVENT_ALL_FREE, + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } // Check if we need to recover the device's port if (recover_port) { @@ -547,7 +557,13 @@ static inline void handle_port_disable(device_t *dev_obj) static inline void handle_prop_new_evt(device_t *dev_obj) { ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); - p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_NEW, p_usbh_obj->constant.event_cb_arg); + usbh_event_data_t event_data = { + .event = USBH_EVENT_NEW_DEV, + .new_dev_data = { + .dev_addr = dev_obj->constant.address, + }, + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } // ------------------------------------------------- USBH Functions ---------------------------------------------------- From 78515b3fef541da023dd84fc7b65b076c6d026b3 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Fri, 23 Feb 2024 04:44:01 +0800 Subject: [PATCH 02/14] refactor(usb): Remove USBH control transfer callback This commit merges the USBH control transfer callback into the USBH event callback. This simplifies the code as the USBH now uses a single callback. --- components/usb/private_include/usbh.h | 13 +++++-------- components/usb/usb_host.c | 28 +++++++++++++-------------- components/usb/usbh.c | 13 ++++++++----- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index c14eee8658..1d683809bb 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -33,6 +33,7 @@ typedef struct usbh_ep_handle_s *usbh_ep_handle_t; * @brief Enumerator for various USBH events */ typedef enum { + USBH_EVENT_CTRL_XFER, /**< A control transfer has completed */ USBH_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the device pool */ USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ USBH_EVENT_ALL_FREE, /**< All devices have been freed */ @@ -44,6 +45,10 @@ typedef enum { typedef struct { usbh_event_t event; union { + struct { + usb_device_handle_t dev_hdl; + urb_t *urb; + } ctrl_xfer_data; struct { uint8_t dev_addr; } new_dev_data; @@ -118,12 +123,6 @@ typedef enum { // ---------------------- Callbacks ------------------------ -/** - * @brief Callback used to indicate completion of control transfers submitted usbh_dev_submit_ctrl_urb() - * @note This callback is called from within usbh_process() - */ -typedef void (*usbh_ctrl_xfer_cb_t)(usb_device_handle_t dev_hdl, urb_t *urb, void *arg); - /** * @brief Callback used to indicate that the USBH has an event * @@ -166,8 +165,6 @@ typedef struct { typedef struct { usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ void *proc_req_cb_arg; /**< Processing request callback argument */ - usbh_ctrl_xfer_cb_t ctrl_xfer_cb; /**< Control transfer callback */ - void *ctrl_xfer_cb_arg; /**< Control transfer callback argument */ usbh_event_cb_t event_cb; /**< USBH event callback */ void *event_cb_arg; /**< USBH event callback argument */ } usbh_config_t; diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 080fd8a36d..79a1952404 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -262,22 +262,22 @@ static bool proc_req_callback(usb_proc_req_source_t source, bool in_isr, void *a return yield; } -static void ctrl_xfer_callback(usb_device_handle_t dev_hdl, urb_t *urb, void *arg) -{ - assert(urb->usb_host_client != NULL); - // Redistribute done control transfer to the clients that submitted them - client_t *client_obj = (client_t *)urb->usb_host_client; - - HOST_ENTER_CRITICAL(); - TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, urb, tailq_entry); - client_obj->dynamic.num_done_ctrl_xfer++; - _unblock_client(client_obj, false); - HOST_EXIT_CRITICAL(); -} - static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) { switch (event_data->event) { + case USBH_EVENT_CTRL_XFER: { + assert(event_data->ctrl_xfer_data.urb != NULL); + assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL); + // Redistribute done control transfer to the clients that submitted them + client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; + + HOST_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry); + client_obj->dynamic.num_done_ctrl_xfer++; + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); + break; + } case USBH_EVENT_NEW_DEV: { // Prepare a NEW_DEV client event message, the send it to all clients usb_host_client_event_msg_t event_msg = { @@ -394,8 +394,6 @@ esp_err_t usb_host_install(const usb_host_config_t *config) usbh_config_t usbh_config = { .proc_req_cb = proc_req_callback, .proc_req_cb_arg = NULL, - .ctrl_xfer_cb = ctrl_xfer_callback, - .ctrl_xfer_cb_arg = NULL, .event_cb = usbh_event_callback, .event_cb_arg = NULL, }; diff --git a/components/usb/usbh.c b/components/usb/usbh.c index b4297bbd88..456b3dc4e5 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -110,8 +110,6 @@ typedef struct { void *hub_req_cb_arg; usbh_event_cb_t event_cb; void *event_cb_arg; - usbh_ctrl_xfer_cb_t ctrl_xfer_cb; - void *ctrl_xfer_cb_arg; SemaphoreHandle_t mux_lock; } constant; } usbh_t; @@ -482,7 +480,14 @@ static inline void handle_ep0_dequeue(device_t *dev_obj) urb_t *urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); while (urb != NULL) { num_urbs++; - p_usbh_obj->constant.ctrl_xfer_cb((usb_device_handle_t)dev_obj, urb, p_usbh_obj->constant.ctrl_xfer_cb_arg); + usbh_event_data_t event_data = { + .event = USBH_EVENT_CTRL_XFER, + .ctrl_xfer_data = { + .dev_hdl = (usb_device_handle_t)dev_obj, + .urb = urb, + }, + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); urb = hcd_urb_dequeue(dev_obj->constant.default_pipe); } USBH_ENTER_CRITICAL(); @@ -589,8 +594,6 @@ esp_err_t usbh_install(const usbh_config_t *usbh_config) usbh_obj->constant.proc_req_cb_arg = usbh_config->proc_req_cb_arg; usbh_obj->constant.event_cb = usbh_config->event_cb; usbh_obj->constant.event_cb_arg = usbh_config->event_cb_arg; - usbh_obj->constant.ctrl_xfer_cb = usbh_config->ctrl_xfer_cb; - usbh_obj->constant.ctrl_xfer_cb_arg = usbh_config->ctrl_xfer_cb_arg; usbh_obj->constant.mux_lock = mux_lock; // Assign USBH object pointer From 01761f4c99d1d14198a7af77643d622ce4e0bbbb Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 29 Feb 2024 11:26:08 +0100 Subject: [PATCH 03/14] refactor(usb_host): Added chapter11 header, refactor chapter9 header --- components/usb/include/usb/usb_helpers.h | 1 + components/usb/include/usb/usb_types_ch11.h | 221 ++++++++++++++++++++ components/usb/include/usb/usb_types_ch9.h | 2 +- 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 components/usb/include/usb/usb_types_ch11.h diff --git a/components/usb/include/usb/usb_helpers.h b/components/usb/include/usb/usb_helpers.h index 955d2fa96d..e64bc6de3c 100644 --- a/components/usb/include/usb/usb_helpers.h +++ b/components/usb/include/usb/usb_helpers.h @@ -14,6 +14,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to #include "esp_err.h" #include "usb/usb_types_stack.h" #include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" #ifdef __cplusplus extern "C" { diff --git a/components/usb/include/usb/usb_types_ch11.h b/components/usb/include/usb/usb_types_ch11.h new file mode 100644 index 0000000000..6d13998cc5 --- /dev/null +++ b/components/usb/include/usb/usb_types_ch11.h @@ -0,0 +1,221 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USB Hub request types + */ + +#define USB_BM_REQUEST_TYPE_HUB (USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_DEVICE) +#define USB_BM_REQUEST_TYPE_PORT (USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_OTHER) + +/** + * @brief USB Hub descriptor type + */ +#define USB_CLASS_DESCRIPTOR_TYPE_HUB 0x29 + +/** + * @brief USB Hub Class bRequest codes + * + * See USB 2.0 spec Table 11-16 + */ +typedef enum { + USB_B_REQUEST_HUB_GET_PORT_STATUS = 0x00, /**< Get port status. */ + USB_B_REQUEST_HUB_CLEAR_FEATURE = 0x01, /**< Clearing a feature disables that feature or starts a process associated with the feature. */ + USB_B_REQUEST_HUB_GET_STATE = 0x02, /**< Outdated. Used in previous specifications for GET_STATE */ + USB_B_REQUEST_HUB_SET_PORT_FEATURE = 0x03, /**< Set port feature. */ + USB_B_REQUEST_HUB_GET_DESCRIPTOR = 0x06, /**< Get HUB descriptor. */ + USB_B_REQUEST_HUB_SET_DESCRIPTOR = 0x07, /**< Set HUB descriptor. */ + USB_B_REQUEST_HUB_CLEAR_TT_BUFFER = 0x08, /**< This request clears the state of a Transaction Translator(TT) bulk/control buffer after it has been left in a busy state due to high-speed errors. */ + USB_B_REQUEST_HUB_RESET_TT = 0x09, /**< Reset TT. */ + USB_B_REQUEST_HUB_GET_TT_STATE = 0x0A, /**< Get TT state. */ + USB_B_REQUEST_HUB_STOP_TT = 0x0B, /**< Stop TT. */ +} usb_hub_class_request_t ; + +/** + * @brief USB Hub Port feature selector codes + * + * See USB 2.0 spec Table 11-17 + */ +typedef enum { + USB_FEATURE_PORT_CONNECTION = 0x00, + USB_FEATURE_PORT_ENABLE = 0x01, + USB_FEATURE_PORT_SUSPEND = 0x02, + USB_FEATURE_PORT_OVER_CURRENT = 0x03, + USB_FEATURE_PORT_RESET = 0x04, + USB_FEATURE_PORT_POWER = 0x08, + USB_FEATURE_PORT_LOWSPEED = 0x09, + USB_FEATURE_C_PORT_CONNECTION = 0x10, + USB_FEATURE_C_PORT_ENABLE = 0x11, + USB_FEATURE_C_PORT_SUSPEND = 0x12, + USB_FEATURE_C_PORT_OVER_CURRENT = 0x13, + USB_FEATURE_C_PORT_RESET = 0x14, + USB_FEATURE_PORT_TEST = 0x15, + USB_FEATURE_PORT_INDICATOR = 0x16, +} usb_hub_port_feature_t; + +/** + * @brief Size of a USB Hub Port Status and Hub Change results + */ +#define USB_PORT_STATUS_SIZE 4 + +/** + * @brief USB Hub Port Status and Hub Change results + * + * See USB 2.0 spec Table 11-19 and Table 11-20 + */ +typedef struct { + union { + struct { + uint8_t PORT_CONNECTION : 1; /**< 0 = No device is present. 1 = A device is present on this port.*/ + uint8_t PORT_ENABLE : 1; /**< 0 = Port is disabled. 1 = Port is enabled.*/ + uint8_t PORT_SUSPEND : 1; /**< 0 = Not suspended. 1 = Suspended or resuming. */ + uint8_t PORT_OVER_CURRENT : 1; /**< 0 = All no over-current condition exists on this port. 1 = An over-current condition exists on this port. */ + uint8_t PORT_RESET : 1; /**< 0 = Reset signaling not asserted. 1 = Reset signaling asserted. */ + uint8_t RESERVED_1 : 3; /**< Reserved field */ + uint8_t PORT_POWER : 1; /**< 0 = This port is in the Powered-off state. 1 = This port is not in the Powered-off state. */ + uint8_t PORT_LOW_SPEED : 1; /**< 0 = Full-speed or High-speed device attached to this port (determined by bit 10). 1 = Low-speed device attached to this port.*/ + uint8_t PORT_HIGH_SPEED : 1; /**< 0 = Full-speed device attached to this port. 1 = High-speed device attached to this port. */ + uint8_t PORT_TEST : 1; /**< 0 = This port is not in the Port Test Mode. 1 = This port is in Port Test Mode. */ + uint8_t PORT_INDICATOR : 1; /**< 0 = Port indicator displays default colors. 1 = Port indicator displays software controlled color. */ + uint8_t RESERVED_2 : 3; /**< Reserved field */ + }; + uint16_t val; /**< Port status value */ + } wPortStatus; + + union { + struct { + uint8_t C_PORT_CONNECTION : 1; /**< 0 = No change has occurred to Current Connect status. 1 = Current Connect status has changed. */ + uint8_t C_PORT_ENABLE : 1; /**< This field is set to one when a port is disabled because of a Port_Error condition */ + uint8_t C_PORT_SUSPEND : 1; /**< 0 = No change. 1 = Resume complete. */ + uint8_t C_PORT_OVER_CURRENT : 1; /**< 0 = No change has occurred to Over-Current Indicator. 1 = Over-Current Indicator has changed. */ + uint8_t C_PORT_RESET : 1; /**< This field is set when reset processing on this port is complete. 0 = No change. 1 = Reset complete.*/ + uint16_t RESERVED : 11; /**< Reserved field */ + }; + uint16_t val; /**< Port change value */ + } wPortChange; +} __attribute__((packed)) usb_port_status_t; +ESP_STATIC_ASSERT(sizeof(usb_port_status_t) == USB_PORT_STATUS_SIZE, "Size of usb_port_status_t incorrect"); + +/** + * @brief Size of a USB Hub Status + */ +#define USB_HUB_STATUS_SIZE 4 + +/** + * @brief USB Hub Status + */ +typedef struct { + union { + struct { + uint8_t HUB_LOCAL_POWER : 1; /**< 0 = Local power supply good. 1 = Local power supply lost (inactive)*/ + uint8_t HUB_OVER_CURRENT : 1; /**< 0 = No over-current condition currently exists. 1 = A hub over-current condition exists.*/ + uint16_t RESERVED : 14; /**< Reserved fields */ + }; + uint16_t val; /**< Hub status value */ + } wHubStatus; + union { + struct { + uint8_t C_HUB_LOCAL_POWER : 1; /**< 0 = No change has occurred to Local Power Status. 1 = Local Power Status has changed.*/ + uint8_t C_HUB_OVER_CURRENT : 1; /**< 0 = No change has occurred to the Over-Current Status. 1 = Over-Current Status has changed.*/ + uint16_t RESERVED : 14; /**< Reserved fields */ + }; + uint16_t val; /**< Hub change value */ + } wHubChange; +} __attribute__((packed)) usb_hub_status_t; +ESP_STATIC_ASSERT(sizeof(usb_hub_status_t) == USB_HUB_STATUS_SIZE, "Size of usb_hub_status_t incorrect"); + +/** + * @brief Size of a USB Hub Device descriptor + */ +#define USB_HUB_DESCRIPTOR_SIZE (7) + +/** + * @brief USB Hub Device descriptor + */ +typedef struct { + uint8_t bDescLength; /**< Number of bytes in this descriptor, including this byte */ + uint8_t bDescriptorType; /**< Descriptor Type, value: 29H for Hub descriptor */ + uint8_t bNbrPorts; /**< Number of downstream facing ports that this Hub supports */ + uint16_t wHubCharacteristics; /**< Logical Power Switching Mode, Compound Device, Over-current Protection Mode, TT Think Time, Port Indicators Supported */ + uint8_t bPwrOn2PwrGood; /**< Time (in 2 ms intervals) from the time the power-on sequence begins on a port until power is good on that port */ + uint8_t bHubContrCurrent; /**< Maximum current requirements of the Hub Controller electronics in mA. */ +} __attribute__((packed)) usb_hub_descriptor_t; +ESP_STATIC_ASSERT(sizeof(usb_hub_descriptor_t) == USB_HUB_DESCRIPTOR_SIZE, "Size of usb_hub_descriptor_t incorrect"); + +/** + * @brief Initializer for a request to get HUB descriptor + */ +#define USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_HUB; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_HUB_GET_DESCRIPTOR; \ + (setup_pkt_ptr)->wValue = (USB_CLASS_DESCRIPTOR_TYPE_HUB << 8); \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = sizeof(usb_hub_descriptor_t); \ +}) + +/** + * @brief Initializer for a request to get HUB status + */ +#define USB_SETUP_PACKET_INIT_GET_HUB_STATUS(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_HUB; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_HUB_GET_PORT_STATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = sizeof(usb_hub_status_t); \ +}) + +/** + * @brief Initializer for a request to get port status + */ +#define USB_SETUP_PACKET_INIT_GET_PORT_STATUS(setup_pkt_ptr, port) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_PORT; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_HUB_GET_PORT_STATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = (port); \ + (setup_pkt_ptr)->wLength = sizeof(usb_port_status_t); \ +}) + +/** + * @brief Initializer for a set port feature + */ +#define USB_SETUP_PACKET_INIT_SET_PORT_FEATURE(setup_pkt_ptr, port, feature) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_PORT; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_HUB_SET_PORT_FEATURE; \ + (setup_pkt_ptr)->wValue = (feature); \ + (setup_pkt_ptr)->wIndex = (port); \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +/** + * @brief Initializer for a clear port feature + */ +#define USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE(setup_pkt_ptr, port, feature) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_PORT; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_HUB_CLEAR_FEATURE; \ + (setup_pkt_ptr)->wValue = (feature); \ + (setup_pkt_ptr)->wIndex = (port); \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +/** + * @brief Get Port Number from a setup packet + */ +#define USB_SETUP_PACKET_GET_PORT(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->wIndex; \ +}) + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index 8054d91f82..ff9feff80a 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/components/usb/include/usb/usb_types_ch9.h @@ -96,7 +96,7 @@ typedef union { uint16_t wValue; /**< Word-sized field that varies according to request */ uint16_t wIndex; /**< Word-sized field that varies according to request; typically used to pass an index or offset */ uint16_t wLength; /**< Number of bytes to transfer if there is a data stage */ - } __attribute__((packed)); + } USB_DESC_ATTR; /**< USB descriptor attributes */ uint8_t val[USB_SETUP_PACKET_SIZE]; /**< Descriptor value */ } usb_setup_packet_t; ESP_STATIC_ASSERT(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb_setup_packet_t incorrect"); From 54d984644a0221be9d28d8b6a67fd2c31fc01225 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 29 Feb 2024 12:40:58 +0100 Subject: [PATCH 04/14] feat(usb_host): Added KConfig parameter for External HUB support enable --- components/usb/Kconfig | 8 +++++++- components/usb/include/usb/usb_helpers.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/components/usb/Kconfig b/components/usb/Kconfig index e6a426924b..8c170d17fd 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -93,7 +93,6 @@ menu "USB-OTG" The default value is set to 10 ms to be safe. - endmenu #Root Hub configuration # Hidden or compatibility options @@ -115,4 +114,11 @@ menu "USB-OTG" If enabled, the enumeration filter callback can be set via 'usb_host_config_t' when calling 'usb_host_install()'. + config USB_HOST_EXT_HUB_SUPPORT + depends on IDF_EXPERIMENTAL_FEATURES + bool "Support USB HUB (Experimental)" + default n + help + Feature is under development. + endmenu #USB-OTG diff --git a/components/usb/include/usb/usb_helpers.h b/components/usb/include/usb/usb_helpers.h index e64bc6de3c..faf430de39 100644 --- a/components/usb/include/usb/usb_helpers.h +++ b/components/usb/include/usb/usb_helpers.h @@ -12,9 +12,12 @@ Warning: The USB Host Library API is still a beta version and may be subject to #include #include "esp_err.h" +#include "sdkconfig.h" #include "usb/usb_types_stack.h" #include "usb/usb_types_ch9.h" +#if (CONFIG_USB_HOST_EXT_HUB_SUPPORT) #include "usb/usb_types_ch11.h" +#endif // CONFIG_USB_HOST_EXT_HUB_SUPPORT #ifdef __cplusplus extern "C" { From 4270a4edce9ae90afe2bc91fd8214c7c992f3f8e Mon Sep 17 00:00:00 2001 From: Tomas Rezucha Date: Mon, 26 Feb 2024 10:44:35 +0100 Subject: [PATCH 05/14] feat(usb/host): Add missing sync types from USB specification --- components/usb/include/usb/usb_types_ch9.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index ff9feff80a..c09440eb00 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/components/usb/include/usb/usb_types_ch9.h @@ -467,6 +467,8 @@ ESP_STATIC_ASSERT(sizeof(usb_ep_desc_t) == USB_EP_DESC_SIZE, "Size of usb_ep_des #define USB_EP_DESC_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0) #define USB_EP_DESC_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & USB_W_MAX_PACKET_SIZE_MPS_MASK) #define USB_EP_DESC_GET_MULT(desc_ptr) (((desc_ptr)->wMaxPacketSize & USB_W_MAX_PACKET_SIZE_MULT_MASK) >> 11) +#define USB_EP_DESC_GET_SYNCTYPE(desc_ptr) (((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_SYNCTYPE_MASK) >> 2) +#define USB_EP_DESC_GET_USAGETYPE(desc_ptr) (((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_USAGETYPE_MASK) >> 4) // ------------------ String Descriptor -------------------- From cfa48efc6a0f7467f5a0523e3ac7cd0e80aaf292 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 21 Mar 2024 13:06:58 +0100 Subject: [PATCH 06/14] refactor(usb_host): Renamed hub_driver_state to root_port_state --- components/usb/hub.c | 53 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/components/usb/hub.c b/components/usb/hub.c index 9936d07abf..29d64698ae 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -55,18 +55,17 @@ implement the bare minimum to control the root HCD port. #define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x08 /** - * @brief Hub driver states + * @brief Root port states * - * These states represent a Hub driver that only has a single port (the root port) */ typedef enum { - HUB_DRIVER_STATE_INSTALLED, /**< Hub driver is installed. Root port is not powered */ - HUB_DRIVER_STATE_ROOT_POWERED, /**< Root port is powered, is not connected */ - HUB_DRIVER_STATE_ROOT_ENUM, /**< A device has connected to the root port and is undergoing enumeration */ - HUB_DRIVER_STATE_ROOT_ENUM_FAILED, /**< Enumeration of a connect device has failed. Waiting for that device to disconnect */ - HUB_DRIVER_STATE_ROOT_ACTIVE, /**< The connected device is enumerated */ - HUB_DRIVER_STATE_ROOT_RECOVERY, /**< Root port encountered an error and needs to be recovered */ -} hub_driver_state_t; + ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */ + ROOT_PORT_STATE_POWERED, /**< Root port is powered, device is not connected */ + ROOT_PORT_STATE_ENUM, /**< A device has been connected to the root port and is undergoing enumeration */ + ROOT_PORT_STATE_ENUM_FAILED, /**< Enumeration of a connected device has failed. Waiting for that device to be disconnected */ + ROOT_PORT_ACTIVE, /**< The connected device was enumerated and port is active */ + ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */ +} root_port_state_t; /** * @brief Stages of device enumeration listed in their order of execution @@ -186,7 +185,7 @@ typedef struct { }; uint32_t val; } flags; - hub_driver_state_t driver_state; + root_port_state_t root_port_state; } dynamic; // Single thread members don't require a critical section so long as they are never accessed from multiple threads struct { @@ -666,7 +665,7 @@ static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) { // We currently only support a single device connected to the root port. Move the device handle from enum to root HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ACTIVE; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_ACTIVE; HUB_DRIVER_EXIT_CRITICAL(); p_hub_driver_obj->single_thread.root_dev_hdl = enum_ctrl->dev_hdl; usb_device_handle_t dev_hdl = enum_ctrl->dev_hdl; @@ -693,11 +692,11 @@ static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) enum_ctrl->pipe = NULL; HUB_DRIVER_ENTER_CRITICAL(); // Enum could have failed due to a port error. If so, we need to trigger a port recovery - if (p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_ROOT_RECOVERY) { + if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) { p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; } else { // Otherwise, we move to the enum failed state and wait for the device to disconnect - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUM_FAILED; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED; } HUB_DRIVER_EXIT_CRITICAL(); } @@ -860,7 +859,7 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) // Start enumeration HUB_DRIVER_ENTER_CRITICAL(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUM; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM; HUB_DRIVER_EXIT_CRITICAL(); p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; } else { @@ -873,18 +872,18 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) case HCD_PORT_EVENT_OVERCURRENT: { bool pass_event_to_usbh = false; HUB_DRIVER_ENTER_CRITICAL(); - switch (p_hub_driver_obj->dynamic.driver_state) { - case HUB_DRIVER_STATE_ROOT_POWERED: // This occurred before enumeration - case HUB_DRIVER_STATE_ROOT_ENUM_FAILED: // This occurred after a failed enumeration. + switch (p_hub_driver_obj->dynamic.root_port_state) { + case ROOT_PORT_STATE_POWERED: // This occurred before enumeration + case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration. // Therefore, there's no device and we can go straight to port recovery p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; break; - case HUB_DRIVER_STATE_ROOT_ENUM: + case ROOT_PORT_STATE_ENUM: // This occurred during enumeration. Therefore, we need to recover the failed enumeration p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED; break; - case HUB_DRIVER_STATE_ROOT_ACTIVE: + case ROOT_PORT_ACTIVE: // There was an enumerated device. We need to indicate to USBH that the device is gone pass_event_to_usbh = true; break; @@ -892,7 +891,7 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) abort(); // Should never occur break; } - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_RECOVERY; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); if (pass_event_to_usbh) { assert(p_hub_driver_obj->single_thread.root_dev_hdl); @@ -1011,7 +1010,6 @@ esp_err_t hub_install(hub_config_t *hub_config) goto err; } // Initialize Hub driver object - hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED; hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; #ifdef ENABLE_ENUM_FILTER_CALLBACK @@ -1022,6 +1020,7 @@ esp_err_t hub_install(hub_config_t *hub_config) hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; HUB_DRIVER_ENTER_CRITICAL(); + hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; if (p_hub_driver_obj != NULL) { HUB_DRIVER_EXIT_CRITICAL(); ret = ESP_ERR_INVALID_STATE; @@ -1046,7 +1045,7 @@ esp_err_t hub_uninstall(void) { HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); - HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE); hub_driver_t *hub_driver_obj = p_hub_driver_obj; p_hub_driver_obj = NULL; HUB_DRIVER_EXIT_CRITICAL(); @@ -1062,14 +1061,14 @@ esp_err_t hub_root_start(void) { HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); - HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE); HUB_DRIVER_EXIT_CRITICAL(); // Power ON the root port esp_err_t ret; ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON); if (ret == ESP_OK) { HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERED; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; HUB_DRIVER_EXIT_CRITICAL(); } return ret; @@ -1079,13 +1078,13 @@ esp_err_t hub_root_stop(void) { HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); - HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state != HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state != ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE); HUB_DRIVER_EXIT_CRITICAL(); esp_err_t ret; ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_OFF); if (ret == ESP_OK) { HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; HUB_DRIVER_EXIT_CRITICAL(); } return ret; @@ -1120,7 +1119,7 @@ esp_err_t hub_process(void) ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERED; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; HUB_DRIVER_EXIT_CRITICAL(); // USBH requesting a port recovery means the device has already been freed. Clear root_dev_hdl p_hub_driver_obj->single_thread.root_dev_hdl = NULL; From 0b77a7289c5386ecae09e438e4b141e7ab2f138d Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Thu, 21 Mar 2024 20:57:32 +0800 Subject: [PATCH 07/14] refactor(usb/host): Simplify USBH and Hub interaction Previously, on a device disconnection, the USBH and Hub would the require the following 2-way interaction: - Hub -> usbh_hub_pass_event() -> USBH to indicate a port error - USBH -> usbh_hub_req_cb_t -> Hub to request port recovery after the device has been freed. The 2-way interaction has been simplified: - USBH now nofities upper layers of devices being freed via the USBH_EVENT_DEV_FREE event - Hub now handles port recovery only after a device has been freed --- components/usb/hub.c | 114 ++++++++----------- components/usb/private_include/hub.h | 11 ++ components/usb/private_include/usbh.h | 65 +---------- components/usb/usb_host.c | 5 + components/usb/usbh.c | 152 +++++++++----------------- 5 files changed, 117 insertions(+), 230 deletions(-) diff --git a/components/usb/hub.c b/components/usb/hub.c index 29d64698ae..e5d5e80582 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -50,9 +50,8 @@ implement the bare minimum to control the root HCD port. // Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive #define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 -#define HUB_DRIVER_FLAG_ACTION_PORT_DISABLE 0x02 -#define HUB_DRIVER_FLAG_ACTION_PORT_RECOVER 0x04 -#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x08 +#define HUB_DRIVER_FLAG_ACTION_PORT 0x02 +#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04 /** * @brief Root port states @@ -255,18 +254,6 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port */ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); -/** - * @brief USBH Hub driver request callback - * - * - This callback is called from the context of the USBH, so so any event handling should be deferred to hub_process() - * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run - * - * @param port_hdl HCD port handle - * @param hub_req Hub driver request - * @param arg Callback argument - */ -static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); - // ------------------------------------------------- Enum Functions ---------------------------------------------------- static bool enum_stage_start(enum_ctrl_t *enum_ctrl) @@ -693,7 +680,7 @@ static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) HUB_DRIVER_ENTER_CRITICAL(); // Enum could have failed due to a port error. If so, we need to trigger a port recovery if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) { - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; } else { // Otherwise, we move to the enum failed state and wait for the device to disconnect p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED; @@ -809,7 +796,7 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); assert(in_isr); // Currently, this callback should only ever be called from an ISR context - return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);; + return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) @@ -821,29 +808,6 @@ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } -static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg) -{ - // We currently only support the root port, so the port_hdl should match the root port - assert(port_hdl == p_hub_driver_obj->constant.root_port_hdl); - - HUB_DRIVER_ENTER_CRITICAL(); - switch (hub_req) { - case USBH_HUB_REQ_PORT_DISABLE: - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_DISABLE; - break; - case USBH_HUB_REQ_PORT_RECOVER: - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; - break; - default: - // Should never occur - abort(); - break; - } - HUB_DRIVER_EXIT_CRITICAL(); - - p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); -} - // ---------------------- Handlers ------------------------- static void root_port_handle_events(hcd_port_handle_t root_port_hdl) @@ -876,10 +840,10 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) case ROOT_PORT_STATE_POWERED: // This occurred before enumeration case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration. // Therefore, there's no device and we can go straight to port recovery - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; break; case ROOT_PORT_STATE_ENUM: - // This occurred during enumeration. Therefore, we need to recover the failed enumeration + // This occurred during enumeration. Therefore, we need to cleanup the failed enumeration p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED; break; @@ -894,8 +858,7 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); if (pass_event_to_usbh) { - assert(p_hub_driver_obj->single_thread.root_dev_hdl); - ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_ERROR)); + ESP_ERROR_CHECK(usbh_hub_dev_gone(p_hub_driver_obj->single_thread.root_dev_hdl)); } break; } @@ -1028,9 +991,8 @@ esp_err_t hub_install(hub_config_t *hub_config) } p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); - // Indicate to USBH that the hub is installed - ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_req_callback, NULL)); ret = ESP_OK; + return ret; assign_err: @@ -1090,6 +1052,20 @@ esp_err_t hub_root_stop(void) return ret; } +esp_err_t hub_dev_is_free(uint8_t dev_addr) +{ + assert(dev_addr == ENUM_DEV_ADDR); + assert(p_hub_driver_obj->single_thread.root_dev_hdl); + p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + // Device is free, we can now request its port be recycled + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; + HUB_DRIVER_EXIT_CRITICAL(); + + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); + return ESP_OK; +} + esp_err_t hub_process(void) { HUB_DRIVER_ENTER_CRITICAL(); @@ -1098,33 +1074,33 @@ esp_err_t hub_process(void) HUB_DRIVER_EXIT_CRITICAL(); while (action_flags) { - /* - Mutually exclude Root event and Port disable: - If a device was waiting for its port to be disabled, and a port error occurs in that time, the root event - handler will send a USBH_HUB_EVENT_PORT_ERROR to the USBH already, thus freeing the device and canceling the - waiting of port disable. - */ if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); - } else if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_DISABLE) { - ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); - hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); - ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_DISABLED)); - // The root port has been disabled, so the root_dev_hdl is no longer valid - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; } - - if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_RECOVER) { - ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); - ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); - ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; - HUB_DRIVER_EXIT_CRITICAL(); - // USBH requesting a port recovery means the device has already been freed. Clear root_dev_hdl - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT) { + // Check current state of port + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + switch (port_state) { + case HCD_PORT_STATE_ENABLED: + // Port is still enabled with a connect device. Disable it. + ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); + // We allow this to fail in case a disconnect/port error happens while disabling. + hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); + break; + case HCD_PORT_STATE_RECOVERY: + // Port is in recovery after a disconnect/error. Recover it. + ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); + ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); + ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; + HUB_DRIVER_EXIT_CRITICAL(); + break; + default: + abort(); // Should never occur + break; + } } - if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { enum_handle_events(); } diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index dac89b7ea7..a096de8fd9 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -77,6 +77,17 @@ esp_err_t hub_root_start(void); */ esp_err_t hub_root_stop(void); +/** + * @brief Indicate to the Hub driver that a device has been freed + * + * Hub driver can now recover the port that the device was connected to + * + * @param dev_addr Device address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_dev_is_free(uint8_t dev_addr); + /** * @brief Hub driver's processing function * diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 1d683809bb..75be2b1f66 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -36,6 +36,7 @@ typedef enum { USBH_EVENT_CTRL_XFER, /**< A control transfer has completed */ USBH_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the device pool */ USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ + USBH_EVENT_DEV_FREE, /**< A device has been freed. Its upstream port can now be recycled */ USBH_EVENT_ALL_FREE, /**< All devices have been freed */ } usbh_event_t; @@ -56,6 +57,9 @@ typedef struct { uint8_t dev_addr; usb_device_handle_t dev_hdl; } dev_gone_data; + struct { + uint8_t dev_addr; + } dev_free_data; }; } usbh_event_data_t; @@ -73,43 +77,8 @@ typedef enum { USBH_EP_EVENT_ERROR_STALL, /**< EP received a STALL response */ } usbh_ep_event_t; -/** - * @brief Hub driver events for the USBH - * - * These events as passed by the Hub driver to the USBH via usbh_hub_pass_event() - * - * USBH_HUB_EVENT_PORT_ERROR: - * - The port has encountered an error (such as a sudden disconnection). The device connected to that port is no longer valid. - * - The USBH should: - * - Trigger a USBH_EVENT_DEV_GONE - * - Prevent further transfers to the device - * - Trigger the device's cleanup if it is already closed - * - When the last client closes the device via usbh_dev_close(), free the device object and issue a USBH_HUB_REQ_PORT_RECOVER request - * - * USBH_HUB_EVENT_PORT_DISABLED: - * - A previous USBH_HUB_REQ_PORT_DISABLE has completed. - * - The USBH should free the device object - */ -typedef enum { - USBH_HUB_EVENT_PORT_ERROR, /**< The port has encountered an error (such as a sudden disconnection). The device - connected to that port should be marked gone. */ - USBH_HUB_EVENT_PORT_DISABLED, /**< Previous USBH_HUB_REQ_PORT_DISABLE request completed */ -} usbh_hub_event_t; - // ------------------ Requests/Commands -------------------- -/** - * @brief Hub driver requests - * - * Various requests of the Hub driver that the USBH can make. - */ -typedef enum { - USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device - has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ - USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone - device has been freed). */ -} usbh_hub_req_t; - /** * @brief Endpoint commands * @@ -130,14 +99,6 @@ typedef enum { */ typedef void (*usbh_event_cb_t)(usbh_event_data_t *event_data, void *arg); -/** - * @brief Callback used by the USBH to request actions from the Hub driver - * - * The Hub Request Callback allows the USBH to request the Hub actions on a particular port. Conversely, the Hub driver - * will indicate completion of some of these requests to the USBH via the usbh_hub_event() funtion. - */ -typedef void (*usbh_hub_req_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); - /** * @brief Callback used to indicate an event on an endpoint * @@ -422,19 +383,6 @@ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); // ------------------- Device Related ---------------------- -/** - * @brief Indicates to USBH that the Hub driver is installed - * - * - The Hub driver must call this function in its installation to indicate the the USBH that it has been installed. - * - This should only be called after the USBH has already be installed - * - * @note Hub Driver only - * @param[in] hub_req_callback Hub request callback - * @param[in] callback_arg Callback argument - * @return esp_err_t - */ -esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg); - /** * @brief Indicates to USBH the start of enumeration for a device * @@ -453,13 +401,12 @@ esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callba esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); /** - * @brief Indicates to the USBH that a hub event has occurred for a particular device + * @brief Indicates to the USBH that a device is gone * * @param dev_hdl Device handle - * @param hub_event Hub event * @return esp_err_t */ -esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event); +esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl); // ----------------- Enumeration Related ------------------- diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 79a1952404..11fd7810aa 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -296,6 +296,11 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) send_event_msg_to_clients(&event_msg, false, event_data->dev_gone_data.dev_addr); break; } + case USBH_EVENT_DEV_FREE: { + // Let the Hub driver know that the device is free + ESP_ERROR_CHECK(hub_dev_is_free(event_data->dev_free_data.dev_addr)); + break; + } case USBH_EVENT_ALL_FREE: { // Notify the lib handler that all devices are free HOST_ENTER_CRITICAL(); diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 456b3dc4e5..a50f4fc944 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -32,10 +32,8 @@ typedef enum { DEV_ACTION_EP0_DEQUEUE = (1 << 2), // Dequeue all URBs from EP0 DEV_ACTION_EP0_CLEAR = (1 << 3), // Move EP0 to the the active state DEV_ACTION_PROP_GONE_EVT = (1 << 4), // Propagate a USBH_EVENT_DEV_GONE event - DEV_ACTION_FREE_AND_RECOVER = (1 << 5), // Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. - DEV_ACTION_FREE = (1 << 6), // Free the device object - DEV_ACTION_PORT_DISABLE = (1 << 7), // Request the hub driver to disable the port of the device - DEV_ACTION_PROP_NEW = (1 << 8), // Propagate a USBH_EVENT_NEW_DEV event + DEV_ACTION_FREE = (1 << 5), // Free the device object + DEV_ACTION_PROP_NEW_DEV = (1 << 6), // Propagate a USBH_EVENT_NEW_DEV event } dev_action_t; typedef struct device_s device_t; @@ -57,11 +55,9 @@ struct device_s { union { struct { uint32_t in_pending_list: 1; - uint32_t is_gone: 1; - uint32_t waiting_close: 1; - uint32_t waiting_port_disable: 1; - uint32_t waiting_free: 1; - uint32_t reserved27: 27; + uint32_t is_gone: 1; // Device is gone (disconnected or port error) + uint32_t waiting_free: 1; // Device object is awaiting to be freed + uint32_t reserved29: 29; }; uint32_t val; } flags; @@ -106,8 +102,6 @@ typedef struct { struct { usb_proc_req_cb_t proc_req_cb; void *proc_req_cb_arg; - usbh_hub_req_cb_t hub_req_cb; - void *hub_req_cb_arg; usbh_event_cb_t event_cb; void *event_cb_arg; SemaphoreHandle_t mux_lock; @@ -515,11 +509,11 @@ static inline void handle_prop_gone_evt(device_t *dev_obj) p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } -static void handle_free_and_recover(device_t *dev_obj, bool recover_port) +static inline void handle_free(device_t *dev_obj) { - // Cache a copy of the port handle as we are about to free the device object + // Cache a copy of the device's address as we are about to free the device object + const uint8_t dev_addr = dev_obj->constant.address; bool all_free; - hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); // We need to take the mux_lock to access mux_protected members @@ -538,28 +532,24 @@ static void handle_free_and_recover(device_t *dev_obj, bool recover_port) xSemaphoreGive(p_usbh_obj->constant.mux_lock); device_free(dev_obj); + // Propagate USBH_EVENT_DEV_FREE event + usbh_event_data_t event_data = { + .event = USBH_EVENT_DEV_FREE, + .dev_free_data = { + .dev_addr = dev_addr, + } + }; + p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); + // If all devices have been freed, propagate a USBH_EVENT_ALL_FREE event if (all_free) { ESP_LOGD(USBH_TAG, "Device all free"); - usbh_event_data_t event_data = { - .event = USBH_EVENT_ALL_FREE, - }; + event_data.event = USBH_EVENT_ALL_FREE; p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } - // Check if we need to recover the device's port - if (recover_port) { - p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg); - } } -static inline void handle_port_disable(device_t *dev_obj) -{ - // Request that the HUB disables this device's port - ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); - p_usbh_obj->constant.hub_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg); -} - -static inline void handle_prop_new_evt(device_t *dev_obj) +static inline void handle_prop_new_dev(device_t *dev_obj) { ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); usbh_event_data_t event_data = { @@ -694,16 +684,11 @@ esp_err_t usbh_process(void) in the order of precedence For example - New device event is requested followed immediately by a disconnection - - Port disable requested followed immediately by a disconnection */ - if (action_flags & DEV_ACTION_FREE_AND_RECOVER) { - handle_free_and_recover(dev_obj, true); - } else if (action_flags & DEV_ACTION_FREE) { - handle_free_and_recover(dev_obj, false); - } else if (action_flags & DEV_ACTION_PORT_DISABLE) { - handle_port_disable(dev_obj); - } else if (action_flags & DEV_ACTION_PROP_NEW) { - handle_prop_new_evt(dev_obj); + if (action_flags & DEV_ACTION_FREE) { + handle_free(dev_obj); + } else if (action_flags & DEV_ACTION_PROP_NEW_DEV) { + handle_prop_new_dev(dev_obj); } USBH_ENTER_CRITICAL(); /* --------------------------------------------------------------------- @@ -782,7 +767,7 @@ esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) exit: if (found_dev_obj != NULL) { // The device is not in a state to be referenced - if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_port_disable || dev_obj->dynamic.flags.waiting_free) { + if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { ret = ESP_ERR_INVALID_STATE; } else { dev_obj->dynamic.ref_count++; @@ -809,14 +794,9 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) // Sanity check. assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); // There cannot be any control transfer in-flight assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when ref count reaches 0 - if (dev_obj->dynamic.flags.is_gone) { - // Device is already gone so it's port is already disabled. Trigger the USBH process to free the device - dev_obj->dynamic.flags.waiting_free = 1; - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it - } else if (dev_obj->dynamic.flags.waiting_close) { - // Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled - dev_obj->dynamic.flags.waiting_port_disable = 1; - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PORT_DISABLE); + if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { + // Device is already gone or is awaiting to be freed. Trigger the USBH process to free the device + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); } // Else, there's nothing to do. Leave the device allocated } @@ -848,18 +828,17 @@ esp_err_t usbh_dev_mark_all_free(void) dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq); } while (dev_obj_cur != NULL) { - assert(!dev_obj_cur->dynamic.flags.waiting_close); // Sanity check // Keep a copy of the next item first in case we remove the current item dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); - if (dev_obj_cur->dynamic.ref_count == 0 && !dev_obj_cur->dynamic.flags.is_gone) { - // Device is not opened as is not gone, so we can disable it now - dev_obj_cur->dynamic.flags.waiting_port_disable = 1; - call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_PORT_DISABLE); + if (dev_obj_cur->dynamic.ref_count == 0) { + // Device is not referenced. Can free immediately. + call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_FREE); } else { - // Device is still opened. Just mark it as waiting to be closed - dev_obj_cur->dynamic.flags.waiting_close = 1; + // Device is still referenced. Just mark it as waiting to be freed + dev_obj_cur->dynamic.flags.waiting_free = 1; } - wait_for_free = true; // As long as there is still a device, we need to wait for an event indicating it is freed + // At least one device needs to be freed. User needs to wait for USBH_EVENT_ALL_FREE event + wait_for_free = true; dev_obj_cur = dev_obj_next; } } @@ -1145,22 +1124,6 @@ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) // ------------------- Device Related ---------------------- -esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg) -{ - USBH_CHECK(hub_req_callback != NULL, ESP_ERR_INVALID_ARG); - - USBH_ENTER_CRITICAL(); - // Check that USBH is already installed - USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); - // Check that Hub has not be installed yet - USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_req_cb == NULL, ESP_ERR_INVALID_STATE); - p_usbh_obj->constant.hub_req_cb = hub_req_callback; - p_usbh_obj->constant.hub_req_cb_arg = callback_arg; - USBH_EXIT_CRITICAL(); - - return ESP_OK; -} - esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl) { // Note: Parent device handle can be NULL if it's connected to the root hub @@ -1178,42 +1141,27 @@ esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, us return ret; } -esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event) +esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - bool call_proc_req_cb; - switch (hub_event) { - case USBH_HUB_EVENT_PORT_ERROR: { - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.flags.is_gone = 1; - // Check if the device can be freed now - if (dev_obj->dynamic.ref_count == 0) { - dev_obj->dynamic.flags.waiting_free = 1; - // Device is already waiting free so none of it's EP's will be in use. Can free immediately. - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it - } else { - call_proc_req_cb = _dev_set_actions(dev_obj, - DEV_ACTION_EPn_HALT_FLUSH | - DEV_ACTION_EP0_FLUSH | - DEV_ACTION_EP0_DEQUEUE | - DEV_ACTION_PROP_GONE_EVT); - } - USBH_EXIT_CRITICAL(); - break; - } - case USBH_HUB_EVENT_PORT_DISABLED: { - USBH_ENTER_CRITICAL(); - assert(dev_obj->dynamic.ref_count == 0); // At this stage, the device should have been closed by all users - dev_obj->dynamic.flags.waiting_free = 1; + + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.flags.is_gone = 1; + // Check if the device can be freed immediately + if (dev_obj->dynamic.ref_count == 0) { + // Device is not currently referenced at all. Can free immediately. call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); - USBH_EXIT_CRITICAL(); - break; - } - default: - return ESP_ERR_INVALID_ARG; + } else { + // Device is still being referenced. Flush endpoints and propagate device gone event + call_proc_req_cb = _dev_set_actions(dev_obj, + DEV_ACTION_EPn_HALT_FLUSH | + DEV_ACTION_EP0_FLUSH | + DEV_ACTION_EP0_DEQUEUE | + DEV_ACTION_PROP_GONE_EVT); } + USBH_EXIT_CRITICAL(); if (call_proc_req_cb) { p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); @@ -1303,7 +1251,7 @@ esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; // Add the device to list of devices, then trigger a device event TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); // Add it to the idle device list first - bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW); + bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); USBH_EXIT_CRITICAL(); p_usbh_obj->mux_protected.num_device++; xSemaphoreGive(p_usbh_obj->constant.mux_lock); From 84793025030d6d88efe205faef14eb4dd5adb694 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 26 Mar 2024 11:34:29 +0100 Subject: [PATCH 08/14] refactor(hcd_dwc): Added mps request from hcd_dwc --- components/usb/hcd_dwc.c | 10 ++++++++++ components/usb/private_include/hcd.h | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index e51de377c7..53b1aef38f 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -1798,6 +1798,16 @@ err: return ret; } +int hcd_pipe_get_mps(hcd_pipe_handle_t pipe_hdl) +{ + pipe_t *pipe = (pipe_t *)pipe_hdl; + int mps; + HCD_ENTER_CRITICAL(); + mps = pipe->ep_char.mps; + HCD_EXIT_CRITICAL(); + return mps; +} + esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl) { pipe_t *pipe = (pipe_t *)pipe_hdl; diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index d2de4aa9f0..b640f0e8bd 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -361,6 +361,15 @@ esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_ */ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pipe_config, hcd_pipe_handle_t *pipe_hdl); +/** + * @brief Get maximum packet size (mps) of HCD pipe + * + * @param[in] port_hdl Pipe handle + * + * @retval HCD pipe mps + */ +int hcd_pipe_get_mps(hcd_pipe_handle_t pipe_hdl); + /** * @brief Free a pipe * From 5f0a659e73bc43edd9532b1ef8cbd87bdcd11404 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Fri, 29 Mar 2024 21:29:01 +0800 Subject: [PATCH 09/14] refactor(usb/host): Refactor USBH function grouping This commit rearranges the USBH functions into new groupings to provide a clearer abstraction. This is in preparation for refactoring/removing the Hub related functions in the USBH API. This commit DOES NOT MAKE ANY BEHAVIORAL CHANGES to the code. Functions are now grouped into... - USBH Processing: Functions dealing with overall USBH processing - Device Pool: Functions that add/remove/open/close devices from the internal device pool - Device: Functions that pertain to setting/getting a particular device - Endpoints: Functions that pertain to a particular endpoint - Transfer: Functions that pertain to sending transfers --- components/usb/private_include/usbh.h | 96 +++++++------ components/usb/usbh.c | 196 +++++++++++++------------- 2 files changed, 148 insertions(+), 144 deletions(-) diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 75be2b1f66..fe80799f87 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -130,7 +130,7 @@ typedef struct { void *event_cb_arg; /**< USBH event callback argument */ } usbh_config_t; -// ------------------------------------------------- USBH Functions ---------------------------------------------------- +// -------------------------------------------- USBH Processing Functions ---------------------------------------------- /** * @brief Installs the USBH driver @@ -169,6 +169,8 @@ esp_err_t usbh_uninstall(void); */ esp_err_t usbh_process(void); +// ---------------------------------------------- Device Pool Functions ------------------------------------------------ + /** * @brief Get the current number of devices * @@ -178,10 +180,6 @@ esp_err_t usbh_process(void); */ esp_err_t usbh_num_devs(int *num_devs_ret); -// ------------------------------------------------ Device Functions --------------------------------------------------- - -// --------------------- Device Pool ----------------------- - /** * @brief Fill list with address of currently connected devices * @@ -197,6 +195,17 @@ esp_err_t usbh_num_devs(int *num_devs_ret); */ esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); +/** + * @brief Mark that all devices should be freed at the next possible opportunity + * + * A device marked as free will not be freed until the last client using the device has called usbh_dev_close() + * + * @return + * - ESP_OK: There were no devices to free to begin with. Current state is all free + * - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed") + */ +esp_err_t usbh_dev_mark_all_free(void); + /** * @brief Open a device by address * @@ -218,18 +227,9 @@ esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); */ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl); -/** - * @brief Mark that all devices should be freed at the next possible opportunity - * - * A device marked as free will not be freed until the last client using the device has called usbh_dev_close() - * - * @return - * - ESP_OK: There were no devices to free to begin with. Current state is all free - * - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed") - */ -esp_err_t usbh_dev_mark_all_free(void); +// ------------------------------------------------ Device Functions --------------------------------------------------- -// ------------------- Single Device ---------------------- +// ----------------------- Getters ------------------------- /** * @brief Get a device's address @@ -275,15 +275,6 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t */ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); -/** - * @brief Submit a control transfer (URB) to a device - * - * @param[in] dev_hdl Device handle - * @param[in] urb URB - * @return esp_err_t - */ -esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb); - // ----------------------------------------------- Endpoint Functions ------------------------------------------------- /** @@ -334,6 +325,39 @@ esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl); */ esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret); +/** + * @brief Execute a command on a particular endpoint + * + * Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc) + * + * @param[in] ep_hdl Endpoint handle + * @param[in] command Endpoint command + * @return esp_err_t + */ +esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command); + +/** + * @brief Get the context of an endpoint + * + * Get the context variable assigned to and endpoint on allocation. + * + * @note This function can block + * @param[in] ep_hdl Endpoint handle + * @return Endpoint context + */ +void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); + +// ----------------------------------------------- Transfer Functions -------------------------------------------------- + +/** + * @brief Submit a control transfer (URB) to a device + * + * @param[in] dev_hdl Device handle + * @param[in] urb URB + * @return esp_err_t + */ +esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb); + /** * @brief Enqueue a URB to an endpoint * @@ -357,28 +381,6 @@ esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb); */ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret); -/** - * @brief Execute a command on a particular endpoint - * - * Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc) - * - * @param[in] ep_hdl Endpoint handle - * @param[in] command Endpoint command - * @return esp_err_t - */ -esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command); - -/** - * @brief Get the context of an endpoint - * - * Get the context variable assigned to and endpoint on allocation. - * - * @note This function can block - * @param[in] ep_hdl Endpoint handle - * @return Endpoint context - */ -void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); - // -------------------------------------------------- Hub Functions ---------------------------------------------------- // ------------------- Device Related ---------------------- diff --git a/components/usb/usbh.c b/components/usb/usbh.c index a50f4fc944..26daf4a3ff 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -561,7 +561,7 @@ static inline void handle_prop_new_dev(device_t *dev_obj) p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } -// ------------------------------------------------- USBH Functions ---------------------------------------------------- +// -------------------------------------------- USBH Processing Functions ---------------------------------------------- esp_err_t usbh_install(const usbh_config_t *usbh_config) { @@ -700,6 +700,8 @@ esp_err_t usbh_process(void) return ESP_OK; } +// ---------------------------------------------- Device Pool Functions ------------------------------------------------ + esp_err_t usbh_num_devs(int *num_devs_ret) { USBH_CHECK(num_devs_ret != NULL, ESP_ERR_INVALID_ARG); @@ -709,10 +711,6 @@ esp_err_t usbh_num_devs(int *num_devs_ret) return ESP_OK; } -// ------------------------------------------------ Device Functions --------------------------------------------------- - -// --------------------- Device Pool ----------------------- - esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) { USBH_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG); @@ -743,6 +741,48 @@ esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num return ESP_OK; } +esp_err_t usbh_dev_mark_all_free(void) +{ + USBH_ENTER_CRITICAL(); + /* + Go through the device list and mark each device as waiting to be closed. If the device is not opened at all, we can + disable it immediately. + Note: We manually traverse the list because we need to add/remove items while traversing + */ + bool call_proc_req_cb = false; + bool wait_for_free = false; + for (int i = 0; i < 2; i++) { + device_t *dev_obj_cur; + device_t *dev_obj_next; + // Go through pending list first as it's more efficient + if (i == 0) { + dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq); + } else { + dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq); + } + while (dev_obj_cur != NULL) { + // Keep a copy of the next item first in case we remove the current item + dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); + if (dev_obj_cur->dynamic.ref_count == 0) { + // Device is not referenced. Can free immediately. + call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_FREE); + } else { + // Device is still referenced. Just mark it as waiting to be freed + dev_obj_cur->dynamic.flags.waiting_free = 1; + } + // At least one device needs to be freed. User needs to wait for USBH_EVENT_ALL_FREE event + wait_for_free = true; + dev_obj_cur = dev_obj_next; + } + } + USBH_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); + } + return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK; +} + esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); @@ -808,49 +848,9 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) return ESP_OK; } -esp_err_t usbh_dev_mark_all_free(void) -{ - USBH_ENTER_CRITICAL(); - /* - Go through the device list and mark each device as waiting to be closed. If the device is not opened at all, we can - disable it immediately. - Note: We manually traverse the list because we need to add/remove items while traversing - */ - bool call_proc_req_cb = false; - bool wait_for_free = false; - for (int i = 0; i < 2; i++) { - device_t *dev_obj_cur; - device_t *dev_obj_next; - // Go through pending list first as it's more efficient - if (i == 0) { - dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq); - } else { - dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq); - } - while (dev_obj_cur != NULL) { - // Keep a copy of the next item first in case we remove the current item - dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); - if (dev_obj_cur->dynamic.ref_count == 0) { - // Device is not referenced. Can free immediately. - call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_FREE); - } else { - // Device is still referenced. Just mark it as waiting to be freed - dev_obj_cur->dynamic.flags.waiting_free = 1; - } - // At least one device needs to be freed. User needs to wait for USBH_EVENT_ALL_FREE event - wait_for_free = true; - dev_obj_cur = dev_obj_next; - } - } - USBH_EXIT_CRITICAL(); +// ------------------------------------------------ Device Functions --------------------------------------------------- - if (call_proc_req_cb) { - p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); - } - return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK; -} - -// ------------------- Single Device ---------------------- +// ----------------------- Getters ------------------------- esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr) { @@ -928,40 +928,7 @@ exit: return ret; } -esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) -{ - USBH_CHECK(dev_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - USBH_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG); - bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; - USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); - - USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - // Increment the control transfer count first - dev_obj->dynamic.num_ctrl_xfers_inflight++; - USBH_EXIT_CRITICAL(); - - esp_err_t ret; - if (hcd_pipe_get_state(dev_obj->constant.default_pipe) != HCD_PIPE_STATE_ACTIVE) { - ret = ESP_ERR_INVALID_STATE; - goto hcd_err; - } - ret = hcd_urb_enqueue(dev_obj->constant.default_pipe, urb); - if (ret != ESP_OK) { - goto hcd_err; - } - ret = ESP_OK; - return ret; - -hcd_err: - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.num_ctrl_xfers_inflight--; - USBH_EXIT_CRITICAL(); - return ret; -} - -// ----------------------------------------------- Interface Functions ------------------------------------------------- +// ----------------------------------------------- Endpoint Functions ------------------------------------------------- esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, usbh_ep_handle_t *ep_hdl_ret) { @@ -1074,6 +1041,57 @@ esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddre return ret; } +esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command) +{ + USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG); + + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + // Send the command to the EP's underlying pipe + return hcd_pipe_command(ep_obj->constant.pipe_hdl, (hcd_pipe_cmd_t)command); +} + +void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) +{ + assert(ep_hdl); + endpoint_t *ep_obj = (endpoint_t *)ep_hdl; + return hcd_pipe_get_context(ep_obj->constant.pipe_hdl); +} + +// ----------------------------------------------- Transfer Functions -------------------------------------------------- + +esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) +{ + USBH_CHECK(dev_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + USBH_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG); + bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; + USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); + + USBH_ENTER_CRITICAL(); + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + // Increment the control transfer count first + dev_obj->dynamic.num_ctrl_xfers_inflight++; + USBH_EXIT_CRITICAL(); + + esp_err_t ret; + if (hcd_pipe_get_state(dev_obj->constant.default_pipe) != HCD_PIPE_STATE_ACTIVE) { + ret = ESP_ERR_INVALID_STATE; + goto hcd_err; + } + ret = hcd_urb_enqueue(dev_obj->constant.default_pipe, urb); + if (ret != ESP_OK) { + goto hcd_err; + } + ret = ESP_OK; + return ret; + +hcd_err: + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.num_ctrl_xfers_inflight--; + USBH_EXIT_CRITICAL(); + return ret; +} + esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb) { USBH_CHECK(ep_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG); @@ -1104,22 +1122,6 @@ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret) return ESP_OK; } -esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command) -{ - USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG); - - endpoint_t *ep_obj = (endpoint_t *)ep_hdl; - // Send the command to the EP's underlying pipe - return hcd_pipe_command(ep_obj->constant.pipe_hdl, (hcd_pipe_cmd_t)command); -} - -void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) -{ - assert(ep_hdl); - endpoint_t *ep_obj = (endpoint_t *)ep_hdl; - return hcd_pipe_get_context(ep_obj->constant.pipe_hdl); -} - // -------------------------------------------------- Hub Functions ---------------------------------------------------- // ------------------- Device Related ---------------------- From 8053174dd43bc19f8c5ecd678ccf326a10f36720 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 3 Apr 2024 11:24:03 +0800 Subject: [PATCH 10/14] refactor(usb/usbh): Rename device pool functions and ref_count This commit renames the following APIs and variables in the USBH: - Rename the prefix of device pool functions from 'usbh_dev_...' to 'usbh_devs_...'. - Rename 'ref_count' to 'open_count'. This variable tracks the number of times a device has been opened. --- components/usb/private_include/usbh.h | 18 +++++++------- components/usb/usb_host.c | 12 +++++----- components/usb/usbh.c | 34 +++++++++++++-------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index fe80799f87..6dee5e55b4 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -178,13 +178,13 @@ esp_err_t usbh_process(void); * @param[out] num_devs_ret Current number of devices * @return esp_err_t */ -esp_err_t usbh_num_devs(int *num_devs_ret); +esp_err_t usbh_devs_num(int *num_devs_ret); /** * @brief Fill list with address of currently connected devices * * - This function fills the provided list with the address of current connected devices - * - Device address can then be used in usbh_dev_open() + * - Device address can then be used in usbh_devs_open() * - If there are more devices than the list_len, this function will only fill * up to list_len number of devices. * @@ -193,18 +193,18 @@ esp_err_t usbh_num_devs(int *num_devs_ret); * @param[out] num_dev_ret Number of devices filled into list * @return esp_err_t */ -esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); +esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); /** * @brief Mark that all devices should be freed at the next possible opportunity * - * A device marked as free will not be freed until the last client using the device has called usbh_dev_close() + * A device marked as free will not be freed until the last client using the device has called usbh_devs_close() * * @return * - ESP_OK: There were no devices to free to begin with. Current state is all free * - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed") */ -esp_err_t usbh_dev_mark_all_free(void); +esp_err_t usbh_devs_mark_all_free(void); /** * @brief Open a device by address @@ -215,17 +215,17 @@ esp_err_t usbh_dev_mark_all_free(void); * @param[out] dev_hdl Device handle * @return esp_err_t */ -esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); +esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); /** * @brief CLose a device * - * Device can be opened by calling usbh_dev_open() + * Device can be opened by calling usbh_devs_open() * * @param[in] dev_hdl Device handle * @return esp_err_t */ -esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl); +esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl); // ------------------------------------------------ Device Functions --------------------------------------------------- @@ -282,7 +282,7 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config * * This function allows clients to allocate a non-default endpoint (i.e., not EP0) on a connected device * - * - A client must have opened the device using usbh_dev_open() before attempting to allocate an endpoint on the device + * - A client must have opened the device using usbh_devs_open() before attempting to allocate an endpoint on the device * - A client should call this function to allocate all endpoints in an interface that the client has claimed. * - A client must allocate an endpoint using this function before attempting to communicate with it * - Once the client allocates an endpoint, the client is now owns/manages the endpoint. No other client should use or diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 11fd7810aa..b9b12dafcc 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -574,7 +574,7 @@ esp_err_t usb_host_lib_info(usb_host_lib_info_t *info_ret) HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE); num_clients_temp = p_host_lib_obj->dynamic.flags.num_clients; HOST_EXIT_CRITICAL(); - usbh_num_devs(&num_devs_temp); + usbh_devs_num(&num_devs_temp); // Write back return values info_ret->num_devices = num_devs_temp; @@ -820,7 +820,7 @@ esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_ esp_err_t ret; usb_device_handle_t dev_hdl; - ret = usbh_dev_open(dev_addr, &dev_hdl); + ret = usbh_devs_open(dev_addr, &dev_hdl); if (ret != ESP_OK) { goto exit; } @@ -841,7 +841,7 @@ esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_ return ret; already_opened: - ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + ESP_ERROR_CHECK(usbh_devs_close(dev_hdl)); exit: return ret; } @@ -883,7 +883,7 @@ esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_ _clear_client_opened_device(client_obj, dev_addr); HOST_EXIT_CRITICAL(); - ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + ESP_ERROR_CHECK(usbh_devs_close(dev_hdl)); ret = ESP_OK; exit: xSemaphoreGive(p_host_lib_obj->constant.mux_lock); @@ -896,7 +896,7 @@ esp_err_t usb_host_device_free_all(void) HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.flags.num_clients == 0, ESP_ERR_INVALID_STATE); // All clients must have been deregistered HOST_EXIT_CRITICAL(); esp_err_t ret; - ret = usbh_dev_mark_all_free(); + ret = usbh_devs_mark_all_free(); // If ESP_ERR_NOT_FINISHED is returned, caller must wait for USB_HOST_LIB_EVENT_FLAGS_ALL_FREE to confirm all devices are free return ret; } @@ -904,7 +904,7 @@ esp_err_t usb_host_device_free_all(void) esp_err_t usb_host_device_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) { HOST_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG); - return usbh_dev_addr_list_fill(list_len, dev_addr_list, num_dev_ret); + return usbh_devs_addr_list_fill(list_len, dev_addr_list, num_dev_ret); } // ------------------------------------------------- Device Requests --------------------------------------------------- diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 26daf4a3ff..06d6f8d942 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -64,7 +64,7 @@ struct device_s { uint32_t action_flags; int num_ctrl_xfers_inflight; usb_device_state_t state; - uint32_t ref_count; + uint32_t open_count; } dynamic; // Mux protected members must be protected by the USBH mux_lock when accessed struct { @@ -702,7 +702,7 @@ esp_err_t usbh_process(void) // ---------------------------------------------- Device Pool Functions ------------------------------------------------ -esp_err_t usbh_num_devs(int *num_devs_ret) +esp_err_t usbh_devs_num(int *num_devs_ret) { USBH_CHECK(num_devs_ret != NULL, ESP_ERR_INVALID_ARG); xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); @@ -711,7 +711,7 @@ esp_err_t usbh_num_devs(int *num_devs_ret) return ESP_OK; } -esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) +esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) { USBH_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG); USBH_ENTER_CRITICAL(); @@ -741,7 +741,7 @@ esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num return ESP_OK; } -esp_err_t usbh_dev_mark_all_free(void) +esp_err_t usbh_devs_mark_all_free(void) { USBH_ENTER_CRITICAL(); /* @@ -763,11 +763,11 @@ esp_err_t usbh_dev_mark_all_free(void) while (dev_obj_cur != NULL) { // Keep a copy of the next item first in case we remove the current item dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry); - if (dev_obj_cur->dynamic.ref_count == 0) { - // Device is not referenced. Can free immediately. + if (dev_obj_cur->dynamic.open_count == 0) { + // Device is not opened. Can free immediately. call_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_FREE); } else { - // Device is still referenced. Just mark it as waiting to be freed + // Device is still opened. Just mark it as waiting to be freed dev_obj_cur->dynamic.flags.waiting_free = 1; } // At least one device needs to be freed. User needs to wait for USBH_EVENT_ALL_FREE event @@ -783,7 +783,7 @@ esp_err_t usbh_dev_mark_all_free(void) return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK; } -esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) +esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret; @@ -806,11 +806,11 @@ esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) } exit: if (found_dev_obj != NULL) { - // The device is not in a state to be referenced + // The device is not in a state to be opened if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { ret = ESP_ERR_INVALID_STATE; } else { - dev_obj->dynamic.ref_count++; + dev_obj->dynamic.open_count++; *dev_hdl = (usb_device_handle_t)found_dev_obj; ret = ESP_OK; } @@ -822,18 +822,18 @@ exit: return ret; } -esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) +esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); - dev_obj->dynamic.ref_count--; + dev_obj->dynamic.open_count--; bool call_proc_req_cb = false; - if (dev_obj->dynamic.ref_count == 0) { + if (dev_obj->dynamic.open_count == 0) { // Sanity check. assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); // There cannot be any control transfer in-flight - assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when ref count reaches 0 + assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when open_count reaches 0 if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { // Device is already gone or is awaiting to be freed. Trigger the USBH process to free the device call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); @@ -1152,11 +1152,11 @@ esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl) USBH_ENTER_CRITICAL(); dev_obj->dynamic.flags.is_gone = 1; // Check if the device can be freed immediately - if (dev_obj->dynamic.ref_count == 0) { - // Device is not currently referenced at all. Can free immediately. + if (dev_obj->dynamic.open_count == 0) { + // Device is not currently opened at all. Can free immediately. call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); } else { - // Device is still being referenced. Flush endpoints and propagate device gone event + // Device is still opened. Flush endpoints and propagate device gone event call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_EPn_HALT_FLUSH | DEV_ACTION_EP0_FLUSH | From 144463be5c78fb8591bfd3aa4f02fed66f37a798 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 24 Apr 2024 18:47:41 +0800 Subject: [PATCH 11/14] refactor(usb/hcd): Allow port resets with allocated pipes This commit updates the HCD API to allow port resets to occur even if pipes are allocated. The pipes cannot be active and the port reset will simply restore the pipes (by reinitializing their channel registers) following the reset. Changes: - Allow port resets while channels are allocated - Remove pipe persistance API 'hcd_pipe_set_persist_reset()' --- components/hal/include/hal/usb_dwc_hal.h | 17 ++- components/usb/hcd_dwc.c | 174 +++++++---------------- components/usb/hub.c | 1 - components/usb/private_include/hcd.h | 16 +-- 4 files changed, 62 insertions(+), 146 deletions(-) diff --git a/components/hal/include/hal/usb_dwc_hal.h b/components/hal/include/hal/usb_dwc_hal.h index 9b2a2c3bc3..33ac57be75 100644 --- a/components/hal/include/hal/usb_dwc_hal.h +++ b/components/hal/include/hal/usb_dwc_hal.h @@ -205,7 +205,7 @@ typedef struct { * - The peripheral must have been reset and clock un-gated * - The USB PHY (internal or external) and associated GPIOs must already be configured * - GPIO pins configured - * - Interrupt allocated but DISABLED (in case of an unknown interupt state) + * - Interrupt allocated but DISABLED (in case of an unknown interrupt state) * Exit: * - Checks to see if DWC_OTG is alive, and if HW version/config is correct * - HAl context initialized @@ -290,7 +290,7 @@ static inline void usb_dwc_hal_port_init(usb_dwc_hal_context_t *hal) /** * @brief Deinitialize the host port * - * - Will disable the host port's interrupts preventing further port aand channel events from ocurring + * - Will disable the host port's interrupts preventing further port aand channel events from occurring * * @param hal Context of the HAL layer */ @@ -333,7 +333,6 @@ static inline void usb_dwc_hal_port_toggle_power(usb_dwc_hal_context_t *hal, boo */ static inline void usb_dwc_hal_port_toggle_reset(usb_dwc_hal_context_t *hal, bool enable) { - HAL_ASSERT(hal->channels.num_allocd == 0); //Cannot reset if there are still allocated channels usb_dwc_ll_hprt_set_port_reset(hal->dev, enable); } @@ -447,7 +446,7 @@ static inline void usb_dwc_hal_port_periodic_enable(usb_dwc_hal_context_t *hal) /** * @brief Disable periodic scheduling * - * Disabling periodic scheduling will save a bit of DMA bandwith (as the controller will no longer fetch the schedule + * Disabling periodic scheduling will save a bit of DMA bandwidth (as the controller will no longer fetch the schedule * from the frame list). * * @note Before disabling periodic scheduling, it is the user's responsibility to ensure that all periodic channels have @@ -505,17 +504,17 @@ static inline usb_dwc_speed_t usb_dwc_hal_port_get_conn_speed(usb_dwc_hal_contex * @brief Disable the debounce lock * * This function must be called after calling usb_dwc_hal_port_check_if_connected() and will allow connection/disconnection - * events to occur again. Any pending connection or disconenction interrupts are cleared. + * events to occur again. Any pending connection or disconnection interrupts are cleared. * * @param hal Context of the HAL layer */ static inline void usb_dwc_hal_disable_debounce_lock(usb_dwc_hal_context_t *hal) { hal->flags.dbnc_lock_enabled = 0; - //Clear Conenction and disconenction interrupt in case it triggered again + //Clear Connection and disconnection interrupt in case it triggered again usb_dwc_ll_gintsts_clear_intrs(hal->dev, USB_DWC_LL_INTR_CORE_DISCONNINT); usb_dwc_ll_hprt_intr_clear(hal->dev, USB_DWC_LL_INTR_HPRT_PRTCONNDET); - //Reenable the hprt (connection) and disconnection interrupts + //Re-enable the hprt (connection) and disconnection interrupts usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_DISCONNINT); } @@ -672,10 +671,10 @@ bool usb_dwc_hal_chan_request_halt(usb_dwc_hal_chan_t *chan_obj); /** * @brief Indicate that a channel is halted after a port error * - * When a port error occurs (e.g., discconect, overcurrent): + * When a port error occurs (e.g., disconnect, overcurrent): * - Any previously active channels will remain active (i.e., they will not receive a channel interrupt) * - Attempting to disable them using usb_dwc_hal_chan_request_halt() will NOT generate an interrupt for ISOC channels - * (probalby something to do with the periodic scheduling) + * (probably something to do with the periodic scheduling) * * However, the channel's enable bit can be left as 1 since after a port error, a soft reset will be done anyways. * This function simply updates the channels internal state variable to indicate it is halted (thus allowing it to be diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 53b1aef38f..19104f8b8c 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -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: Apache-2.0 */ @@ -174,9 +174,7 @@ struct pipe_obj { uint32_t waiting_halt: 1; uint32_t pipe_cmd_processing: 1; uint32_t has_urb: 1; // Indicates there is at least one URB either pending, in-flight, or done - uint32_t persist: 1; // indicates that this pipe should persist through a run-time port reset - uint32_t reset_lock: 1; // Indicates that this pipe is undergoing a run-time reset - uint32_t reserved27: 27; + uint32_t reserved29: 29; }; uint32_t val; } cs_flags; @@ -443,28 +441,6 @@ static esp_err_t _pipe_cmd_clear(pipe_t *pipe); // ------------------------ Port --------------------------- -/** - * @brief Prepare persistent pipes for reset - * - * This function checks if all pipes are reset persistent and proceeds to free their underlying HAL channels for the - * persistent pipes. This should be called before a run time reset - * - * @param port Port object - * @return true All pipes are persistent and their channels are freed - * @return false Not all pipes are persistent - */ -static bool _port_persist_all_pipes(port_t *port); - -/** - * @brief Recovers all persistent pipes after a reset - * - * This function will recover all persistent pipes after a reset and reallocate their underlying HAl channels. This - * function should be called after a reset. - * - * @param port Port object - */ -static void _port_recover_all_pipes(port_t *port); - /** * @brief Checks if all pipes are in the halted state * @@ -993,43 +969,6 @@ esp_err_t hcd_uninstall(void) // ----------------------- Helpers ------------------------- -static bool _port_persist_all_pipes(port_t *port) -{ - if (port->num_pipes_queued > 0) { - // All pipes must be idle before we run-time reset - return false; - } - bool all_persist = true; - pipe_t *pipe; - // Check that each pipe is persistent - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - if (!pipe->cs_flags.persist) { - all_persist = false; - break; - } - } - if (!all_persist) { - // At least one pipe is not persistent. All pipes must be freed or made persistent before we can reset - return false; - } - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - pipe->cs_flags.reset_lock = 1; - usb_dwc_hal_chan_free(port->hal, pipe->chan_obj); - } - return true; -} - -static void _port_recover_all_pipes(port_t *port) -{ - pipe_t *pipe; - TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { - pipe->cs_flags.persist = 0; - pipe->cs_flags.reset_lock = 0; - usb_dwc_hal_chan_alloc(port->hal, pipe->chan_obj, (void *)pipe); - usb_dwc_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char); - } -} - static bool _port_check_all_pipes_halted(port_t *port) { bool all_halted = true; @@ -1106,20 +1045,26 @@ static esp_err_t _port_cmd_power_off(port_t *port) static esp_err_t _port_cmd_reset(port_t *port) { esp_err_t ret; - // Port can only a reset when it is in the enabled or disabled states (in case of new connection) + + // Port can only a reset when it is in the enabled or disabled (in the case of a new connection)states. if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_DISABLED) { ret = ESP_ERR_INVALID_STATE; goto exit; } - bool is_runtime_reset = (port->state == HCD_PORT_STATE_ENABLED) ? true : false; - if (is_runtime_reset && !_port_persist_all_pipes(port)) { - // If this is a run time reset, check all pipes that are still allocated can persist the reset + // Port can only be reset if all pipes are idle + if (port->num_pipes_queued > 0) { ret = ESP_ERR_INVALID_STATE; goto exit; } - // All pipes (if any_) are guaranteed to be persistent at this point. Proceed to resetting the bus + /* + Proceed to resetting the bus + - Update the port's state variable + - Hold the bus in the reset state for RESET_HOLD_MS. + - Return the bus to the idle state for RESET_RECOVERY_MS + */ port->state = HCD_PORT_STATE_RESETTING; - // Put and hold the bus in the reset state. If the port was previously enabled, a disabled event will occur after this + + // Place the bus into the reset state. If the port was previously enabled, a disabled event will occur after this usb_dwc_hal_port_toggle_reset(port->hal, true); HCD_EXIT_CRITICAL(); vTaskDelay(pdMS_TO_TICKS(RESET_HOLD_MS)); @@ -1129,7 +1074,8 @@ static esp_err_t _port_cmd_reset(port_t *port) ret = ESP_ERR_INVALID_RESPONSE; goto bailout; } - // Return the bus to the idle state and hold it for the required reset recovery time. Port enabled event should occur + + // Return the bus to the idle state. Port enabled event should occur usb_dwc_hal_port_toggle_reset(port->hal, false); HCD_EXIT_CRITICAL(); vTaskDelay(pdMS_TO_TICKS(RESET_RECOVERY_MS)); @@ -1139,15 +1085,25 @@ static esp_err_t _port_cmd_reset(port_t *port) ret = ESP_ERR_INVALID_RESPONSE; goto bailout; } - // Set FIFO sizes based on the selected biasing - usb_dwc_hal_set_fifo_bias(port->hal, port->fifo_bias); - // We start periodic scheduling only after a RESET command since SOFs only start after a reset - usb_dwc_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN); - usb_dwc_hal_port_periodic_enable(port->hal); + + // Reinitialize port registers. + usb_dwc_hal_set_fifo_bias(port->hal, port->fifo_bias); // Set FIFO biases + usb_dwc_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN); // Set periodic frame list + usb_dwc_hal_port_periodic_enable(port->hal); // Enable periodic scheduling + ret = ESP_OK; bailout: - if (is_runtime_reset) { - _port_recover_all_pipes(port); + /* + We add a blank statement here to work around a quirk in the C language where a label can only be followed by + statements, thus generating this warning when building with "-Wpedantic": + + "warning: a label can only be part of a statement and a declaration is not a statement" + */ + {}; + // Reinitialize channel registers + pipe_t *pipe; + TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) { + usb_dwc_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char); } exit: return ret; @@ -1814,8 +1770,7 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl) HCD_ENTER_CRITICAL(); // Check that all URBs have been removed and pipe has no pending events HCD_CHECK_FROM_CRIT(!pipe->multi_buffer_control.buffer_is_executing - && !pipe->cs_flags.has_urb - && !pipe->cs_flags.reset_lock, + && !pipe->cs_flags.has_urb, ESP_ERR_INVALID_STATE); // Remove pipe from the list of idle pipes (it must be in the idle list because it should have no queued URBs) TAILQ_REMOVE(&pipe->port->pipes_idle_tailq, pipe, tailq_entry); @@ -1838,8 +1793,7 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps) HCD_ENTER_CRITICAL(); // Check if pipe is in the correct state to be updated HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb && - !pipe->cs_flags.reset_lock, + !pipe->cs_flags.has_urb, ESP_ERR_INVALID_STATE); pipe->ep_char.mps = mps; // Update the underlying channel's registers @@ -1854,8 +1808,7 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) HCD_ENTER_CRITICAL(); // Check if pipe is in the correct state to be updated HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb && - !pipe->cs_flags.reset_lock, + !pipe->cs_flags.has_urb, ESP_ERR_INVALID_STATE); pipe->ep_char.dev_addr = dev_addr; // Update the underlying channel's registers @@ -1870,8 +1823,7 @@ esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback HCD_ENTER_CRITICAL(); // Check if pipe is in the correct state to be updated HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb && - !pipe->cs_flags.reset_lock, + !pipe->cs_flags.has_urb, ESP_ERR_INVALID_STATE); pipe->callback = callback; pipe->callback_arg = user_arg; @@ -1879,20 +1831,6 @@ esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback return ESP_OK; } -esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl) -{ - pipe_t *pipe = (pipe_t *)pipe_hdl; - HCD_ENTER_CRITICAL(); - // Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb && - !pipe->cs_flags.reset_lock, - ESP_ERR_INVALID_STATE); - pipe->cs_flags.persist = 1; - HCD_EXIT_CRITICAL(); - return ESP_OK; -} - void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl) { pipe_t *pipe = (pipe_t *)pipe_hdl; @@ -1929,27 +1867,22 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command) esp_err_t ret = ESP_OK; HCD_ENTER_CRITICAL(); - // Cannot execute pipe commands the pipe is already executing a command, or if the pipe or its port are no longer valid - if (pipe->cs_flags.reset_lock) { - ret = ESP_ERR_INVALID_STATE; - } else { - pipe->cs_flags.pipe_cmd_processing = 1; - switch (command) { - case HCD_PIPE_CMD_HALT: { - ret = _pipe_cmd_halt(pipe); - break; - } - case HCD_PIPE_CMD_FLUSH: { - ret = _pipe_cmd_flush(pipe); - break; - } - case HCD_PIPE_CMD_CLEAR: { - ret = _pipe_cmd_clear(pipe); - break; - } - } - pipe->cs_flags.pipe_cmd_processing = 0; + pipe->cs_flags.pipe_cmd_processing = 1; + switch (command) { + case HCD_PIPE_CMD_HALT: { + ret = _pipe_cmd_halt(pipe); + break; } + case HCD_PIPE_CMD_FLUSH: { + ret = _pipe_cmd_flush(pipe); + break; + } + case HCD_PIPE_CMD_CLEAR: { + ret = _pipe_cmd_clear(pipe); + break; + } + } + pipe->cs_flags.pipe_cmd_processing = 0; HCD_EXIT_CRITICAL(); return ret; } @@ -2477,8 +2410,7 @@ esp_err_t hcd_urb_enqueue(hcd_pipe_handle_t pipe_hdl, urb_t *urb) // Check that pipe and port are in the correct state to receive URBs HCD_CHECK_FROM_CRIT(pipe->port->state == HCD_PORT_STATE_ENABLED // The pipe's port must be in the correct state && pipe->state == HCD_PIPE_STATE_ACTIVE // The pipe must be in the correct state - && !pipe->cs_flags.pipe_cmd_processing // Pipe cannot currently be processing a pipe command - && !pipe->cs_flags.reset_lock, // Pipe cannot be persisting through a port reset + && !pipe->cs_flags.pipe_cmd_processing, // Pipe cannot currently be processing a pipe command ESP_ERR_INVALID_STATE); // Use the URB's reserved_ptr to store the pipe's urb->hcd_ptr = (void *)pipe; diff --git a/components/usb/hub.c b/components/usb/hub.c index e5d5e80582..e5209fda9c 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -285,7 +285,6 @@ static bool enum_stage_start(enum_ctrl_t *enum_ctrl) static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl) { - ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(enum_ctrl->pipe)); // Persist the default pipe through the reset if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); return false; diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index b640f0e8bd..24ec22b085 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -434,20 +434,6 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) */ esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg); -/** - * @brief Make a pipe persist through a run time reset - * - * Normally when a HCD_PORT_CMD_RESET is called, all pipes should already have been freed. However There may be cases - * (such as during enumeration) when a pipe must persist through a reset. This function will mark a pipe as - * persistent allowing it to survive a reset. When HCD_PORT_CMD_RESET is called, the pipe can continue to be used after - * the reset. - * - * @param pipe_hdl Pipe handle - * @retval ESP_OK: Pipe successfully marked as persistent - * @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be made persistent - */ -esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl); - /** * @brief Get the context variable of a pipe from its handle * From 15121a3ef662c550b5b5b28f546ab02a9fb57ce0 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Tue, 16 Apr 2024 01:51:30 +0800 Subject: [PATCH 12/14] refactor(usb/hub): Update Hub driver port request logic --- components/usb/hub.c | 79 ++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/components/usb/hub.c b/components/usb/hub.c index e5209fda9c..788af291ce 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -50,9 +50,12 @@ implement the bare minimum to control the root HCD port. // Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive #define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 -#define HUB_DRIVER_FLAG_ACTION_PORT 0x02 +#define HUB_DRIVER_FLAG_ACTION_PORT_REQ 0x02 #define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04 +#define PORT_REQ_DISABLE 0x01 +#define PORT_REQ_RECOVER 0x02 + /** * @brief Root port states * @@ -185,6 +188,7 @@ typedef struct { uint32_t val; } flags; root_port_state_t root_port_state; + unsigned int port_reqs; } dynamic; // Single thread members don't require a critical section so long as they are never accessed from multiple threads struct { @@ -679,7 +683,8 @@ static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) HUB_DRIVER_ENTER_CRITICAL(); // Enum could have failed due to a port error. If so, we need to trigger a port recovery if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) { - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; } else { // Otherwise, we move to the enum failed state and wait for the device to disconnect p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED; @@ -839,7 +844,8 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) case ROOT_PORT_STATE_POWERED: // This occurred before enumeration case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration. // Therefore, there's no device and we can go straight to port recovery - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; break; case ROOT_PORT_STATE_ENUM: // This occurred during enumeration. Therefore, we need to cleanup the failed enumeration @@ -867,6 +873,30 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) } } +static void root_port_req(hcd_port_handle_t root_port_hdl) +{ + unsigned int port_reqs; + + HUB_DRIVER_ENTER_CRITICAL(); + port_reqs = p_hub_driver_obj->dynamic.port_reqs; + p_hub_driver_obj->dynamic.port_reqs = 0; + HUB_DRIVER_EXIT_CRITICAL(); + + if (port_reqs & PORT_REQ_DISABLE) { + ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); + // We allow this to fail in case a disconnect/port error happens while disabling. + hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); + } + if (port_reqs & PORT_REQ_RECOVER) { + ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); + ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); + ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; + HUB_DRIVER_EXIT_CRITICAL(); + } +} + static void enum_handle_events(void) { bool stage_pass; @@ -1055,10 +1085,24 @@ esp_err_t hub_dev_is_free(uint8_t dev_addr) { assert(dev_addr == ENUM_DEV_ADDR); assert(p_hub_driver_obj->single_thread.root_dev_hdl); - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; // Device is free, we can now request its port be recycled + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT; + // How the port is recycled will depend on the port's state + switch (port_state) { + case HCD_PORT_STATE_ENABLED: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; + break; + case HCD_PORT_STATE_RECOVERY: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; + break; + default: + abort(); // Should never occur + break; + } + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; HUB_DRIVER_EXIT_CRITICAL(); p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); @@ -1076,29 +1120,8 @@ esp_err_t hub_process(void) if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); } - if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT) { - // Check current state of port - hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); - switch (port_state) { - case HCD_PORT_STATE_ENABLED: - // Port is still enabled with a connect device. Disable it. - ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); - // We allow this to fail in case a disconnect/port error happens while disabling. - hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); - break; - case HCD_PORT_STATE_RECOVERY: - // Port is in recovery after a disconnect/error. Recover it. - ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); - ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); - ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED; - HUB_DRIVER_EXIT_CRITICAL(); - break; - default: - abort(); // Should never occur - break; - } + if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) { + root_port_req(p_hub_driver_obj->constant.root_port_hdl); } if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { enum_handle_events(); From 29ae4e7a4f9cb834c69bd339792248d7869dc2dc Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 10 Apr 2024 05:06:46 +0800 Subject: [PATCH 13/14] refactor(usb/usbh): Update USBH device creation and enumeration handling This commit updates how the USBH handles device creation and enumeration so that upper layers (such as the Hub driver) can use the USBH API for enumeration instead of calling the HCD. USBH Updates: USBH now creates unenumerated devices set to address 0 with no device/config descriptor. A newly created device can be opened and communicated with immediately (using control transfers). This allows the Hub driver to call the USBH instead of the HCD. Summary of USBH changes: - Added new APIs to add/remove a device. Devices are now created as unenumerated and can be immediately opened and communicated with. - Added new APIs to enumerate a device (see 'usbh_dev_set_...()' functions). Device must be locked (see 'usbh_dev_enum_lock()') before enumeration functions can be called. - Added UID for each device. This allows the particular USBH without needing to use the device's handle (which implies opening the device). Hub Driver Updates: Hub driver now calls the USBH for enumeration. Summary of USBH changes: - Replace all 'hcd_pipe_...()' calls with 'usbh_dev_...()' calls - Refactored port event handling to fit with new USBH API - Updated to use UID to uniquely identify devices without opening them USB Host Updates: - Reroute USBH control transfers to clients and hub driver Note: Backported ESP_ERR_NOT_ALLOWED macro --- components/usb/hcd_dwc.c | 14 - components/usb/hub.c | 230 ++++---- components/usb/private_include/hcd.h | 16 - components/usb/private_include/hub.h | 12 +- components/usb/private_include/usbh.h | 245 +++++---- components/usb/usb_host.c | 38 +- components/usb/usbh.c | 737 +++++++++++++++++--------- 7 files changed, 769 insertions(+), 523 deletions(-) diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 19104f8b8c..468077ecbf 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -1817,20 +1817,6 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr) return ESP_OK; } -esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg) -{ - pipe_t *pipe = (pipe_t *)pipe_hdl; - HCD_ENTER_CRITICAL(); - // Check if pipe is in the correct state to be updated - HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing && - !pipe->cs_flags.has_urb, - ESP_ERR_INVALID_STATE); - pipe->callback = callback; - pipe->callback_arg = user_arg; - HCD_EXIT_CRITICAL(); - return ESP_OK; -} - void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl) { pipe_t *pipe = (pipe_t *)pipe_hdl; diff --git a/components/usb/hub.c b/components/usb/hub.c index 788af291ce..2b98a057b4 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -40,6 +40,7 @@ implement the bare minimum to control the root HCD port. #define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE #define ENUM_DEV_ADDR 1 // Device address used in enumeration +#define ENUM_DEV_UID 1 // Unique ID for device connected to root port #define ENUM_CONFIG_INDEX_DEFAULT 0 // Index used to get the first configuration descriptor of the device #define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) #define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device @@ -63,9 +64,8 @@ implement the bare minimum to control the root HCD port. typedef enum { ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */ ROOT_PORT_STATE_POWERED, /**< Root port is powered, device is not connected */ - ROOT_PORT_STATE_ENUM, /**< A device has been connected to the root port and is undergoing enumeration */ - ROOT_PORT_STATE_ENUM_FAILED, /**< Enumeration of a connected device has failed. Waiting for that device to be disconnected */ - ROOT_PORT_ACTIVE, /**< The connected device was enumerated and port is active */ + ROOT_PORT_STATE_DISABLED, /**< A device is connected but is disabled (i.e., not reset, no SOFs are sent) */ + ROOT_PORT_STATE_ENABLED, /**< A device is connected, port has been reset, SOFs are sent */ ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */ } root_port_state_t; @@ -159,7 +159,6 @@ typedef struct { urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */ // Initialized at start of a particular enumeration usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */ - hcd_pipe_handle_t pipe; /**< Default pipe handle of the device being enumerated */ // Updated during enumeration enum_stage_t stage; /**< Current enumeration stage */ int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */ @@ -192,7 +191,7 @@ typedef struct { } dynamic; // Single thread members don't require a critical section so long as they are never accessed from multiple threads struct { - usb_device_handle_t root_dev_hdl; // Indicates if an enumerated device is connected to the root port + unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected enum_ctrl_t enum_ctrl; } single_thread; // Constant members do no change after installation thus do not require a critical section @@ -245,40 +244,26 @@ const char *HUB_DRIVER_TAG = "HUB"; static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); /** - * @brief HCD pipe callback for the default pipe of the device under enumeration + * @brief Control transfer callback used for enumeration * - * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process() - * - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run - * - * @param pipe_hdl HCD pipe handle - * @param pipe_event Pipe event - * @param user_arg Callback argument - * @param in_isr Whether callback is in an ISR context - * @return Whether a yield is required + * @param transfer Transfer object */ -static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); +static void enum_transfer_callback(usb_transfer_t *transfer); // ------------------------------------------------- Enum Functions ---------------------------------------------------- static bool enum_stage_start(enum_ctrl_t *enum_ctrl) { - // Get the speed of the device, and set the enum MPS to the worst case size for now - usb_speed_t speed; - if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { - return false; - } - enum_ctrl->bMaxPacketSize0 = (speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; - // Try to add the device to USBH - usb_device_handle_t enum_dev_hdl; - hcd_pipe_handle_t enum_dflt_pipe_hdl; - // We use NULL as the parent device to indicate the Root Hub port 1. We currently only support a single device - if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) { - return false; - } - // Set our own default pipe callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL)); - enum_ctrl->dev_hdl = enum_dev_hdl; - enum_ctrl->pipe = enum_dflt_pipe_hdl; + // Open the newly added device (at address 0) + ESP_ERROR_CHECK(usbh_devs_open(0, &p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); + + // Get the speed of the device to set the initial MPS of EP0 + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl, &dev_info)); + enum_ctrl->bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; + + // Lock the device for enumeration. This allows us call usbh_dev_set_...() functions during enumeration + ESP_ERROR_CHECK(usbh_dev_enum_lock(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); // Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb #ifdef ENABLE_ENUM_FILTER_CALLBACK @@ -445,7 +430,7 @@ static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl) abort(); break; } - if (hcd_urb_enqueue(enum_ctrl->pipe, enum_ctrl->urb) != ESP_OK) { + if (usbh_dev_submit_ctrl_urb(enum_ctrl->dev_hdl, enum_ctrl->urb) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to submit: %s", enum_stage_strings[enum_ctrl->stage]); return false; } @@ -470,18 +455,10 @@ static bool enum_stage_wait(enum_ctrl_t *enum_ctrl) static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) { - // Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(enum_ctrl->pipe); - assert(dequeued_enum_urb == enum_ctrl->urb); - // Check transfer status - usb_transfer_t *transfer = &dequeued_enum_urb->transfer; + usb_transfer_t *transfer = &enum_ctrl->urb->transfer; if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) { ESP_LOGE(HUB_DRIVER_TAG, "Bad transfer status %d: %s", transfer->status, enum_stage_strings[enum_ctrl->stage]); - if (transfer->status == USB_TRANSFER_STATUS_STALL) { - // EP stalled, clearing the pipe to execute further stages - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_CLEAR)); - } return false; } // Check IN transfer returned the expected correct number of bytes @@ -508,8 +485,8 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) ret = false; break; } - // Update and save the MPS of the default pipe - if (hcd_pipe_update_mps(enum_ctrl->pipe, device_desc->bMaxPacketSize0) != ESP_OK) { + // Update and save the MPS of the EP0 + if (usbh_dev_set_ep0_mps(enum_ctrl->dev_hdl, device_desc->bMaxPacketSize0) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to update MPS"); ret = false; break; @@ -520,16 +497,15 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) break; } case ENUM_STAGE_CHECK_ADDR: { - // Update the pipe and device's address, and fill the address into the device object - ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(enum_ctrl->pipe, ENUM_DEV_ADDR)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); + // Update the device's address + ESP_ERROR_CHECK(usbh_dev_set_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); ret = true; break; } case ENUM_STAGE_CHECK_FULL_DEV_DESC: { - // Fill device descriptor into the device object + // Set the device's descriptor const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(enum_ctrl->dev_hdl, device_desc)); + ESP_ERROR_CHECK(usbh_dev_set_desc(enum_ctrl->dev_hdl, device_desc)); enum_ctrl->iManufacturer = device_desc->iManufacturer; enum_ctrl->iProduct = device_desc->iProduct; enum_ctrl->iSerialNumber = device_desc->iSerialNumber; @@ -558,10 +534,10 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) break; } case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: { - // Fill configuration descriptor into the device object + // Set the device's configuration descriptor const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); enum_ctrl->bConfigurationValue = config_desc->bConfigurationValue; - ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(enum_ctrl->dev_hdl, config_desc)); + ESP_ERROR_CHECK(usbh_dev_set_config_desc(enum_ctrl->dev_hdl, config_desc)); ret = true; break; } @@ -638,7 +614,7 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) } else { // ENUM_STAGE_CHECK_FULL_PROD_STR_DESC select = 2; } - ESP_ERROR_CHECK(usbh_hub_enum_fill_str_desc(enum_ctrl->dev_hdl, str_desc, select)); + ESP_ERROR_CHECK(usbh_dev_set_str_desc(enum_ctrl->dev_hdl, str_desc, select)); ret = true; break; } @@ -653,43 +629,27 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) { - // We currently only support a single device connected to the root port. Move the device handle from enum to root - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_ACTIVE; - HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.root_dev_hdl = enum_ctrl->dev_hdl; - usb_device_handle_t dev_hdl = enum_ctrl->dev_hdl; + // Unlock the device as we are done with the enumeration + ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); + // Propagate a new device event + ESP_ERROR_CHECK(usbh_devs_new_dev_event(enum_ctrl->dev_hdl)); + // We are done with using the device. Close it. + ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); // Clear values in enum_ctrl enum_ctrl->dev_hdl = NULL; - enum_ctrl->pipe = NULL; - // Update device object after enumeration is done - ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl)); } static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) { - // Enumeration failed. Clear the enum device handle and pipe if (enum_ctrl->dev_hdl) { - // If enumeration failed due to a port event, we need to Halt, flush, and dequeue enum default pipe in case there - // was an in-flight URB. - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_FLUSH)); - hcd_urb_dequeue(enum_ctrl->pipe); // This could return NULL if there - ESP_ERROR_CHECK(usbh_hub_enum_failed(enum_ctrl->dev_hdl)); // Free the underlying device object first before recovering the port + // Close the device and unlock it as we done with enumeration + ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); + ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); + // We allow this to fail in case the device object was already freed + usbh_devs_remove(ENUM_DEV_UID); } // Clear values in enum_ctrl enum_ctrl->dev_hdl = NULL; - enum_ctrl->pipe = NULL; - HUB_DRIVER_ENTER_CRITICAL(); - // Enum could have failed due to a port error. If so, we need to trigger a port recovery - if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) { - p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; - } else { - // Otherwise, we move to the enum failed state and wait for the device to disconnect - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED; - } - HUB_DRIVER_EXIT_CRITICAL(); } static enum_stage_t get_next_stage(enum_stage_t old_stage, enum_ctrl_t *enum_ctrl) @@ -803,13 +763,13 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } -static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) +static void enum_transfer_callback(usb_transfer_t *transfer) { - // Note: This callback may have triggered when a failed enumeration is already cleaned up (e.g., due to a failed port reset) + // We simply trigger a processing request to handle the completed enumeration control transfer HUB_DRIVER_ENTER_CRITICAL_SAFE(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); - return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } // ---------------------- Handlers ------------------------- @@ -822,17 +782,32 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) // Nothing to do break; case HCD_PORT_EVENT_CONNECTION: { - if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) == ESP_OK) { - ESP_LOGD(HUB_DRIVER_TAG, "Root port reset"); - // Start enumeration - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM; - HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; - } else { + if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed"); + goto reset_err; } + ESP_LOGD(HUB_DRIVER_TAG, "Root port reset"); + usb_speed_t speed; + if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { + goto new_dev_err; + } + // Allocate a new device. We use a fixed ENUM_DEV_UID for now since we only support a single device + if (usbh_devs_add(ENUM_DEV_UID, speed, p_hub_driver_obj->constant.root_port_hdl) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device"); + goto new_dev_err; + } + p_hub_driver_obj->single_thread.root_dev_uid = ENUM_DEV_UID; + // Start enumeration + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED; + HUB_DRIVER_EXIT_CRITICAL(); + p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; + break; +new_dev_err: + // We allow this to fail in case a disconnect/port error happens while disabling. + hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); +reset_err: break; } case HCD_PORT_EVENT_DISCONNECTION: @@ -842,18 +817,13 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) HUB_DRIVER_ENTER_CRITICAL(); switch (p_hub_driver_obj->dynamic.root_port_state) { case ROOT_PORT_STATE_POWERED: // This occurred before enumeration - case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration. - // Therefore, there's no device and we can go straight to port recovery + case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled + // Therefore, there's no device object to clean up, and we can go straight to port recovery p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; break; - case ROOT_PORT_STATE_ENUM: - // This occurred during enumeration. Therefore, we need to cleanup the failed enumeration - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED; - break; - case ROOT_PORT_ACTIVE: - // There was an enumerated device. We need to indicate to USBH that the device is gone + case ROOT_PORT_STATE_ENABLED: + // There is an enabled (active) device. We need to indicate to USBH that the device is gone pass_event_to_usbh = true; break; default: @@ -863,7 +833,10 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); if (pass_event_to_usbh) { - ESP_ERROR_CHECK(usbh_hub_dev_gone(p_hub_driver_obj->single_thread.root_dev_hdl)); + // The port must have a device object + assert(p_hub_driver_obj->single_thread.root_dev_uid != 0); + // We allow this to fail in case the device object was already freed + usbh_devs_remove(p_hub_driver_obj->single_thread.root_dev_uid); } break; } @@ -955,7 +928,6 @@ static void enum_handle_events(void) stage_pass = true; break; default: - // Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup. stage_pass = true; break; } @@ -977,18 +949,22 @@ static void enum_handle_events(void) // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- -esp_err_t hub_install(hub_config_t *hub_config) +esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) { HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE); HUB_DRIVER_EXIT_CRITICAL(); + esp_err_t ret; + // Allocate Hub driver object hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT); urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0); if (hub_driver_obj == NULL || enum_urb == NULL) { return ESP_ERR_NO_MEM; } - esp_err_t ret; + enum_urb->usb_host_client = (void *)hub_driver_obj; + enum_urb->transfer.callback = enum_transfer_callback; + // Install HCD port hcd_port_config_t port_config = { .fifo_bias = HUB_ROOT_HCD_PORT_FIFO_BIAS, @@ -1001,6 +977,7 @@ esp_err_t hub_install(hub_config_t *hub_config) if (ret != ESP_OK) { goto err; } + // Initialize Hub driver object hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; @@ -1020,6 +997,9 @@ esp_err_t hub_install(hub_config_t *hub_config) } p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); + + // Write-back client_ret pointer + *client_ret = (void *)hub_driver_obj; ret = ESP_OK; return ret; @@ -1081,31 +1061,31 @@ esp_err_t hub_root_stop(void) return ret; } -esp_err_t hub_dev_is_free(uint8_t dev_addr) +esp_err_t hub_port_recycle(unsigned int dev_uid) { - assert(dev_addr == ENUM_DEV_ADDR); - assert(p_hub_driver_obj->single_thread.root_dev_hdl); - // Device is free, we can now request its port be recycled - hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); - p_hub_driver_obj->single_thread.root_dev_hdl = NULL; + if (dev_uid == p_hub_driver_obj->single_thread.root_dev_uid) { + // Device is free, we can now request its port be recycled + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + p_hub_driver_obj->single_thread.root_dev_uid = 0; + HUB_DRIVER_ENTER_CRITICAL(); + // How the port is recycled will depend on the port's state + switch (port_state) { + case HCD_PORT_STATE_ENABLED: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; + break; + case HCD_PORT_STATE_RECOVERY: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; + break; + default: + abort(); // Should never occur + break; + } + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; + HUB_DRIVER_EXIT_CRITICAL(); - HUB_DRIVER_ENTER_CRITICAL(); - // How the port is recycled will depend on the port's state - switch (port_state) { - case HCD_PORT_STATE_ENABLED: - p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; - break; - case HCD_PORT_STATE_RECOVERY: - p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; - break; - default: - abort(); // Should never occur - break; + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; - HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); return ESP_OK; } diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index 24ec22b085..1fd3617307 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -418,22 +418,6 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps); */ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr); -/** - * @brief Update a pipe's callback - * - * This function is intended to be called on default pipes at the end of enumeration to switch to a callback that - * handles the completion of regular control transfer. - * - Pipe is not current processing a command - * - Pipe does not have any enqueued URBs - * - Port cannot be resetting - * - * @param pipe_hdl Pipe handle - * @param callback Callback - * @param user_arg Callback argument - * @return esp_err_t - */ -esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg); - /** * @brief Get the context variable of a pipe from its handle * diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index a096de8fd9..cf85ad861d 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -42,9 +42,10 @@ typedef struct { * - Initializes the HCD root port * * @param[in] hub_config Hub driver configuration + * @param[out] client_ret Unique pointer to identify the Hub as a USB Host client * @return esp_err_t */ -esp_err_t hub_install(hub_config_t *hub_config); +esp_err_t hub_install(hub_config_t *hub_config, void **client_ret); /** * @brief Uninstall Hub driver @@ -78,15 +79,16 @@ esp_err_t hub_root_start(void); esp_err_t hub_root_stop(void); /** - * @brief Indicate to the Hub driver that a device has been freed + * @brief Indicate to the Hub driver that a device's port can be recycled * - * Hub driver can now recover the port that the device was connected to + * The device connected to the port has been freed. The Hub driver can now + * recycled the port. * - * @param dev_addr Device address + * @param dev_uid Device's unique ID * @return * - ESP_OK: Success */ -esp_err_t hub_dev_is_free(uint8_t dev_addr); +esp_err_t hub_port_recycle(unsigned int dev_uid); /** * @brief Hub driver's processing function diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 6dee5e55b4..a1b5b2a845 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -58,7 +58,7 @@ typedef struct { usb_device_handle_t dev_hdl; } dev_gone_data; struct { - uint8_t dev_addr; + unsigned int dev_uid; } dev_free_data; }; } usbh_event_data_t; @@ -195,6 +195,32 @@ esp_err_t usbh_devs_num(int *num_devs_ret); */ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret); +/** + * @brief Create a device and add it to the device pool + * + * The created device will not be enumerated where the device's address is 0, + * device and config descriptor are NULL. The device will still have a default + * pipe, thus allowing control transfers to be submitted. + * + * - Call usbh_devs_open() before communicating with the device + * - Call usbh_dev_enum_lock() before enumerating the device via the various + * usbh_dev_set_...() functions. + * + * @param[in] uid Unique ID assigned to the device + * @param[in] dev_speed Device's speed + * @param[in] port_hdl Handle of the port that the device is connected to + * @return esp_err_t + */ +esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl); + +/** + * @brief Indicates to the USBH that a device is gone + * + * @param[in] uid Unique ID assigned to the device on creation (see 'usbh_devs_add()') + * @return esp_err_t + */ +esp_err_t usbh_devs_remove(unsigned int uid); + /** * @brief Mark that all devices should be freed at the next possible opportunity * @@ -227,6 +253,16 @@ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); */ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl); +/** + * @brief Trigger a USBH_EVENT_NEW_DEV event for the device + * + * This is typically called after a device has been fully enumerated. + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl); + // ------------------------------------------------ Device Functions --------------------------------------------------- // ----------------------- Getters ------------------------- @@ -245,7 +281,8 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr); /** * @brief Get a device's information * - * @note This function can block + * @note It is possible that the device has not been enumerated yet, thus some + * fields may be NULL. * @param[in] dev_hdl Device handle * @param[out] dev_info Device information * @return esp_err_t @@ -257,6 +294,8 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ * * - The device descriptor is cached when the device is created by the Hub driver * + * @note It is possible that the device has not been enumerated yet, thus the + * device descriptor could be NULL. * @param[in] dev_hdl Device handle * @param[out] dev_desc_ret Device descriptor * @return esp_err_t @@ -268,13 +307,109 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t * * Simply returns a reference to the internally cached configuration descriptor * - * @note This function can block + * @note It is possible that the device has not been enumerated yet, thus the + * configuration descriptor could be NULL. * @param[in] dev_hdl Device handle * @param config_desc_ret * @return esp_err_t */ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); +// ----------------------- Setters ------------------------- + +/** + * @brief Lock a device for enumeration + * + * - A device's enumeration lock must be set before any of its enumeration fields + * (e.g., address, device/config descriptors) can be set/updated. + * - The caller must be the sole opener of the device (see 'usbh_devs_open()') + * when locking the device for enumeration. + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl); + +/** + * @brief Release a device's enumeration lock + * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl); + +/** + * @brief Set the maximum packet size of EP0 for a device + * + * Typically called during enumeration after obtaining the first 8 bytes of the + * device's descriptor. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] wMaxPacketSize Maximum packet size + * @return esp_err_t + */ +esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketSize); + +/** + * @brief Set a device's address + * + * Typically called during enumeration after a SET_ADDRESSS request has be + * sent to the device. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] dev_addr + * @return esp_err_t + */ +esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); + +/** + * @brief Set a device's descriptor + * + * Typically called during enumeration after obtaining the device's descriptor + * via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] device_desc Device descriptor to copy + * @return esp_err_t + */ +esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); + +/** + * @brief Set a device's configuration descriptor + * + * Typically called during enumeration after obtaining the device's configuration + * descriptor via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] config_desc_full Configuration descriptor to copy + * @return esp_err_t + */ +esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); + +/** + * @brief Set a device's string descriptor + * + * Typically called during enumeration after obtaining one of the device's string + * descriptor via a GET_DESCRIPTOR request. + * + * @note The device's enumeration lock must be set before calling this function + * (see 'usbh_dev_enum_lock()') + * @param[in] dev_hdl Device handle + * @param[in] str_desc String descriptor to copy + * @param[in] select Select string descriptor. 0/1/2 for Manufacturer/Product/Serial + * Number string descriptors respectively + * @return esp_err_t + */ +esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); + // ----------------------------------------------- Endpoint Functions ------------------------------------------------- /** @@ -381,110 +516,6 @@ esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb); */ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret); -// -------------------------------------------------- Hub Functions ---------------------------------------------------- - -// ------------------- Device Related ---------------------- - -/** - * @brief Indicates to USBH the start of enumeration for a device - * - * - The Hub driver calls this function before it starts enumerating a new device. - * - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration - * functions. - * - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration. - * - * @note Hub Driver only - * @param[in] port_hdl Handle of the port that the device is connected to - * @param[in] dev_speed Device's speed - * @param[out] new_dev_hdl Device's handle - * @param[out] default_pipe_hdl Device's default pipe handle - * @return esp_err_t - */ -esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); - -/** - * @brief Indicates to the USBH that a device is gone - * - * @param dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl); - -// ----------------- Enumeration Related ------------------- - -/** - * @brief Assign the enumerating device's address - * - * @note Hub Driver only - * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param dev_addr - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); - -/** - * @brief Fill the enumerating device's descriptor - * - * @note Hub Driver only - * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param device_desc - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); - -/** - * @brief Fill the enumerating device's active configuration descriptor - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @param config_desc_full - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); - -/** - * @brief Fill one of the string descriptors of the enumerating device - * - * @note Hub Driver only - * @note Must call in sequence - * @param dev_hdl Device handle - * @param str_desc Pointer to string descriptor - * @param select Select which string descriptor. 0/1/2 for Manufacturer/Product/Serial Number string descriptors respectively - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); - -/** - * @brief Indicate the device enumeration is completed - * - * This will allow the device to be opened by clients, and also trigger a USBH_EVENT_NEW_DEV event. - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl); - -/** - * @brief Indicate that device enumeration has failed - * - * This will cause the enumerating device's resources to be cleaned up - * The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued. - * - * @note Hub Driver only - * @note Must call in sequence - * @note This function can block - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl); - #ifdef __cplusplus } #endif diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index b9b12dafcc..b2e5ed137a 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -148,6 +148,7 @@ typedef struct { SemaphoreHandle_t event_sem; SemaphoreHandle_t mux_lock; usb_phy_handle_t phy_handle; // Will be NULL if host library is installed with skip_phy_setup + void *hub_client; // Pointer to Hub driver (acting as a client). Used to reroute completed USBH control transfers } constant; } host_lib_t; @@ -171,8 +172,15 @@ static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr) { - assert(dev_addr != 0); - return (client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1))); + bool ret; + + if (dev_addr != 0) { + ret = client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1)); + } else { + ret = false; + } + + return ret; } static bool _unblock_client(client_t *client_obj, bool in_isr) @@ -268,14 +276,18 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) case USBH_EVENT_CTRL_XFER: { assert(event_data->ctrl_xfer_data.urb != NULL); assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL); - // Redistribute done control transfer to the clients that submitted them - client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; - - HOST_ENTER_CRITICAL(); - TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry); - client_obj->dynamic.num_done_ctrl_xfer++; - _unblock_client(client_obj, false); - HOST_EXIT_CRITICAL(); + // Redistribute completed control transfers to the clients that submitted them + if (event_data->ctrl_xfer_data.urb->usb_host_client == p_host_lib_obj->constant.hub_client) { + // Redistribute to Hub driver. Simply call the transfer callback + event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer); + } else { + client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; + HOST_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry); + client_obj->dynamic.num_done_ctrl_xfer++; + _unblock_client(client_obj, false); + HOST_EXIT_CRITICAL(); + } break; } case USBH_EVENT_NEW_DEV: { @@ -297,8 +309,8 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) break; } case USBH_EVENT_DEV_FREE: { - // Let the Hub driver know that the device is free - ESP_ERROR_CHECK(hub_dev_is_free(event_data->dev_free_data.dev_addr)); + // Let the Hub driver know that the device is free and its port can be recycled + ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.dev_uid)); break; } case USBH_EVENT_ALL_FREE: { @@ -420,7 +432,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config) .enum_filter_cb = config->enum_filter_cb, #endif // ENABLE_ENUM_FILTER_CALLBACK }; - ret = hub_install(&hub_config); + ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client); if (ret != ESP_OK) { goto hub_err; } diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 06d6f8d942..b402e72a99 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -57,7 +57,8 @@ struct device_s { uint32_t in_pending_list: 1; uint32_t is_gone: 1; // Device is gone (disconnected or port error) uint32_t waiting_free: 1; // Device object is awaiting to be freed - uint32_t reserved29: 29; + uint32_t enum_lock: 1; // Device is locked for enumeration. Enum information (e.g., address, device/config desc etc) may change + uint32_t reserved28: 28; }; uint32_t val; } flags; @@ -74,17 +75,22 @@ struct device_s { */ endpoint_t *endpoints[NUM_NON_DEFAULT_EP]; } mux_protected; - // Constant members do not change after device allocation and enumeration thus do not require a critical section + // Constant members do not require a critical section struct { + // Assigned on device allocation and remain constant for the device's lifetime hcd_pipe_handle_t default_pipe; hcd_port_handle_t port_hdl; - uint8_t address; usb_speed_t speed; - const usb_device_desc_t *desc; - const usb_config_desc_t *config_desc; - const usb_str_desc_t *str_desc_manu; - const usb_str_desc_t *str_desc_product; - const usb_str_desc_t *str_desc_ser_num; + unsigned int uid; + /* + These fields are can only be changed when enum_lock is set, thus can be treated as constant + */ + uint8_t address; + usb_device_desc_t *desc; + usb_config_desc_t *config_desc; + usb_str_desc_t *str_desc_manu; + usb_str_desc_t *str_desc_product; + usb_str_desc_t *str_desc_ser_num; } constant; }; @@ -143,6 +149,50 @@ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags); // ----------------------------------------------------- Helpers ------------------------------------------------------- +static device_t *_find_dev_from_uid(unsigned int uid) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + device_t *dev_iter; + + // Search the device lists for a device with the specified address + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.uid == uid) { + return dev_iter; + } + } + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.uid == uid) { + return dev_iter; + } + } + + return NULL; +} + +static device_t *_find_dev_from_addr(uint8_t dev_addr) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + device_t *dev_iter; + + // Search the device lists for a device with the specified address + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.address == dev_addr) { + return dev_iter; + } + } + TAILQ_FOREACH(dev_iter, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { + if (dev_iter->constant.address == dev_addr) { + return dev_iter; + } + } + + return NULL; +} + static inline bool check_ep_addr(uint8_t bEndpointAddress) { /* @@ -204,13 +254,21 @@ static bool urb_check_args(urb_t *urb) return true; } -static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in) +static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, unsigned int mps, bool is_in) { if (type == USB_TRANSFER_TYPE_CTRL) { // Check that num_bytes and wLength are set correctly usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer; - if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) { - ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes and usb_setup_packet_t wLength mismatch"); + bool mismatch = false; + if (is_in) { + // For IN transfers, 'num_bytes >= sizeof(usb_setup_packet_t) + setup_pkt->wLength' due to MPS rounding + mismatch = (transfer->num_bytes < sizeof(usb_setup_packet_t) + setup_pkt->wLength); + } else { + // For OUT transfers, num_bytes must match 'sizeof(usb_setup_packet_t) + setup_pkt->wLength' + mismatch = (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength); + } + if (mismatch) { + ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes %d and usb_setup_packet_t wLength %d mismatch", transfer->num_bytes, setup_pkt->wLength); return false; } } else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) { @@ -300,19 +358,21 @@ static void endpoint_free(endpoint_t *ep_obj) heap_caps_free(ep_obj); } -static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, device_t **dev_obj_ret) +static esp_err_t device_alloc(unsigned int uid, + usb_speed_t speed, + hcd_port_handle_t port_hdl, + device_t **dev_obj_ret) { - esp_err_t ret; device_t *dev_obj = heap_caps_calloc(1, sizeof(device_t), MALLOC_CAP_DEFAULT); - usb_device_desc_t *dev_desc = heap_caps_calloc(1, sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT); - if (dev_obj == NULL || dev_desc == NULL) { - ret = ESP_ERR_NO_MEM; - goto err; + if (dev_obj == NULL) { + return ESP_ERR_NO_MEM; } - // Allocate a pipe for EP0. We set the pipe callback to NULL for now + + esp_err_t ret; + // Allocate a pipe for EP0 hcd_pipe_config_t pipe_config = { - .callback = NULL, - .callback_arg = NULL, + .callback = ep0_pipe_callback, + .callback_arg = (void *)dev_obj, .context = (void *)dev_obj, .ep_desc = NULL, // No endpoint descriptor means we're allocating a pipe for EP0 .dev_speed = speed, @@ -327,15 +387,17 @@ static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, dev dev_obj->dynamic.state = USB_DEVICE_STATE_DEFAULT; dev_obj->constant.default_pipe = default_pipe_hdl; dev_obj->constant.port_hdl = port_hdl; - // Note: dev_obj->constant.address is assigned later during enumeration dev_obj->constant.speed = speed; - dev_obj->constant.desc = dev_desc; + dev_obj->constant.uid = uid; + // Note: Enumeration related dev_obj->constant fields are initialized later using usbh_dev_set_...() functions + + // Write-back device object *dev_obj_ret = dev_obj; ret = ESP_OK; + return ret; err: - heap_caps_free(dev_desc); heap_caps_free(dev_obj); return ret; } @@ -345,21 +407,24 @@ static void device_free(device_t *dev_obj) if (dev_obj == NULL) { return; } - // Configuration might not have been allocated (in case of early enumeration failure) - if (dev_obj->constant.config_desc) { - heap_caps_free((usb_config_desc_t *)dev_obj->constant.config_desc); + // Device descriptor might not have been set yet + if (dev_obj->constant.desc) { + heap_caps_free(dev_obj->constant.desc); } - // String descriptors might not have been allocated (in case of early enumeration failure) + // Configuration descriptor might not have been set yet + if (dev_obj->constant.config_desc) { + heap_caps_free(dev_obj->constant.config_desc); + } + // String descriptors might not have been set yet if (dev_obj->constant.str_desc_manu) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_manu); + heap_caps_free(dev_obj->constant.str_desc_manu); } if (dev_obj->constant.str_desc_product) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_product); + heap_caps_free(dev_obj->constant.str_desc_product); } if (dev_obj->constant.str_desc_ser_num) { - heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_ser_num); + heap_caps_free(dev_obj->constant.str_desc_ser_num); } - heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc); ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe)); heap_caps_free(dev_obj); } @@ -426,6 +491,9 @@ static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) { + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ if (action_flags == 0) { return false; } @@ -512,7 +580,7 @@ static inline void handle_prop_gone_evt(device_t *dev_obj) static inline void handle_free(device_t *dev_obj) { // Cache a copy of the device's address as we are about to free the device object - const uint8_t dev_addr = dev_obj->constant.address; + const unsigned int dev_uid = dev_obj->constant.uid; bool all_free; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); @@ -536,7 +604,7 @@ static inline void handle_free(device_t *dev_obj) usbh_event_data_t event_data = { .event = USBH_EVENT_DEV_FREE, .dev_free_data = { - .dev_addr = dev_addr, + .dev_uid = dev_uid, } }; p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); @@ -661,8 +729,6 @@ esp_err_t usbh_process(void) --------------------------------------------------------------------- */ USBH_EXIT_CRITICAL(); ESP_LOGD(USBH_TAG, "Processing actions 0x%"PRIx32"", action_flags); - // Sanity check. If the device is being freed, there must not be any other action flags set - assert(!(action_flags & DEV_ACTION_FREE) || action_flags == DEV_ACTION_FREE); if (action_flags & DEV_ACTION_EPn_HALT_FLUSH) { handle_epn_halt_flush(dev_obj); @@ -714,24 +780,42 @@ esp_err_t usbh_devs_num(int *num_devs_ret) esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret) { USBH_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG); - USBH_ENTER_CRITICAL(); int num_filled = 0; device_t *dev_obj; - // Fill list with devices from idle tailq + + USBH_ENTER_CRITICAL(); + /* + Fill list with devices from idle tailq and pending tailq. Only devices that + are fully enumerated are added to the list. Thus, the following devices are + not excluded: + - Devices with their enum_lock set + - Devices not in the configured state + - Devices with address 0 + */ TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { if (num_filled < list_len) { - dev_addr_list[num_filled] = dev_obj->constant.address; - num_filled++; + if (!dev_obj->dynamic.flags.enum_lock && + dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED && + dev_obj->constant.address != 0) { + dev_addr_list[num_filled] = dev_obj->constant.address; + num_filled++; + } } else { + // Address list is already full break; } } // Fill list with devices from pending tailq TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { if (num_filled < list_len) { - dev_addr_list[num_filled] = dev_obj->constant.address; - num_filled++; + if (!dev_obj->dynamic.flags.enum_lock && + dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED && + dev_obj->constant.address != 0) { + dev_addr_list[num_filled] = dev_obj->constant.address; + num_filled++; + } } else { + // Address list is already full break; } } @@ -741,6 +825,82 @@ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *nu return ESP_OK; } +esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl) +{ + USBH_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj; + + // Allocate a device object (initialized to address 0) + ret = device_alloc(uid, dev_speed, port_hdl, &dev_obj); + if (ret != ESP_OK) { + return ret; + } + + // We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + USBH_ENTER_CRITICAL(); + + // Check that there is not already a device with the same uid + if (_find_dev_from_uid(uid) != NULL) { + ret = ESP_ERR_INVALID_ARG; + goto exit; + } + // Check that there is not already a device currently with address 0 + if (_find_dev_from_addr(0) != NULL) { + ret = ESP_ERR_NOT_FINISHED; + goto exit; + } + // Add the device to the idle device list + TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); + p_usbh_obj->mux_protected.num_device++; + ret = ESP_OK; + +exit: + USBH_EXIT_CRITICAL(); + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + + return ret; +} + +esp_err_t usbh_devs_remove(unsigned int uid) +{ + esp_err_t ret; + device_t *dev_obj; + bool call_proc_req_cb = false; + + USBH_ENTER_CRITICAL(); + dev_obj = _find_dev_from_uid(uid); + if (dev_obj == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto exit; + } + // Mark the device as gone + dev_obj->dynamic.flags.is_gone = 1; + // Check if the device can be freed immediately + if (dev_obj->dynamic.open_count == 0) { + // Device is not currently opened at all. Can free immediately. + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); + } else { + // Device is still opened. Flush endpoints and propagate device gone event + call_proc_req_cb = _dev_set_actions(dev_obj, + DEV_ACTION_EPn_HALT_FLUSH | + DEV_ACTION_EP0_FLUSH | + DEV_ACTION_EP0_DEQUEUE | + DEV_ACTION_PROP_GONE_EVT); + } + ret = ESP_OK; +exit: + USBH_EXIT_CRITICAL(); + + // Call the processing request callback + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); + } + + return ret; +} + esp_err_t usbh_devs_mark_all_free(void) { USBH_ENTER_CRITICAL(); @@ -790,28 +950,17 @@ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) USBH_ENTER_CRITICAL(); // Go through the device lists to find the device with the specified address - device_t *found_dev_obj = NULL; - device_t *dev_obj; - TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) { - if (dev_obj->constant.address == dev_addr) { - found_dev_obj = dev_obj; - goto exit; - } - } - TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) { - if (dev_obj->constant.address == dev_addr) { - found_dev_obj = dev_obj; - goto exit; - } - } -exit: - if (found_dev_obj != NULL) { - // The device is not in a state to be opened - if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) { + device_t *dev_obj = _find_dev_from_addr(dev_addr); + if (dev_obj != NULL) { + // Check if the device is in a state to be opened + if (dev_obj->dynamic.flags.is_gone || // Device is already gone (disconnected) + dev_obj->dynamic.flags.waiting_free) { // Device is waiting to be freed ret = ESP_ERR_INVALID_STATE; + } else if (dev_obj->dynamic.flags.enum_lock) { // Device's enum_lock is set + ret = ESP_ERR_NOT_ALLOWED; } else { dev_obj->dynamic.open_count++; - *dev_hdl = (usb_device_handle_t)found_dev_obj; + *dev_hdl = (usb_device_handle_t)dev_obj; ret = ESP_OK; } } else { @@ -828,6 +977,8 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); + // Device should never be closed while its enum_lock is + USBH_CHECK_FROM_CRIT(!dev_obj->dynamic.flags.enum_lock, ESP_ERR_NOT_ALLOWED); dev_obj->dynamic.open_count--; bool call_proc_req_cb = false; if (dev_obj->dynamic.open_count == 0) { @@ -845,6 +996,26 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) if (call_proc_req_cb) { p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); } + + return ESP_OK; +} + +esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl) +{ + device_t *dev_obj = (device_t *)dev_hdl; + bool call_proc_req_cb = false; + + USBH_ENTER_CRITICAL(); + // Device must be in the configured state + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); + USBH_EXIT_CRITICAL(); + + // Call the processing request callback + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); + } + return ESP_OK; } @@ -870,28 +1041,26 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ USBH_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - esp_err_t ret; - // Device must be configured, or not attached (if it suddenly disconnected) - USBH_ENTER_CRITICAL(); - if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED)) { - USBH_EXIT_CRITICAL(); - ret = ESP_ERR_INVALID_STATE; - goto exit; - } - // Critical section for the dynamic members dev_info->speed = dev_obj->constant.speed; dev_info->dev_addr = dev_obj->constant.address; - dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; - USBH_EXIT_CRITICAL(); - assert(dev_obj->constant.config_desc); - dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue; - // String descriptors are allowed to be NULL as not all devices support them + // Device descriptor might not have been set yet + if (dev_obj->constant.desc) { + dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; + } else { + // Use the default pipe's MPS instead + dev_info->bMaxPacketSize0 = hcd_pipe_get_mps(dev_obj->constant.default_pipe); + } + // Configuration descriptor might not have been set yet + if (dev_obj->constant.config_desc) { + dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue; + } else { + dev_info->bConfigurationValue = 0; + } dev_info->str_desc_manufacturer = dev_obj->constant.str_desc_manu; dev_info->str_desc_product = dev_obj->constant.str_desc_product; dev_info->str_desc_serial_num = dev_obj->constant.str_desc_ser_num; - ret = ESP_OK; -exit: - return ret; + + return ESP_OK; } esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret) @@ -899,10 +1068,6 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t USBH_CHECK(dev_hdl != NULL && dev_desc_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - USBH_EXIT_CRITICAL(); - *dev_desc_ret = dev_obj->constant.desc; return ESP_OK; } @@ -912,19 +1077,256 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config USBH_CHECK(dev_hdl != NULL && config_desc_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; + *config_desc_ret = dev_obj->constant.config_desc; + + return ESP_OK; +} + +// ----------------------- Setters ------------------------- + +esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret; - // Device must be in the configured state - USBH_ENTER_CRITICAL(); - if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { - USBH_EXIT_CRITICAL(); + device_t *dev_obj = (device_t *)dev_hdl; + + // We need to take the mux_lock to access mux_protected members + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + + /* + The device's enum_lock can only be set when the following conditions are met: + - No other endpoints except EP0 have been allocated + - We are the sole opener + - Device's enum_lock is not already set + */ + // Check that no other endpoints except EP0 have been allocated + bool ep_found = false; + for (int i = 0; i < NUM_NON_DEFAULT_EP; i++) { + if (dev_obj->mux_protected.endpoints[i] != NULL) { + ep_found = true; + break; + } + } + if (ep_found) { ret = ESP_ERR_INVALID_STATE; goto exit; } + // Check that we are the sole opener and enum_lock is not already set + USBH_ENTER_CRITICAL(); + if (!dev_obj->dynamic.flags.enum_lock && (dev_obj->dynamic.open_count == 1)) { + dev_obj->dynamic.flags.enum_lock = true; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } USBH_EXIT_CRITICAL(); - assert(dev_obj->constant.config_desc); - *config_desc_ret = dev_obj->constant.config_desc; - ret = ESP_OK; + exit: + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + + return ret; +} + +esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's enum_lock must have been previously set + if (dev_obj->dynamic.flags.enum_lock) { + assert(dev_obj->dynamic.open_count == 1); // We must still be the sole opener + dev_obj->dynamic.flags.enum_lock = false; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketSize) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's EP0 MPS can only be updated when in the default state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_DEFAULT) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + // Device's enum_lock must be set before enumeration related data fields can be set + if (dev_obj->dynamic.flags.enum_lock) { + ret = hcd_pipe_update_mps(dev_obj->constant.default_pipe, wMaxPacketSize); + } else { + ret = ESP_ERR_NOT_ALLOWED; + } +exit: + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) +{ + USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + + USBH_ENTER_CRITICAL(); + // Device's address can only be set when in the default state + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_DEFAULT, ESP_ERR_INVALID_STATE); + // Device's enum_lock must be set before enumeration related data fields can be set + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.flags.enum_lock, ESP_ERR_NOT_ALLOWED); + // Update the device and default pipe's target address + ret = hcd_pipe_update_dev_addr(dev_obj->constant.default_pipe, dev_addr); + if (ret == ESP_OK) { + dev_obj->constant.address = dev_addr; + dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; + } + USBH_EXIT_CRITICAL(); + + return ret; +} + +esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc) +{ + USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_device_desc_t *new_desc, *old_desc; + + // Allocate and copy new device descriptor + new_desc = heap_caps_malloc(sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, device_desc, sizeof(usb_device_desc_t)); + + USBH_ENTER_CRITICAL(); + // Device's descriptor can only be set in the default or addressed state + if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_DEFAULT || dev_obj->dynamic.state == USB_DEVICE_STATE_ADDRESS)) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its device descriptor + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + old_desc = dev_obj->constant.desc; // Save old descriptor for cleanup + dev_obj->constant.desc = new_desc; // Assign new descriptor + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); + return ret; +} + +esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full) +{ + USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_config_desc_t *new_desc, *old_desc; + + // Allocate and copy new config descriptor + new_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, config_desc_full, config_desc_full->wTotalLength); + + USBH_ENTER_CRITICAL(); + // Device's config descriptor can only be set when in the addressed state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_ADDRESS) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its config descriptor + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + old_desc = dev_obj->constant.config_desc; // Save old descriptor for cleanup + dev_obj->constant.config_desc = new_desc; // Assign new descriptor + dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); + return ret; +} + +esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select) +{ + USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG); + esp_err_t ret; + device_t *dev_obj = (device_t *)dev_hdl; + usb_str_desc_t *new_desc, *old_desc; + + // Allocate and copy new string descriptor + new_desc = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT); + if (new_desc == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(new_desc, str_desc, str_desc->bLength); + + USBH_ENTER_CRITICAL(); + // Device's string descriptors can only be set when in the default state + if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { + ret = ESP_ERR_INVALID_STATE; + goto err; + } + // Device's enum_lock must be set before we can set its string descriptors + if (!dev_obj->dynamic.flags.enum_lock) { + ret = ESP_ERR_NOT_ALLOWED; + goto err; + } + // Assign to the selected descriptor + switch (select) { + case 0: + old_desc = dev_obj->constant.str_desc_manu; + dev_obj->constant.str_desc_manu = new_desc; + break; + case 1: + old_desc = dev_obj->constant.str_desc_product; + dev_obj->constant.str_desc_product = new_desc; + break; + default: // 2 + old_desc = dev_obj->constant.str_desc_ser_num; + dev_obj->constant.str_desc_ser_num = new_desc; + break; + } + USBH_EXIT_CRITICAL(); + + // Clean up old descriptor or failed assignment + heap_caps_free(old_desc); + ret = ESP_OK; + + return ret; + +err: + USBH_EXIT_CRITICAL(); + heap_caps_free(new_desc); return ret; } @@ -939,6 +1341,7 @@ esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config esp_err_t ret; device_t *dev_obj = (device_t *)dev_hdl; endpoint_t *ep_obj; + USBH_CHECK(dev_obj->constant.config_desc, ESP_ERR_INVALID_STATE); // Configuration descriptor must be set // Find the endpoint descriptor from the device's current configuration descriptor const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_address(dev_obj->constant.config_desc, ep_config->bInterfaceNumber, ep_config->bAlternateSetting, ep_config->bEndpointAddress, NULL); @@ -1065,10 +1468,11 @@ esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) device_t *dev_obj = (device_t *)dev_hdl; USBH_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG); bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; - USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG); + // Device descriptor could still be NULL at this point, so we get the MPS from the pipe instead. + unsigned int mps = hcd_pipe_get_mps(dev_obj->constant.default_pipe); + USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, mps, xfer_is_in), ESP_ERR_INVALID_ARG); USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); // Increment the control transfer count first dev_obj->dynamic.num_ctrl_xfers_inflight++; USBH_EXIT_CRITICAL(); @@ -1121,156 +1525,3 @@ esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret) *urb_ret = hcd_urb_dequeue(ep_obj->constant.pipe_hdl); return ESP_OK; } - -// -------------------------------------------------- Hub Functions ---------------------------------------------------- - -// ------------------- Device Related ---------------------- - -esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl) -{ - // Note: Parent device handle can be NULL if it's connected to the root hub - USBH_CHECK(new_dev_hdl != NULL, ESP_ERR_INVALID_ARG); - esp_err_t ret; - device_t *dev_obj; - ret = device_alloc(port_hdl, dev_speed, &dev_obj); - if (ret != ESP_OK) { - return ret; - } - // Write-back device handle - *new_dev_hdl = (usb_device_handle_t)dev_obj; - *default_pipe_hdl = dev_obj->constant.default_pipe; - ret = ESP_OK; - return ret; -} - -esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - bool call_proc_req_cb; - - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.flags.is_gone = 1; - // Check if the device can be freed immediately - if (dev_obj->dynamic.open_count == 0) { - // Device is not currently opened at all. Can free immediately. - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE); - } else { - // Device is still opened. Flush endpoints and propagate device gone event - call_proc_req_cb = _dev_set_actions(dev_obj, - DEV_ACTION_EPn_HALT_FLUSH | - DEV_ACTION_EP0_FLUSH | - DEV_ACTION_EP0_DEQUEUE | - DEV_ACTION_PROP_GONE_EVT); - } - USBH_EXIT_CRITICAL(); - - if (call_proc_req_cb) { - p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); - } - return ESP_OK; -} - -// ----------------- Enumeration Related ------------------- - -esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; - USBH_EXIT_CRITICAL(); - - // We can modify the info members outside the critical section - dev_obj->constant.address = dev_addr; - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc) -{ - USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // We can modify the info members outside the critical section - memcpy((usb_device_desc_t *)dev_obj->constant.desc, device_desc, sizeof(usb_device_desc_t)); - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full) -{ - USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // Allocate memory to store the configuration descriptor - usb_config_desc_t *config_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); // Buffer to copy over full configuration descriptor (wTotalLength) - if (config_desc == NULL) { - return ESP_ERR_NO_MEM; - } - // Copy the configuration descriptor - memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength); - // Assign the config desc to the device object - assert(dev_obj->constant.config_desc == NULL); - dev_obj->constant.config_desc = config_desc; - return ESP_OK; -} - -esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select) -{ - USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - // Allocate memory to store the manufacturer string descriptor - usb_str_desc_t *str_desc_fill = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT); - if (str_desc_fill == NULL) { - return ESP_ERR_NO_MEM; - } - // Copy the string descriptor - memcpy(str_desc_fill, str_desc, str_desc->bLength); - // Assign filled string descriptor to the device object - switch (select) { - case 0: - assert(dev_obj->constant.str_desc_manu == NULL); - dev_obj->constant.str_desc_manu = str_desc_fill; - break; - case 1: - assert(dev_obj->constant.str_desc_product == NULL); - dev_obj->constant.str_desc_product = str_desc_fill; - break; - default: // 2 - assert(dev_obj->constant.str_desc_ser_num == NULL); - dev_obj->constant.str_desc_ser_num = str_desc_fill; - break; - } - return ESP_OK; -} - -esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - // We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED; - // Add the device to list of devices, then trigger a device event - TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); // Add it to the idle device list first - bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); - USBH_EXIT_CRITICAL(); - p_usbh_obj->mux_protected.num_device++; - xSemaphoreGive(p_usbh_obj->constant.mux_lock); - - // Update the EP0's underlying pipe's callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, ep0_pipe_callback, (void *)dev_obj)); - // Call the processing request callback - if (call_proc_req_cb) { - p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); - } - return ESP_OK; -} - -esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - device_free(dev_obj); - return ESP_OK; -} From f0b26289d766885d41cffc95dcc7a3300740e6d9 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Wed, 17 Apr 2024 01:52:21 +0800 Subject: [PATCH 14/14] docs(usb): Add USBH maintainer notes --- docs/conf_common.py | 1 + .../usb_host/usb_host_notes_index.rst | 2 +- .../usb_host/usb_host_notes_usbh.rst | 115 ++++++++++++++++++ .../usb_host/usb_host_notes_arch.rst | 3 +- .../usb_host/usb_host_notes_index.rst | 2 +- .../usb_host/usb_host_notes_usbh.rst | 1 + 6 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 docs/en/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst create mode 100644 docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst diff --git a/docs/conf_common.py b/docs/conf_common.py index 9fdb396783..526e85b7f8 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -102,6 +102,7 @@ USB_DOCS = ['api-reference/peripherals/usb_device.rst', 'api-reference/peripherals/usb_host/usb_host_notes_design.rst', 'api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst', 'api-reference/peripherals/usb_host/usb_host_notes_index.rst', + 'api-reference/peripherals/usb_host/usb_host_notes_usbh.rst', 'api-guides/usb-otg-console.rst', 'api-guides/dfu.rst'] diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst index e419d832ca..a9c820044b 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -19,12 +19,12 @@ This document is split into the following sections: usb_host_notes_design usb_host_notes_arch usb_host_notes_dwc_otg + usb_host_notes_usbh Todo: - USB Host Maintainers Notes (HAL & LL) - USB Host Maintainers Notes (HCD) -- USB Host Maintainers Notes (USBH) - USB Host Maintainers Notes (Hub) - USB Host Maintainers Notes (USB Host Library) diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst new file mode 100644 index 0000000000..b1cd35fad9 --- /dev/null +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst @@ -0,0 +1,115 @@ +USB Host Driver (USBH) +====================== + +Introduction +------------ + +The USB Host Driver (henceforth referred to as USBH) provides a USB Host software interface which abstracts USB devices. The USBH interface provides APIs to... + +- manage the device pool (i.e., adding and removing devices) +- address and configure a device (i.e., setting device and configuration descriptors) +- submit transfers to a particular endpoint of a device + +Requirements +------------ + +USB Specification Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Chapter 10 of the USB 2.0 specification outlines some requirements of the USBH (referred to as USBD in the specification). The design of the USBH takes into consideration these requirements from the specification. + +- Default pipe of a device is owned by the USBH +- All other pipes are owned and managed by clients of the USBH +- USBH interface must provide the following services + + - Configuration and command mechanism + - Transfer services via both command and pipe mechanisms + - Event notification + - Status reporting and error recovery + +Host Stack Requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the USB 2.0 specification requirements, the USBH also takes into consideration the requirements set for the overall Host Stack (see :doc:`./usb_host_notes_design`): + +- USBH must not instantiate any tasks/threads +- USBH must be event driven, providing event callbacks and an event processing function + +Implementation & Usage +---------------------- + +Events & Processing +^^^^^^^^^^^^^^^^^^^ + +The USBH is completely event driven and all event handling is done via then ``usbh_process()`` function. The ``usbh_config_t.proc_req_cb`` callback provided on USBH installation will be called when processing is required. Typically, ``usbh_process()`` will be called from a dedicated thread/task. + +The USBH exposes the following event callbacks: + +- ``usbh_event_cb_t`` used to indicate various events regarding a particular device and control transfers to EP0. This callback is called from the context of ``usbh_process()`` +- ``usbh_ep_cb_t`` used to indicate events for all other endpoints. This callback is not called from the ``usbh_process()`` context (currently called from an HCD interrupt context). + +Device Pool +^^^^^^^^^^^ + +The USBH keeps track of all currently connected devices by internally maintaining a device pool (simply a linked list) where each device is represented by a device object. + +The USB 2.0 specification "assumes a specialized client of the USBD, called a hub driver, that acts as a clearinghouse for the addition and removal of devices from a particular hub". As a result, the USBH is completely reliant on an external client(s) (typically a Hub Driver) to inform the USBH of device addition and removal. The USBH provides the following APIs for device addition and removal: + +- ``usbh_devs_add()`` which will allocate a new device object and add it to the device pool. The newly added device will be unenumerated, meaning the device object will... + + - be assigned to address 0 + - have no device and configuration descriptor + +- ``usbh_devs_remove()`` which will indicate to the USBH that a device has been removed (such as due to a disconnection or a port error). + + - If the device is not currently opened (i.e., used by one or more clients), the USBH will free the underlying device object immediately. + - If the device is currently opened, a ``USBH_EVENT_DEV_GONE`` event will be propagated and the device will be flagged for removal. The last client to close the device will free the device object. + - When a device object is freed, a ``USBH_EVENT_DEV_FREE`` event will be propagated. This event is used to indicate that the device's upstream port can be recycled. + +Device Enumeration +^^^^^^^^^^^^^^^^^^ + +Newly added devices will need to be enumerated. The USBH provides various ``usbh_dev_set_...()`` functions to enumerate the device, such as assigning the device's address and setting device/configuration/string descriptors. Given that USBH devices can be shared by multiple clients, attempting to enumerate a device while another client has opened the device can cause issues. + +Thus, before calling any ``usbh_dev_set_...()`` enumeration function, a device must be locked for enumeration by calling ``usbh_dev_enum_lock()``. This prevents the device from being opened by any other client but the enumerating client. + +After enumeration is complete, the enumerating client can call ``usbh_devs_trigger_new_dev_event()`` to propagate a ``USBH_EVENT_NEW_DEV`` event. + +Device Usage +^^^^^^^^^^^^ + +Clients that want to use a device must open the device by calling ``usbh_devs_open()`` and providing the device's address. The device's address can either be obtained from a ``USBH_EVENT_NEW_DEV`` event or by calling ``usbh_devs_addr_list_fill()``. + +Opening a device will do the following: + +- Return a ``usb_device_handle_t`` device handle which can be used to refer to the device in various USBH functions +- Increment the device's internal ``open_count`` which indicates how many clients have opened the device. As long as ``open_count > 0``, the underlying device object will not be freed, thus guaranteeing that the device handle refers to a valid device object. + +Once a client no longer needs to use a device, the client should call ``usbh_devs_close()`` thus invalidating the device handle. + +.. note:: + + Most device related APIs accept ``usb_device_handle_t`` as an argument, which means that the calling client must have previously opened the device to obtain the device handle beforehand. This design choice is intentional in order to enforce an "open before use" pattern. + + However, a limited set of APIs (e.g., ``usbh_devs_remove()``) refer to devices using a Unique Identifier (``uid``) which is assigned on device addition (see ``usbh_devs_add()``). The use of ``uid`` in these functions allows their callers to refer to a device **without needing to open it** due to the lack of a ``usb_device_handle_t``. + + As a result, it is possible that a caller of a ``uid`` function may refer to a device that has already been freed. Thus, callers should account for a fact that these functions may return :c:macro:`ESP_ERR_NOT_FOUND`. + +Endpoints & Transfers +^^^^^^^^^^^^^^^^^^^^^ + +USBH supports transfer to default (i.e., EP0) and non-default endpoints. + +For non-default endpoints: + +- A client must first allocate the endpoint by calling ``usbh_ep_alloc()`` which assigns a ``usbh_ep_cb_t`` callback and returns a ``usbh_ep_handle_t`` endpoint handle so that the endpoint can be referred to. +- A client can then enqueue a ``urb_t`` transfer to the endpoint by calling ``usbh_ep_enqueue_urb()``. +- The ``usbh_ep_cb_t`` callback is called to indicate transfer completion +- The client must then dequeue the transfer using ``usbh_ep_dequeue_urb()`` + +Default endpoints are owned and managed by the USBH, thus API for control transfers are different: + +- EP0 is always allocated for each device, thus clients do no need to allocate EP0 or provide an endpoint callback. +- Clients call should call ``usbh_dev_submit_ctrl_urb()`` to submit a control transfer to a device's EP0. +- A ``USBH_EVENT_CTRL_XFER`` event will be propagated when the transfer is complete +- Control transfers do not need to be dequeued diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_arch.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_arch.rst index d01d02eba4..3670348e5a 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_arch.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_arch.rst @@ -1,2 +1 @@ -.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_arch - .rst \ No newline at end of file +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_arch.rst diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 4423f1fe2d..09d410debf 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -1 +1 @@ -.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_index.rst \ No newline at end of file +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_index.rst diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst new file mode 100644 index 0000000000..262547c0f0 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst @@ -0,0 +1 @@ +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_usbh.rst