From 52aa6a1d4c312f053c69d69e9d59828ca46d483e Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 18 Jun 2024 12:06:16 +0200 Subject: [PATCH 1/5] feat(ext_port): Added External Port driver Closes https://github.com/espressif/esp-idf/issues/12554 --- Kconfig | 2 + components/usb/CMakeLists.txt | 3 +- components/usb/Kconfig | 45 + components/usb/ext_hub.c | 29 +- components/usb/ext_port.c | 1398 +++++++++++++++++++ components/usb/hub.c | 86 +- components/usb/include/usb/usb_types_ch11.h | 22 + components/usb/private_include/ext_hub.h | 8 +- components/usb/private_include/ext_port.h | 143 ++ components/usb/private_include/hub.h | 15 +- components/usb/usbh.c | 1 - 11 files changed, 1723 insertions(+), 29 deletions(-) create mode 100644 components/usb/ext_port.c create mode 100644 components/usb/private_include/ext_port.h diff --git a/Kconfig b/Kconfig index b20e3b7318..23715a670d 100644 --- a/Kconfig +++ b/Kconfig @@ -655,3 +655,5 @@ mainmenu "Espressif IoT Development Framework Configuration" - CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH - CONFIG_ESP_WIFI_EAP_TLS1_3 - CONFIG_ESP_WIFI_ENABLE_ROAMING_APP + - CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS + - CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 9f678163cd..fd3210c8ff 100644 --- a/components/usb/CMakeLists.txt +++ b/components/usb/CMakeLists.txt @@ -30,7 +30,8 @@ if(CONFIG_SOC_USB_OTG_SUPPORTED) endif() if(CONFIG_USB_HOST_HUBS_SUPPORTED) - list(APPEND srcs "ext_hub.c") + list(APPEND srcs "ext_hub.c" + "ext_port.c") endif() idf_component_register(SRCS ${srcs} diff --git a/components/usb/Kconfig b/components/usb/Kconfig index 1c333f870b..a527613c7f 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -112,6 +112,51 @@ menu "USB-OTG" help Enables support for connecting multiple Hubs simultaneously. + menu "Downstream Port configuration" + depends on USB_HOST_HUBS_SUPPORTED + + config USB_HOST_EXT_PORT_SUPPORT_LS + depends on IDF_EXPERIMENTAL_FEATURES + bool "Support LS" + default n + help + Enables support of Low-speed devices, connected through the external Hub. + + config USB_HOST_EXT_PORT_RESET_ATTEMPTS + depends on IDF_EXPERIMENTAL_FEATURES + # Invisible config option + # Todo: IDF-11283 + int + default 1 + help + Amount of attempts to reset the device. + + The default value is 1. + + config USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE + bool "Custom bPwrOn2PwrGood value" + default n + help + Enables the possibility to configure custom time for the power-on sequence on a port + until power is good on that port. + + When enabled, applies the custom PwrOn2PwrGood delay. + When disabled, applies the PwrOn2PwrGood value from the Hub Descriptor. + + config USB_HOST_EXT_PORT_CUSTOM_RESET_MS + depends on USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE + int "PwrOn2PwrGood delay in ms" + default 100 + range 0 5000 + help + Custom value of delay from the time the power-on sequence begins on a port + until power is good on that port. + Value 0 is used for a hub with no power switches. + + The default value is 100 ms. + + endmenu #Downstream Port configuration + endmenu #Hub Driver Configuration config USB_HOST_ENABLE_ENUM_FILTER_CALLBACK diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c index fb20242f70..149993070c 100644 --- a/components/usb/ext_hub.c +++ b/components/usb/ext_hub.c @@ -5,18 +5,17 @@ */ #include #include +#include "sdkconfig.h" #include "esp_err.h" #include "esp_log.h" #include "esp_heap_caps.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" #include "usb_private.h" #include "ext_hub.h" +#include "ext_port.h" #include "usb/usb_helpers.h" -typedef struct ext_port_s *ext_port_hdl_t; /* This will be implemented during ext_port driver implementation */ - #define EXT_HUB_MAX_STATUS_BYTES_SIZE (sizeof(uint32_t)) #define EXT_HUB_STATUS_CHANGE_FLAG (1 << 0) #define EXT_HUB_STATUS_PORT1_CHANGE_FLAG (1 << 1) @@ -396,8 +395,15 @@ static void device_error(ext_hub_dev_t *ext_hub_dev) static esp_err_t device_port_new(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx) { + ext_port_config_t port_config = { + .ext_hub_hdl = (ext_hub_handle_t) ext_hub_dev, + .parent_dev_hdl = ext_hub_dev->constant.dev_hdl, + .parent_port_num = port_idx + 1, + .port_power_delay_ms = ext_hub_dev->constant.hub_desc->bPwrOn2PwrGood * 2, + }; + assert(p_ext_hub_driver->constant.port_driver); - esp_err_t ret = p_ext_hub_driver->constant.port_driver->new (NULL, (void**) &ext_hub_dev->constant.ports[port_idx]); + esp_err_t ret = p_ext_hub_driver->constant.port_driver->new (&port_config, (void**) &ext_hub_dev->constant.ports[port_idx]); if (ret != ESP_OK) { ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port allocation error: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret)); goto fail; @@ -418,7 +424,7 @@ static esp_err_t device_port_free(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx) assert(ext_hub_dev->single_thread.maxchild != 0); assert(p_ext_hub_driver->constant.port_driver); - esp_err_t ret = p_ext_hub_driver->constant.port_driver->free(ext_hub_dev->constant.ports[port_idx]); + esp_err_t ret = p_ext_hub_driver->constant.port_driver->del(ext_hub_dev->constant.ports[port_idx]); if (ret != ESP_OK) { ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to free port: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret)); @@ -1038,6 +1044,7 @@ static void handle_device(ext_hub_dev_t *ext_hub_dev) // FSM for external Hub switch (ext_hub_dev->single_thread.stage) { case EXT_HUB_STAGE_IDLE: + stage_pass = true; break; case EXT_HUB_STAGE_GET_DEVICE_STATUS: case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: @@ -1118,8 +1125,7 @@ esp_err_t ext_hub_install(const ext_hub_config_t *config) { esp_err_t ret; ext_hub_driver_t *ext_hub_drv = heap_caps_calloc(1, sizeof(ext_hub_driver_t), MALLOC_CAP_DEFAULT); - SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); - if (ext_hub_drv == NULL || mux_lock == NULL) { + if (ext_hub_drv == NULL) { ret = ESP_ERR_NO_MEM; goto err; } @@ -1151,9 +1157,6 @@ esp_err_t ext_hub_install(const ext_hub_config_t *config) return ESP_OK; err: - if (mux_lock != NULL) { - vSemaphoreDelete(mux_lock); - } heap_caps_free(ext_hub_drv); return ret; } @@ -1672,7 +1675,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature) { EXT_HUB_ENTER_CRITICAL(); - EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); esp_err_t ret; @@ -1701,7 +1704,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature) { EXT_HUB_ENTER_CRITICAL(); - EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); esp_err_t ret; @@ -1730,7 +1733,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_ esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) { EXT_HUB_ENTER_CRITICAL(); - EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); esp_err_t ret; diff --git a/components/usb/ext_port.c b/components/usb/ext_port.c new file mode 100644 index 0000000000..541eb60a50 --- /dev/null +++ b/components/usb/ext_port.c @@ -0,0 +1,1398 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "usb_private.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" +#include "usbh.h" +#include "ext_port.h" +#include "usb/usb_helpers.h" + +// Amount of attempts to reset the port with a device in case of a failure +#if (CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS) +#define EXT_PORT_RESET_ATTEMPTS CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS +#else +#define EXT_PORT_RESET_ATTEMPTS 1 +#endif +// Delay in ms after sending the SetFeature() class specific request +#define EXT_PORT_RESET_CUSTOM_DELAY CONFIG_USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE +#define EXT_PORT_RESET_CUSTOM_DELAY_MS CONFIG_USB_HOST_EXT_PORT_CUSTOM_RESET_MS +#define EXT_PORT_RESET_DEFAULT_DELAY_MS 100 + +/** + * @brief External Port driver action flags + */ +typedef enum { + PORT_ACTION_HANDLE = (1 << 0), /**< Port requires handling */ + PORT_ACTION_DISABLE = (1 << 1), /**< Disable port */ + PORT_ACTION_RECYCLE = (1 << 2), /**< Recycle port */ + PORT_ACTION_RESET = (1 << 3), /**< Reset port */ + PORT_ACTION_GET_STATUS = (1 << 4), /**< Get status request */ +} port_action_t; + +/** + * @brief State of the device, attached to the port + */ +typedef enum { + PORT_DEV_NOT_PRESENT = 0, + PORT_DEV_PRESENT +} port_dev_state_t; + +/** + * @brief Object representing a single External Port + */ +struct ext_port_s { + /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */ + TAILQ_ENTRY(ext_port_s) tailq_entry; + union { + struct { + uint32_t in_pending_list: 1; /**< Port in pending list */ + uint32_t status_lock: 1; /**< Ports' status lock. Parent Hub processes receiving new status */ + uint32_t status_outdated: 1; /**< Ports' has changed and status is not valid. Get port status is required */ + uint32_t is_gone: 1; /**< Ports' parent Hub is gone */ + uint32_t has_enum_device: 1; /**< Port has an enumerated device */ + uint32_t waiting_recycle: 1; /**< Port is waiting to be recycled */ + uint32_t waiting_free: 1; /**< Port is waiting to be freed */ + uint32_t reserved25: 25; /**< Reserved */ + }; + uint32_t val; /**< Ports' flags value */ + } flags; /**< Ports' flags */ + uint32_t action_flags; /**< Ports' action flags */ + usb_hub_port_state_t state; /**< Ports' state */ + usb_port_status_t status; /**< Ports' status data */ + port_dev_state_t dev_state; /**< Ports' device state */ + uint8_t dev_reset_attempts; /**< Ports' device reset failure */ + + struct { + // Ports' parent info for optimisation and better debug output + usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ + uint8_t parent_dev_addr; /**< Ports' parent device bus address */ + // Port related constant members + ext_hub_handle_t ext_hub_hdl; /**< Ports' parent External Hub handle */ + uint8_t port_num; /**< Ports' parent External Hub Port number */ + int reset_delay_ms; /**< Ports' Power on time to Power Good, ms */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +}; + +/** + * @brief Definition of the type for the struct, representing a single External Port + */ +typedef struct ext_port_s ext_port_t; + +/** + * @brief Object representing the External Port Driver + */ +typedef struct { + struct { + TAILQ_HEAD(ext_ports, ext_port_s) pending_tailq; /**< External Ports require handling */ + } single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */ + + struct { + ext_port_cb_t proc_req_cb; /**< External Port process callback */ + void *proc_req_cb_arg; /**< External Port process callback argument */ + ext_port_event_cb_t event_cb; /**< External Port event callback */ + void *event_cb_arg; /**< External Port event callback argument */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +} ext_port_driver_t; + +static ext_port_driver_t *p_ext_port_driver = NULL; + +const char *EXT_PORT_TAG = "EXT_PORT"; + +// ----------------------------------------------------------------------------- +// --------------------------- Helpers & Macros -------------------------------- +// ----------------------------------------------------------------------------- + +#define EXT_PORT_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +/** + * @brief Port connection status + * + * @param[in] ext_port Port object + * @return + * - true Port has a connection + * - false Port doesn't have a connection + */ +static inline bool port_has_connection(ext_port_t *ext_port) +{ + return ext_port->status.wPortStatus.PORT_CONNECTION ? true : false; +} + +/** + * @brief Port power status + * + * @param[in] ext_port Port object + * @return + * - true Port is powered + * - false Port in not powered + */ +static inline bool port_is_powered(ext_port_t *ext_port) +{ + return ext_port->status.wPortStatus.PORT_POWER ? true : false; +} + +/** + * @brief Port reset status + * + * @param[in] ext_port Port object + * @return + * - true Port is in reset + * - false Port is not in reset + */ +static inline bool port_is_in_reset(ext_port_t *ext_port) +{ + return ext_port->status.wPortStatus.PORT_RESET ? true : false; +} + +/** + * @brief Port enable status + * + * @param[in] ext_port Port object + * @return + * - true Port is enabled + * - false Port is disabled + */ +static inline bool port_is_enabled(ext_port_t *ext_port) +{ + return ext_port->status.wPortStatus.PORT_ENABLE ? true : false; +} + +/** + * @brief Port connection status changed + * + * @param[in] ext_port Port object + * @return + * - true Port connection changed + * - false Port connection not changed + */ +static inline bool port_has_changed_connection(ext_port_t * ext_port) +{ + return ext_port->status.wPortChange.C_PORT_CONNECTION ? true : false; +} + +/** + * @brief Port enabled status changed + * + * @param[in] ext_port Port object + * @return + * - true Port has error state and was disabled + * - false Port operates normally + */ +static inline bool port_has_changed_from_enable(ext_port_t *ext_port) +{ + return ext_port->status.wPortChange.C_PORT_ENABLE ? true : false; +} + +/** + * @brief Port reset status changed + * + * @param[in] ext_port Port object + * @return + * - true Port has finished the reset + * - false Port hasn't finished the reset + */ +static inline bool port_has_finished_reset(ext_port_t *ext_port) +{ + return ext_port->status.wPortChange.C_PORT_RESET ? true : false; +} + +// ----------------------------------------------------------------------------- +// ------------------------ Parent Hub related logic --------------------------- +// ----------------------------------------------------------------------------- + +/** + * @brief Request the port status for the port object + * + * @note This call uses the External Hub Driver API + * + * @param[in] ext_port Port object + * @return + * - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed + * - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL + * - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort] + * - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured + * - ESP_OK: Status has been requested + */ +static esp_err_t port_request_status(ext_port_t* ext_port) +{ + esp_err_t ret = ext_hub_get_port_status(ext_port->constant.ext_hub_hdl, ext_port->constant.port_num); + if (ret != ESP_OK) { + return ret; + } + // Port is requesting status, lock the status + ext_port->flags.status_lock = 1; + return ret; +} + +/** + * @brief Sets the feature to the port + * + * @note This call uses the External Hub Driver API + * + * @param[in] ext_port Port object + * @param[in] feature Port feature to set + * @return + * - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed + * - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL + * - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort] + * - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured + * - ESP_OK: SetPortFeature() has been requested + */ +static esp_err_t port_set_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature) +{ + esp_err_t ret = ext_hub_set_port_feature(ext_port->constant.ext_hub_hdl, + ext_port->constant.port_num, + feature); + if (ret != ESP_OK) { + return ret; + } + // Every set feature requires status update + ext_port->flags.status_outdated = 1; + // PowerOn to PowerGood delay for port + if (feature == USB_FEATURE_PORT_RESET) { + vTaskDelay(pdMS_TO_TICKS(ext_port->constant.reset_delay_ms)); + } + return ret; +} + +/** + * @brief Clears the feature to the port + * + * @note This call uses the External Hub Driver API + * + * @param[in] ext_port Port object + * @param[in] feature Port feature to set + * @return + * - ESP_ERR_NOT_ALLOWED: The External Hub Driver has not been installed + * - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL + * - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort] + * - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured + * - ESP_OK: ClearPortFeature() has been requested + */ +static esp_err_t port_clear_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature) +{ + esp_err_t ret = ext_hub_clear_port_feature(ext_port->constant.ext_hub_hdl, + ext_port->constant.port_num, + feature); + if (ret != ESP_OK) { + return ret; + } + // Every clearing feature requires status update + ext_port->flags.status_outdated = 1; + return ret; +} + +// ----------------------------------------------------------------------------- +// ---------------------------- Internal logic --------------------------------- +// ----------------------------------------------------------------------------- +/** + * @brief Port set actions + * + * @note + * - The External Port Driver is a single threaded driver + * - All calls are made from USB Host related task + * - Does not need critical section + * - Always requires processing + * + * @param[in] ext_port Port object + * @param[in] action_flags Action flags (could not be 0) + */ +static void port_set_actions(ext_port_t *ext_port, uint32_t action_flags) +{ + assert(action_flags != 0); // Sanity check + + // Check if port is not in the pending list + if (!ext_port->flags.in_pending_list) { + // Add port to pending list + ext_port->flags.in_pending_list = 1; + TAILQ_INSERT_TAIL(&p_ext_port_driver->single_thread.pending_tailq, ext_port, tailq_entry); + } + + ext_port->action_flags |= action_flags; + p_ext_port_driver->constant.proc_req_cb(p_ext_port_driver->constant.proc_req_cb_arg); +} + +/** + * @brief Stops the handling and remove the port from the pending list + * + * When the ports parent hub device has been removed, the port could be freed immediately + * if it doesn't have an inserted device. + * For this purpose port object should be removed from the pending + * list and the action flags should be dropped. + * + * @note + * - The External Port Driver is a single threaded driver + * - All calls are made from USB Host related task + * - Does not need critical section + * - Does not require processing + * + * @param[in] ext_port Port object + */ +static void port_stop_handling(ext_port_t *ext_port) +{ + // Check if port is in the pending list + if (ext_port->flags.in_pending_list) { + // Add port to pending list + ext_port->flags.in_pending_list = 0; + TAILQ_REMOVE(&p_ext_port_driver->single_thread.pending_tailq, ext_port, tailq_entry); + } + if (ext_port->action_flags) { + // Port should be freed but has actions + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port stop handling. Dropped actions 0x%"PRIx32"", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->action_flags); + } + ext_port->action_flags = 0; +} + +/** + * @brief Port event propagation + * + * Propagating the following events to the Hub Driver: + * - EXT_PORT_CONNECTED + * - EXT_PORT_RESET_COMPLETED + * - EXT_PORT_DISCONNECTED + * + * Filling all the event data parameters, according to the event. + * + * @param[in] ext_port Port object + * @param[in] event Port event + */ +static void port_event(ext_port_t *ext_port, ext_port_event_t event) +{ + ext_port_event_data_t event_data = { + .event = event, + }; + switch (event) { + case EXT_PORT_CONNECTED: + event_data.connected.ext_hub_hdl = ext_port->constant.ext_hub_hdl; + event_data.connected.parent_dev_hdl = ext_port->constant.parent_dev_hdl; + event_data.connected.parent_port_num = ext_port->constant.port_num; + break; + case EXT_PORT_RESET_COMPLETED: + event_data.reset_completed.parent_dev_hdl = ext_port->constant.parent_dev_hdl; + event_data.reset_completed.parent_port_num = ext_port->constant.port_num; + break; + case EXT_PORT_DISCONNECTED: + event_data.disconnected.parent_dev_hdl = ext_port->constant.parent_dev_hdl; + event_data.disconnected.parent_port_num = ext_port->constant.port_num; + break; + default: + // Should never occur + abort(); + break; + } + + p_ext_port_driver->constant.event_cb(&event_data, p_ext_port_driver->constant.event_cb_arg); +} + +/** + * @brief Returns first port from the pending list + * + * @return + * - Port object pointer. NULL if no ports in pending list. + */ +static ext_port_t *get_port_from_pending_list(void) +{ + ext_port_t *ext_port = NULL; + if (!TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq)) { + ext_port = TAILQ_FIRST(&p_ext_port_driver->single_thread.pending_tailq); + } + return ext_port; +} + +/** + * @brief Allocates port object + * + * Allocates new por object with following parameters: + * - Port state: USB_PORT_STATE_NOT_CONFIGURED + * - Port device status: PORT_DEV_NOT_PRESENT + * + * @param[in] ext_hub_hdl Ports' parent hub handle + * @param[in] parent_dev_hdl Ports' parent device handle + * @param[in] parent_port_num Ports' parent port number + * @param[in] port_delay_ms Ports' Power on time to Power Good, ms + * @param[out] port_obj Port objects' pointer + * @return + * - ESP_ERR_INVALID_ARG: Unable to allocate the port object: parent hub handle and parent device handle must be not NULL + * - ESP_ERR_NO_MEM: Unable to allocate the port object: no memory + * - ESP_ERR_NOT_FINISHED: Unable to allocate the port object: parent device is not available + * - ESP_OK: Port object created successfully + */ +static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, uint16_t port_delay_ms, ext_port_t **port_obj) +{ + uint8_t parent_dev_addr = 0; + EXT_PORT_CHECK(ext_hub_hdl != NULL && parent_dev_hdl != NULL, ESP_ERR_INVALID_ARG); + // This is the one exception from the requirement to use only the Ext Hub Driver API. + // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH + EXT_PORT_CHECK(usbh_dev_get_addr(parent_dev_hdl, &parent_dev_addr) == ESP_OK, ESP_ERR_NOT_FINISHED); + + ext_port_t *ext_port = heap_caps_calloc(1, sizeof(ext_port_t), MALLOC_CAP_DEFAULT); + if (ext_port == NULL) { + return ESP_ERR_NO_MEM; + } + + ext_port->constant.parent_dev_hdl = parent_dev_hdl; + ext_port->constant.parent_dev_addr = parent_dev_addr; + ext_port->constant.ext_hub_hdl = ext_hub_hdl; + ext_port->constant.port_num = parent_port_num; +#if (EXT_PORT_RESET_CUSTOM_DELAY) + ext_port->constant.reset_delay_ms = EXT_PORT_RESET_CUSTOM_DELAY_MS; +#else + ext_port->constant.reset_delay_ms = (port_delay_ms == 0) + ? EXT_PORT_RESET_DEFAULT_DELAY_MS + : port_delay_ms; +#endif // EXT_PORT_POWER_ON_CUSTOM + + ext_port->state = USB_PORT_STATE_NOT_CONFIGURED; + ext_port->dev_state = PORT_DEV_NOT_PRESENT; + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port has been added (PwrOn2PwrGood=%d ms)", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->constant.reset_delay_ms); + + *port_obj = ext_port; + return ESP_OK; +} + +/** + * @brief Port object handling complete + * + * @note This is the final stage of port object handling. + * After port being handled, the port object: + * - is removed from the pending list + * - is freed, is the port gone or port object waiting to be freed + * + * The final stage is parent notification about port object handling completion to enable the EP IN + * + * @param[in] ext_port Port object + * @return + * - ESP_OK: Port object handle completed + */ +static esp_err_t handle_complete(ext_port_t *ext_port) +{ + bool all_ports_were_handled = true; + bool has_pending_ports = false; + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port is %s", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->flags.is_gone ? "gone" : + (ext_port->dev_state == PORT_DEV_PRESENT) ? "active" : "idle"); + + assert(ext_port->flags.is_gone == 0); // Handle complete could be called only on port that is still available + assert(ext_port->flags.waiting_free == 0); // Handle completion could be called only on port that should be freed + assert(ext_port->action_flags == 0); // Port should not have any further actions + assert(ext_port->flags.waiting_recycle == 0); // Port should not await to be recycled + assert(ext_port->flags.in_pending_list == 1); // Port should be in pending list + + // Remove port from it + port_stop_handling(ext_port); + + // Verify, that list has more ports in pending list + if (!TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq)) { + has_pending_ports = true; + } + + // Port handling complete +#if ENABLE_MULTIPLE_HUBS + // When multiply Hubs are attached, we can have ports in pending list, but for another parent + ext_port_t *port = NULL; + TAILQ_FOREACH(port, &p_ext_port_driver->single_thread.pending_tailq, tailq_entry) { + if (port->constant.ext_hub_hdl == ext_port->constant.ext_hub_hdl) { + // Port with same parent has been found + all_ports_were_handled = false; + break; + } + } +#else + // When only one hub is supported - all ports have the same parent + // all_port_were_handled should be triggered if no more ports in pending list + all_ports_were_handled = !has_pending_ports; +#endif // + + if (all_ports_were_handled) { + // Notify parent to enable Interrupt EP + ext_hub_status_handle_complete(ext_port->constant.ext_hub_hdl); + } + + if (has_pending_ports) { + // Re-trigger the processing if there are more ports in pending list + p_ext_port_driver->constant.proc_req_cb(p_ext_port_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +/** + * @brief Handles port status + * + * @param[in] ext_port Port object + * @return + * - true: Port has a status, that requires processing + * - false: Port has a status, that doesn't require processing + */ +static bool handle_port_status(ext_port_t *ext_port) +{ + bool need_processing = false; + if (port_is_in_reset(ext_port)) { + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port still in reset, wait and repeat get status...", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + // PowerOn to PowerGood delay for port + vTaskDelay(pdMS_TO_TICKS(ext_port->constant.reset_delay_ms)); + port_request_status(ext_port); + need_processing = true; + } + + return need_processing; +} + +/** + * @brief Handles port connection + * + * @param[in] ext_port Port object + */ +static void handle_port_connection(ext_port_t *ext_port) +{ + bool has_device = false; + + switch (ext_port->state) { + case USB_PORT_STATE_POWERED_OFF: + if (!port_is_powered(ext_port)) { + ext_port->state = USB_PORT_STATE_DISCONNECTED; + port_set_feature(ext_port, USB_FEATURE_PORT_POWER); + } + break; + case USB_PORT_STATE_DISCONNECTED: + case USB_PORT_STATE_DISABLED: + if (port_has_connection(ext_port)) { + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Mismatch port state and device status", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + has_device = true; + } else { + // New device connected, flush reset attempts + ext_port->dev_reset_attempts = 0; + ext_port->state = USB_PORT_STATE_RESETTING; + // New device has not been enumerated yet, reset the flag + ext_port->flags.has_enum_device = 0; + } + } + break; + case USB_PORT_STATE_RESETTING: + if (!port_has_connection(ext_port)) { + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Failed to issue downstream port reset", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + has_device = true; + } else { + ext_port->state = USB_PORT_STATE_DISCONNECTED; + } + } + break; + case USB_PORT_STATE_ENABLED: + // TODO: IDF-10071 Port debounce mechanism + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ext_port->flags.waiting_recycle = 1; + } + break; + + default: + // Should never occur + abort(); + break; + } + + if (has_device) { + ext_port->flags.waiting_recycle = 1; + ext_port->dev_state = PORT_DEV_NOT_PRESENT; + port_event(ext_port, EXT_PORT_DISCONNECTED); + } +} + +/** + * @brief Handles port changes + * + * @param[in] ext_port Port object + * @return + * - true: Port has a change, that requires processing + * - false: Port has no changes and doesn't require processing + */ +static bool handle_port_changes(ext_port_t *ext_port) +{ + bool need_processing = false; + if (port_has_changed_connection(ext_port)) { + handle_port_connection(ext_port); + port_clear_feature(ext_port, USB_FEATURE_C_PORT_CONNECTION); + need_processing = true; + } else if (port_has_changed_from_enable(ext_port)) { + // For more information, refer to section 11.8.1 Port Error of usb_2.0 specification + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Error on port (state=%d, dev=%d)", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->state, + ext_port->dev_state == PORT_DEV_PRESENT); + port_clear_feature(ext_port, USB_FEATURE_C_PORT_ENABLE); + need_processing = true; + } else if (port_has_finished_reset(ext_port)) { + if (port_has_connection(ext_port)) { + ext_port->state = USB_PORT_STATE_ENABLED; + } + port_clear_feature(ext_port, USB_FEATURE_C_PORT_RESET); + need_processing = true; + } + + return need_processing; +} + +/** + * @brief Handles port state + * + * @param[in] ext_port Port object + */ +static void handle_port_state(ext_port_t *ext_port) +{ + bool need_handling = false; + usb_hub_port_state_t curr_state = ext_port->state; + usb_hub_port_state_t new_state = ext_port->state; + + switch (curr_state) { + case USB_PORT_STATE_NOT_CONFIGURED: + new_state = USB_PORT_STATE_POWERED_OFF; + port_request_status(ext_port); + need_handling = true; + break; + case USB_PORT_STATE_POWERED_OFF: + // Port power state depends on the wHubCharacteristics.power_switching + new_state = USB_PORT_STATE_DISCONNECTED; + port_set_feature(ext_port, USB_FEATURE_PORT_POWER); + need_handling = true; + break; + case USB_PORT_STATE_DISCONNECTED: + if (port_has_connection(ext_port)) { + if (ext_port->dev_reset_attempts < EXT_PORT_RESET_ATTEMPTS) { + // Available, it EXT_PORT_RESET_ATTEMPTS > 1 + ext_port->dev_reset_attempts++; + port_set_feature(ext_port, USB_FEATURE_PORT_RESET); + need_handling = true; + } else { + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Unable to reset the device", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + } + } + break; + case USB_PORT_STATE_DISABLED: + if (port_has_connection(ext_port)) { + // This logic does depend on the moment, when we propagate the EXT_PORT_DISCONNECTED event + // during the port disable. + if (!ext_port->flags.has_enum_device && ext_port->flags.waiting_recycle) { + // Port was disabled before enumeration, so the USBH device object was not created. + // Clean the recycle flag and complete port handling with device attached. + ext_port->flags.waiting_recycle = 0; + } + } + break; + case USB_PORT_STATE_RESETTING: + if (port_has_connection(ext_port)) { + // Port in resetting state and has connection + if (ext_port->dev_state == PORT_DEV_NOT_PRESENT) { + assert(ext_port->dev_reset_attempts == 0); // First reset, attempts always should be 0 + ext_port->dev_reset_attempts++; + port_set_feature(ext_port, USB_FEATURE_PORT_RESET); + } else { + assert(port_is_enabled(ext_port)); // Port should be enabled + port_event(ext_port, EXT_PORT_RESET_COMPLETED); + new_state = USB_PORT_STATE_ENABLED; + } + need_handling = true; + } else { + // Port in resetting state and doesn't have connection + // Error case, could be, when device was removed during port reset + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Device gone during port reset, recover port", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + new_state = USB_PORT_STATE_DISCONNECTED; + } + break; + case USB_PORT_STATE_ENABLED: + if (port_is_enabled(ext_port)) { + if (port_has_connection(ext_port)) { + if (ext_port->dev_state == PORT_DEV_NOT_PRESENT) { + usb_speed_t dev_speed = USB_SPEED_LOW; + if (ext_port->status.wPortStatus.PORT_LOW_SPEED == 0) { + dev_speed = ext_port->status.wPortStatus.PORT_HIGH_SPEED + ? USB_SPEED_HIGH + : USB_SPEED_FULL; + } + ESP_LOGD(EXT_PORT_TAG, "Device speed %s", (char *[]) { + "Low", "Full", "High" + }[dev_speed]); + ext_port->dev_state = PORT_DEV_PRESENT; + port_event(ext_port, EXT_PORT_CONNECTED); + } else { + // Port enabled, device present, reset completed + ext_port->dev_reset_attempts = 0; + port_event(ext_port, EXT_PORT_RESET_COMPLETED); + } + need_handling = true; + } else { + // TODO: IDF-10071 Port debounce mechanism + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Enabled, but doesn't have connection", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + } + } else { + // If port was enabled, there should be an active device + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ext_port->dev_state = PORT_DEV_NOT_PRESENT; + port_event(ext_port, EXT_PORT_DISCONNECTED); + ext_port->flags.waiting_recycle = 1; + } else { + // Error state + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Enabled, but doesn't have a device", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + new_state = USB_PORT_STATE_DISCONNECTED; + } + } + break; + default: + // Should never occur + abort(); + break; + } + + if (curr_state != new_state) { + ESP_LOGD(EXT_PORT_TAG, "New state: %d", new_state); + ext_port->state = new_state; + } + + if (!ext_port->flags.waiting_recycle && !need_handling) { + handle_complete(ext_port); + } +} + +/** + * @brief Port object handling action + * + * @note Handles the port in the following order: + * - Port changes (C_CONNECTION, C_ENABLE, C_RESET) + * - Port status (Port still in RESET state) + * - Port state + * + * When ports' handling has been completed, removes the port object from the pending list. + * + * @param[in] ext_port Port object + */ +static void handle_port(ext_port_t *ext_port) +{ + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] change=0x%04x, status=0x%04x, state=%d", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->status.wPortChange.val, + ext_port->status.wPortStatus.val, + ext_port->state); + + if (handle_port_changes(ext_port)) { + return; + } else if (handle_port_status(ext_port)) { + return; + } + handle_port_state(ext_port); +} + +/** + * @brief Port object handling recycle action + * + * @note Port should undergo a recycle procedure when: + * - Device has been detached and freed by all clients (port is present) + * - Parent device been detached (port is gone) + * - Port has run an error during the handling + * + * @param[in] ext_port Port object + */ +static void handle_recycle(ext_port_t *ext_port) +{ + assert(ext_port->flags.waiting_recycle == 1); + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port recycle (state=%d, dev=%d)", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->state, + ext_port->dev_state); + + ext_port->flags.waiting_recycle = 0; + + if (ext_port->flags.status_lock) { + // Port is awaiting the status + return; + } + + // Port should not have any changes + assert(ext_port->status.wPortChange.val == 0); + switch (ext_port->state) { + case USB_PORT_STATE_DISABLED: + // We don't need to do anything, as port will be handled after completing USB_FEATURE_PORT_ENABLE + if (ext_port->flags.is_gone) { + handle_complete(ext_port); + } + break; + default: + ext_port->state = USB_PORT_STATE_DISCONNECTED; + if (ext_port->flags.is_gone) { + handle_complete(ext_port); + } else { + handle_port(ext_port); + } + break; + } +} + +/** + * @brief Port object handling disable action + * + * @param[in] ext_port Port object + */ +static void handle_disable(ext_port_t *ext_port) +{ + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Disable (state=%d, dev=%d)", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->state, + ext_port->dev_state); + + assert(ext_port->state != USB_PORT_STATE_POWERED_OFF); + assert(ext_port->state != USB_PORT_STATE_DISCONNECTED); + assert(ext_port->state != USB_PORT_STATE_NOT_CONFIGURED); + + if (ext_port->state == USB_PORT_STATE_ENABLED) { + if (port_has_connection(ext_port)) { + ESP_LOGE(EXT_PORT_TAG, "[%d:%d] Port disabled, reset attempts=%d", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->dev_reset_attempts); + + // Do not try to reset port anymore + ext_port->dev_reset_attempts = EXT_PORT_RESET_ATTEMPTS; + + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ext_port->dev_state = PORT_DEV_NOT_PRESENT; + // Propagate device disconnection if device present + port_event(ext_port, EXT_PORT_DISCONNECTED); + ext_port->flags.waiting_recycle = 1; + } + } + } + + ext_port->state = USB_PORT_STATE_DISABLED; + + if (!ext_port->flags.is_gone) { + // Port not gone, disable port + port_clear_feature(ext_port, USB_FEATURE_PORT_ENABLE); + } +} + +// ----------------------------------------------------------------------------- +// ------------------------ External Port API ---------------------------------- +// ----------------------------------------------------------------------------- + +/** + * @brief Creates new port object + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_cfg Port configuration + * @param[out] port_hdl Port object handle + * @return + * - ESP_ERR_INVALID_STATE: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: Unable to create the port, arguments couldn't be NULL + * - ESP_ERR_NO_MEM: Unable to allocate the port object: no memory + * - ESP_ERR_NOT_FINISHED: Unable to allocate the port object: parent device is not available + * - ESP_OK: Port has been created and added to the pending list + */ +static esp_err_t port_new(void *port_cfg, void **port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_PORT_CHECK(port_cfg != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG); + + ext_port_t *port = NULL; + ext_port_config_t *config = (ext_port_config_t *)port_cfg; + esp_err_t ret = port_alloc(config->ext_hub_hdl, + config->parent_dev_hdl, + config->parent_port_num, + config->port_power_delay_ms, + &port); + + if (ret != ESP_OK) { + *port_hdl = NULL; + goto exit; + } + + port_set_actions(port, PORT_ACTION_HANDLE); + *port_hdl = (ext_port_hdl_t) port; +exit: + return ret; +} + +/** + * @brief Indicates to the External Port driver that a device's port can be recycled + * + * The device connected to the port has been freed. The External Port driver can now + * recycle the port. + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: Unable to recycle the port, port handle couldn't be NULL + * - ESP_OK: + */ +static esp_err_t port_recycle(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + + ESP_LOGD(EXT_PORT_TAG, "Port %d request recycle, state=%d", ext_port->constant.port_num, ext_port->state); + + port_set_actions(ext_port, PORT_ACTION_RECYCLE); + return ESP_OK; +} + +/** + * @brief Indicate to the External Port driver that reset of a device's port is required + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: Port handle couldn't be NULL + * - ESP_ERR_INVALID_STATE: Port doesn't have a connection or not enabled + * - ESP_OK: Port reset requested + */ +static esp_err_t port_reset(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + + EXT_PORT_CHECK(port_has_connection(ext_port), ESP_ERR_INVALID_STATE); + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port reset request", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + + // Reset can be triggered only when port is enabled + EXT_PORT_CHECK(ext_port->state == USB_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE); + + port_set_actions(ext_port, PORT_ACTION_RESET); + return ESP_OK; +} + +/** + * @brief Get the speed of the External Port + * + * The speed of the port is determined by the speed of the device connected to it. + * + * @note This function is only when a device connected to the port and has been reset + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @param[out] speed Speed of the port + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL, speed pointer can't be NULL + * - ESP_ERR_INVALID_STATE: No valid device connected to the port + * - ESP_OK: Device speed obtained + */ +esp_err_t port_get_speed(void *port_hdl, usb_speed_t *speed) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL && speed != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + + usb_speed_t dev_speed = USB_SPEED_LOW; + EXT_PORT_CHECK(port_has_connection(ext_port), ESP_ERR_INVALID_STATE); + // Full-speed or High-speed device attached to this port (determined by bit PORT_HIGH_SPEED). + if (ext_port->status.wPortStatus.PORT_LOW_SPEED == 0) { + dev_speed = ext_port->status.wPortStatus.PORT_HIGH_SPEED + ? USB_SPEED_HIGH + : USB_SPEED_FULL; + } + // Otherwise, Low-speed + *speed = dev_speed; + return ESP_OK; +} + +/** + * @brief Indicate to the External Port Driver that port has an active device + * + * The device: + * - has been enumerated + * - still present + * - is in configured state + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_OK: Port has a device and the device completed enumeration process + */ +static esp_err_t port_active(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port has an enumerated device", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + + ext_port->flags.has_enum_device = 1; + + return handle_complete(ext_port); +} + +/** + * @brief Indicate to the External Port Driver that port should be disabled + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_ERR_INVALID_STATE: The port is not enabled + * - ESP_OK: Port disable requested + */ +static esp_err_t port_disable(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Disable", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + + EXT_PORT_CHECK(ext_port->state == USB_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE); + + port_set_actions(ext_port, PORT_ACTION_DISABLE); + return ESP_OK; +} + +/** + * @brief Deletes the port object + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_OK: Port object has been freed and deleted + */ +static esp_err_t port_delete(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *) port_hdl; + // Sanity checks + assert(ext_port->dev_state == PORT_DEV_NOT_PRESENT); // Port should not have a device + assert(ext_port->flags.in_pending_list == 0); // Port should not be in pending list + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Freeing", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num); + + heap_caps_free(ext_port); + + return ESP_OK; +} + +/** + * @brief Marks the Port as gone and initialize process of freeing the Port + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_ERR_NOT_FINISHED: The port has a device and should be recycled + * - ESP_OK: Port object has been marked as gone and could be freed + */ +static esp_err_t port_gone(void *port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port is gone (state=%d, dev=%d)", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + ext_port->state, + ext_port->dev_state); + + bool has_device = false; + + ext_port->flags.is_gone = 1; + ext_port->flags.waiting_free = 1; + + switch (ext_port->state) { + case USB_PORT_STATE_ENABLED: + case USB_PORT_STATE_DISABLED: + case USB_PORT_STATE_RESETTING: + // Port could have a device + if (ext_port->dev_state == PORT_DEV_PRESENT) { + ext_port->dev_state = PORT_DEV_NOT_PRESENT; + ext_port->flags.waiting_recycle = 1; + port_event(ext_port, EXT_PORT_DISCONNECTED); + has_device = true; + } + break; + case USB_PORT_STATE_NOT_CONFIGURED: + // Port has been added, but the Driver has not started the handling yet + case USB_PORT_STATE_POWERED_OFF: + case USB_PORT_STATE_DISCONNECTED: + // Port has been added, but doesn't have a device + // We can remove the port from pending list and could be freed by the External Hub Driver + break; + default: + // Should never occur + abort(); + break; + } + + // If the port is in handling list, stop it + if (ext_port->flags.in_pending_list) { + port_stop_handling(ext_port); + } + + return (has_device) ? ESP_ERR_NOT_FINISHED : ESP_OK; +} + +/** + * @brief Indicate to the External Port Driver that Port request has been finished and the Port requires handling + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_OK: Port action was requested + */ +static esp_err_t port_get_status(void* port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + + port_set_actions(ext_port, PORT_ACTION_GET_STATUS); + return ESP_OK; +} + +/** + * @brief Indicate to the External Port Driver that the Port has new status + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @param[in] port_status New status data + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL, the status data can't be NULL + * - ESP_OK: Port action was requested + */ +static esp_err_t port_set_status(void* port_hdl, const usb_port_status_t *port_status) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL && port_status != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + + // Update status + ext_port->status.wPortChange.val = port_status->wPortChange.val; + ext_port->status.wPortStatus.val = port_status->wPortStatus.val; + // Status valid + ext_port->flags.status_outdated = 0; + // Remove status lock + ext_port->flags.status_lock = 0; + // Request port handling + port_set_actions(ext_port, PORT_ACTION_HANDLE); + return ESP_OK; +} + +/** + * @brief Indicate to the External Port Driver that the Port requires processing + * + * @note This function should only be called from the External Hub Driver + * + * @param[in] port_hdl Port object handle + * @return + * - ESP_ERR_NOT_ALLOWED: The External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG: The port handle can't be NULL + * - ESP_OK: Port action was requested + */ +static esp_err_t port_req_process(void* port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + + if (ext_port->flags.status_outdated) { + port_set_actions(ext_port, PORT_ACTION_GET_STATUS); + } else { + port_set_actions(ext_port, PORT_ACTION_HANDLE); + } + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// ------------------ External Port Processing Functions ----------------------- +// ----------------------------------------------------------------------------- + +/** + * @brief External Port Driver API + */ +const ext_hub_port_driver_t ext_port_driver = { + .new = port_new, + .reset = port_reset, + .recycle = port_recycle, + .active = port_active, + .disable = port_disable, + .gone = port_gone, + .del = port_delete, + .get_speed = port_get_speed, + .get_status = port_get_status, + .set_status = port_set_status, + .req_process = port_req_process, +}; + +esp_err_t ext_port_install(const ext_port_driver_config_t *config) +{ + EXT_PORT_CHECK(p_ext_port_driver == NULL, ESP_ERR_NOT_ALLOWED); + + ext_port_driver_t *ext_port_drv = heap_caps_calloc(1, sizeof(ext_port_driver_t), MALLOC_CAP_DEFAULT); + EXT_PORT_CHECK(ext_port_drv != NULL, ESP_ERR_NO_MEM); + + // Save callbacks + ext_port_drv->constant.proc_req_cb = config->proc_req_cb; + ext_port_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg; + ext_port_drv->constant.event_cb = config->event_cb; + ext_port_drv->constant.event_cb_arg = config->event_cb_arg; + TAILQ_INIT(&ext_port_drv->single_thread.pending_tailq); + + p_ext_port_driver = ext_port_drv; + + ESP_LOGD(EXT_PORT_TAG, "Driver installed"); + return ESP_OK; +} + +esp_err_t ext_port_uninstall(void) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(TAILQ_EMPTY(&p_ext_port_driver->single_thread.pending_tailq), ESP_ERR_INVALID_STATE); + ext_port_driver_t *ext_port_drv = p_ext_port_driver; + p_ext_port_driver = NULL; + + heap_caps_free(ext_port_drv); + ESP_LOGD(EXT_PORT_TAG, "Driver uninstalled"); + return ESP_OK; +} + +esp_err_t ext_port_process(void) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + + ext_port_t *ext_port = get_port_from_pending_list(); + if (ext_port == NULL) { + // No more ports in list to handle + // NOTE: + // This is possible, when an external Hub detached sooner than being + // configured and handled by the Driver. This is not an error case, + // because the processing requests by the HUB_DRIVER_ACTION_EXT_PORT + // flag in the Hub Driver and there is no way to clean it. + ESP_LOGD(EXT_PORT_TAG, "No more ports to handle"); + return ESP_OK; + } + + uint32_t action_flags = ext_port->action_flags; + ext_port->action_flags = 0; + + while (action_flags) { + // Keep processing until all port's action have been handled + ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Processing actions 0x%"PRIx32"", + ext_port->constant.parent_dev_addr, + ext_port->constant.port_num, + action_flags); + + if (action_flags & PORT_ACTION_HANDLE) { + handle_port(ext_port); + } + if (action_flags & PORT_ACTION_DISABLE) { + handle_disable(ext_port); + } + if (action_flags & PORT_ACTION_RECYCLE) { + handle_recycle(ext_port); + } + + /* + * Feature related actions are mutual exclusive and require: + * - transfer completion callback + * - further request of new port status via get_status(), except PORT_ACTION_GET_STATUS itself + */ + if (action_flags & PORT_ACTION_GET_STATUS) { + port_request_status(ext_port); + } else if (action_flags & PORT_ACTION_RESET) { + if (ext_port->state != USB_PORT_STATE_RESETTING) { + /* + * IMPORTANT NOTE + * This is possible, when the reset is requested via port_reset() + * Port reset is possible only in two states: + * - USB_PORT_STATE_DISCONNECTED (mainly, first reset) + * - USB_PORT_STATE_ENABLED (mainly, second reset) + */ + assert(ext_port->state == USB_PORT_STATE_DISCONNECTED || + ext_port->state == USB_PORT_STATE_ENABLED); + ext_port->state = USB_PORT_STATE_RESETTING; + } + port_set_feature(ext_port, USB_FEATURE_PORT_RESET); + } + + action_flags = ext_port->action_flags; + ext_port->action_flags = 0; + } + + return ESP_OK; +} + +const ext_hub_port_driver_t *ext_port_get_driver(void) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, NULL); + return &ext_port_driver; +} diff --git a/components/usb/hub.c b/components/usb/hub.c index 6fa14fa303..079a18f351 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -20,7 +20,7 @@ #include "usb/usb_helpers.h" #if ENABLE_USB_HUBS -#include "ext_hub.h" +#include "ext_port.h" #endif // ENABLE_USB_HUBS /* @@ -320,6 +320,72 @@ static bool ext_hub_callback(bool in_isr, void *user_arg) 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); } + +static void ext_port_callback(void *user_arg) +{ + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_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); +} + +static void ext_port_event_callback(ext_port_event_data_t *event_data, void *arg) +{ + switch (event_data->event) { + case EXT_PORT_CONNECTED: + // First reset is done by ext_port logic + usb_speed_t port_speed; + + if (ext_hub_port_get_speed(event_data->connected.ext_hub_hdl, + event_data->connected.parent_port_num, + &port_speed) != ESP_OK) { + goto new_ds_dev_err; + } + + // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH + usb_device_info_t parent_dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(event_data->connected.parent_dev_hdl, &parent_dev_info)); + if (parent_dev_info.speed == USB_SPEED_HIGH) { + if (port_speed != parent_dev_info.speed) { + ESP_LOGE(HUB_DRIVER_TAG, "Connected device is %s, transaction translator (TT) is not supported", + (char *[]) { + "LS", "FS", "HS" + }[port_speed]); + goto new_ds_dev_err; + } + } + +#if (!CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS) + if (port_speed == USB_SPEED_LOW) { + ESP_LOGE(HUB_DRIVER_TAG, "Connected %s-speed device, not supported", + (char *[]) { + "Low", "Full", "High" + }[port_speed]); + goto new_ds_dev_err; + } +#endif // CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS + + if (new_dev_tree_node(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new downstream device"); + goto new_ds_dev_err; + } + break; +new_ds_dev_err: + ext_hub_port_disable(event_data->connected.ext_hub_hdl, event_data->connected.parent_port_num); + break; + case EXT_PORT_RESET_COMPLETED: + ESP_ERROR_CHECK(dev_tree_node_reset_completed(event_data->reset_completed.parent_dev_hdl, event_data->reset_completed.parent_port_num)); + break; + case EXT_PORT_DISCONNECTED: + // The node could be freed by now, no need to verify the result here + dev_tree_node_dev_gone(event_data->disconnected.parent_dev_hdl, event_data->disconnected.parent_port_num); + break; + default: + // Should never occur + abort(); + break; + } +} #endif // ENABLE_USB_HUBS // ---------------------- Handlers ------------------------- @@ -479,10 +545,20 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) } #if ENABLE_USB_HUBS + // Install External Port driver + ext_port_driver_config_t ext_port_config = { + .proc_req_cb = ext_port_callback, + .event_cb = ext_port_event_callback, + }; + ret = ext_port_install(&ext_port_config); + if (ret != ESP_OK) { + goto err_ext_port; + } + // Install External HUB driver ext_hub_config_t ext_hub_config = { .proc_req_cb = ext_hub_callback, - .port_driver = NULL, + .port_driver = ext_port_get_driver(), }; ret = ext_hub_install(&ext_hub_config); if (ret != ESP_OK) { @@ -536,6 +612,8 @@ err: #if ENABLE_USB_HUBS ext_hub_uninstall(); err_ext_hub: + ext_port_uninstall(); +err_ext_port: #endif // ENABLE_USB_HUBS heap_caps_free(hub_driver_obj); return ret; @@ -552,6 +630,7 @@ esp_err_t hub_uninstall(void) #if ENABLE_USB_HUBS ESP_ERROR_CHECK(ext_hub_uninstall()); + ESP_ERROR_CHECK(ext_port_uninstall()); #endif // ENABLE_USB_HUBS ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl)); @@ -739,10 +818,7 @@ esp_err_t hub_process(void) while (action_flags) { #if ENABLE_USB_HUBS if (action_flags & HUB_DRIVER_ACTION_EXT_PORT) { - ESP_LOGW(HUB_DRIVER_TAG, "ext_port_process() has not been implemented yet"); - /* ESP_ERROR_CHECK(ext_port_process()); - */ } if (action_flags & HUB_DRIVER_ACTION_EXT_HUB) { ESP_ERROR_CHECK(ext_hub_process()); diff --git a/components/usb/include/usb/usb_types_ch11.h b/components/usb/include/usb/usb_types_ch11.h index a30e9d3919..f3c9a3d77a 100644 --- a/components/usb/include/usb/usb_types_ch11.h +++ b/components/usb/include/usb/usb_types_ch11.h @@ -43,6 +43,28 @@ typedef enum { USB_B_REQUEST_HUB_STOP_TT = 0x0B, /**< Stop TT. */ } usb_hub_class_request_t ; +/** + * @brief USB Hub Port state + * + * See USB 2.0 spec, 11.5.1 Downstream Facing Port State Descriptions + */ +typedef enum { + USB_PORT_STATE_NOT_CONFIGURED = 0x00, /**< The hub is not configured. A port transitions to and remains in this state whenever the value of the hub configuration is zero. */ + USB_PORT_STATE_POWERED_OFF, /**< Powered_off: Port requires explicit request to transition. */ + USB_PORT_STATE_DISCONNECTED, /**< In the Disconnected state, the port’s differential transmitter and receiver are disabled and only connection detection is possible.*/ + USB_PORT_STATE_DISABLED, /**< A port in the Disabled state will not propagate signaling in either the upstream or the downstream direction */ + USB_PORT_STATE_RESETTING, /**< The duration of the Resetting state is nominally 10 ms to 20 ms (10 ms is preferred). */ + USB_PORT_STATE_ENABLED, /**< While in this state, the output of the port’s differential receiver is available to the Hub Repeater so that appropriate signaling transitions can establish upstream connectivity*/ + USB_PORT_STATE_TRANSMIT, /**< This state is entered from the Enabled state on the transition of the Hub Repeater to the WFEOPFU state */ + USB_PORT_STATE_TRANSMIT_R, /**< When in this state, the port repeats the resume ‘K’ at the upstream facing port to the downstream facing port. */ + USB_PORT_STATE_SUSPENDED, /**< While a port is in the Suspended state, the port's differential transmitter is disabled. */ + USB_PORT_STATE_RESUMING, /**< While in this state, the hub drives a 'K' on the port. */ + USB_PORT_STATE_SEND_EOR, /**< This state is entered from the Resuming state if the 20 ms timer expires. */ + USB_PORT_STATE_RESTART_S, /**< A port enters the Restart_S state from the Suspended state when an SE0 or ‘K’ is seen at the port and the Receiver is in the Suspended state */ + USB_PORT_STATE_RESTART_E, /**< A port enters the Restart_E state from the Enabled state when an ‘SE0’ or ‘K’ is seen at the port and the Receiver is in the Suspended state. */ + USB_PORT_STATE_TESTING, /**< A port transitions to this state from any state when the port sees SetTest. */ +} usb_hub_port_state_t; + /** * @brief USB Hub Port feature selector codes * diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h index 396d838be1..93432542a7 100644 --- a/components/usb/private_include/ext_hub.h +++ b/components/usb/private_include/ext_hub.h @@ -46,7 +46,7 @@ typedef struct { esp_err_t (*active)(void *port_hdl); esp_err_t (*disable)(void *port_hdl); esp_err_t (*gone)(void *port_hdl); - esp_err_t (*free)(void *port_hdl); + esp_err_t (*del)(void *port_hdl); esp_err_t (*get_speed)(void *por_hdl, usb_speed_t *speed); esp_err_t (*get_status)(void *port_hdl); esp_err_t (*set_status)(void *port_hdl, const usb_port_status_t *status); @@ -278,7 +278,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, * * @return * - ESP_OK: Port's feature set successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed; + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range */ @@ -293,7 +293,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu * * @return * - ESP_OK: Port's feature cleared successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed; + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range */ @@ -310,7 +310,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_ * * @return * - ESP_OK: Port's status obtained successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed; + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range */ diff --git a/components/usb/private_include/ext_port.h b/components/usb/private_include/ext_port.h new file mode 100644 index 0000000000..5795e41575 --- /dev/null +++ b/components/usb/private_include/ext_port.h @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "ext_hub.h" +#include "usb/usb_types_stack.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ext_port_s *ext_port_hdl_t; + +// ------------------------------ Events --------------------------------------- +typedef enum { + EXT_PORT_CONNECTED = 0x01, /**< Port has a device connection event */ + EXT_PORT_RESET_COMPLETED, /**< Port has completed the reset routine */ + EXT_PORT_DISCONNECTED, /**< Port has a device disconnection event */ +} ext_port_event_t; + +/** + * @brief Event data object for External Port driver events + */ +typedef struct { + ext_port_event_t event; + union { + struct { + ext_hub_handle_t ext_hub_hdl; /**< Ports' parent external Hub handle */ + usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ + uint8_t parent_port_num; /**< Ports' parent port number */ + } connected; /**< EXT_PORT_CONNECTED event specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ + uint8_t parent_port_num; /**< Ports' parent port number */ + } reset_completed; /**< EXT_PORT_RESET_COMPLETED event specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ + uint8_t parent_port_num; /**< Ports' parent port number */ + } disconnected; /**< EXT_PORT_DISCONNECTED event specific data */ + }; +} ext_port_event_data_t; + +// ------------------------------ Callbacks ------------------------------------ +/** + * @brief Callback used to indicate that the External Port Driver requires process callback + * + * @note For the Hub Driver only + */ +typedef void (*ext_port_cb_t)(void *user_arg); + +/** + * @brief Callback used to indicate that the External Port driver has an event + * + * @note For the Hub Driver only + */ +typedef void (*ext_port_event_cb_t)(ext_port_event_data_t *event_data, void *arg); + +// ----------------- External Port Driver configuration ------------------------ + +/** + * @brief External Port driver configuration + */ +typedef struct { + ext_port_cb_t proc_req_cb; /**< External Port process callback */ + void *proc_req_cb_arg; /**< External Port process callback argument */ + ext_port_event_cb_t event_cb; /**< External Port event callback */ + void *event_cb_arg; /**< External Port event callback argument */ +} ext_port_driver_config_t; + +/** + * @brief External Port configuration + * + * Structure is used to create new port + */ +typedef struct { + ext_hub_handle_t ext_hub_hdl; /**< Ports' parent external Hub handle */ + usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ + uint8_t parent_port_num; /**< Ports' parent port number */ + uint16_t port_power_delay_ms; /**< Ports' Power on time to Power Good, ms */ +} ext_port_config_t; + +// -------------------- External Port Processing Functions --------------------- + +/** + * @brief Install the External Port Driver + * + * @note This function should only be called from the Hub Driver + * + * @param[in] config External Port Driver configuration + * @return + * - ESP_ERR_NOT_ALLOWED: The Driver was already installed + * - ESP_ERR_NO_MEM: Unable to install the Driver, no memory + * - ESP_OK: The Driver has been installed successfully + */ +esp_err_t ext_port_install(const ext_port_driver_config_t *config); + +/** + * @brief Uninstall the External Port Driver + * + * @note This function should only be called from the Hub Driver + * + * @return + * - ESP_ERR_NOT_ALLOWED: The Driver was not installed + * - ESP_ERR_INVALID_STATE: The Driver has ports in the pending list and can't be uninstalled + * - ESP_OK: The Driver has been uninstall successfully + */ +esp_err_t ext_port_uninstall(void); + +/** + * @brief External Port Driver's process function + * + * @note This function should only be called from the Hub Driver + * + * External Port Driver process function that must be called repeatedly to process the driver's actions and events. + * If blocking, the caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB + * to run this function. + * + * @return + * - ESP_ERR_NOT_ALLOWED: The Driver was not installed + * - ESP_OK: The Driver processed completed + */ +esp_err_t ext_port_process(void); + +/** + * @brief Returns External Port Driver's API + * + * @note This is a specific API for the External Hub Driver to handle the ports. + * @return + * - NULL: The Driver has not been installed + * - not NULL: Pointer to the External Port Driver API + */ +const ext_hub_port_driver_t *ext_port_get_driver(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index a580c45855..e3efaac9bf 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -134,8 +134,8 @@ esp_err_t hub_root_stop(void); * * @note This function should only be called from the Host Library task * - * @param[in] parent_dev_hdl - * @param[in] parent_port_num + * @param[in] parent_dev_hdl Parent device handle (is used to get the External Hub handle) + * @param[in] parent_port_num Parent number (is used to specify the External Port) * @param[in] dev_uid Device's unique ID * * @return @@ -151,9 +151,8 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po * * @note This function should only be called from the Host Library task * - * @param[in] parent_dev_hdl - * @param[in] parent_port_num - * + * @param[in] parent_dev_hdl Parent device handle (is used to get the External Hub handle) + * @param[in] parent_port_num Parent number (is used to specify the External Port) * @return * - ESP_OK: Port reset successful * - ESP_ERR_INVALID_STATE: Hub driver is not installed @@ -198,6 +197,8 @@ esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_po * * If device is has a HUB class, then it will be added as External Hub to Hub Driver. * + * @note This function should only be called from the Host Library task + * * @param[in] dev_addr Device bus address * * @return @@ -211,6 +212,8 @@ esp_err_t hub_notify_new_dev(uint8_t dev_addr); * * If the device was an External Hub, then it will be removed from the Hub Driver. * + * @note This function should only be called from the Host Library task + * * @param[in] dev_addr Device bus address * * @return @@ -222,6 +225,8 @@ esp_err_t hub_notify_dev_gone(uint8_t dev_addr); /** * @brief Notify Hub driver that all devices should be freed * + * @note This function should only be called from the Host Library task + * * @return * - ESP_OK: All the devices can be freed * - ESP_ERR_INVALID_STATE: Hub driver is not in a correct state diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 75188f02d9..c515fc01ca 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -1050,7 +1050,6 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) 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 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); From 670f11644f4bf8985f1052ab6e1507047a40640e Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Mon, 7 Oct 2024 19:03:51 +0800 Subject: [PATCH 2/5] refactor(usb_host/examples): Enabled external Hub support feature --- .../usb/test_apps/hcd/sdkconfig.defaults | 1 + .../usb/test_apps/usb_host/sdkconfig.defaults | 1 + .../host/cdc/cdc_acm_vcp/sdkconfig.defaults | 1 + .../usb/host/hid/sdkconfig.defaults | 4 + .../usb/host/msc/main/msc_example_main.c | 88 +++--- .../usb/host/msc/sdkconfig.defaults | 4 + .../usb/host/usb_host_lib/main/class_driver.c | 254 ++++++++++++------ .../usb_host_lib/main/usb_host_lib_main.c | 49 ++-- .../usb/host/usb_host_lib/sdkconfig.defaults | 4 + 9 files changed, 256 insertions(+), 150 deletions(-) create mode 100644 examples/peripherals/usb/host/hid/sdkconfig.defaults create mode 100644 examples/peripherals/usb/host/msc/sdkconfig.defaults create mode 100644 examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults diff --git a/components/usb/test_apps/hcd/sdkconfig.defaults b/components/usb/test_apps/hcd/sdkconfig.defaults index 0632edf757..d433503fbf 100644 --- a/components/usb/test_apps/hcd/sdkconfig.defaults +++ b/components/usb/test_apps/hcd/sdkconfig.defaults @@ -6,3 +6,4 @@ CONFIG_HEAP_POISONING_COMPREHENSIVE=y # CONFIG_UNITY_ENABLE_FLOAT is not set # CONFIG_UNITY_ENABLE_DOUBLE is not set CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y +CONFIG_USB_HOST_HUBS_SUPPORTED=y diff --git a/components/usb/test_apps/usb_host/sdkconfig.defaults b/components/usb/test_apps/usb_host/sdkconfig.defaults index 0632edf757..d433503fbf 100644 --- a/components/usb/test_apps/usb_host/sdkconfig.defaults +++ b/components/usb/test_apps/usb_host/sdkconfig.defaults @@ -6,3 +6,4 @@ CONFIG_HEAP_POISONING_COMPREHENSIVE=y # CONFIG_UNITY_ENABLE_FLOAT is not set # CONFIG_UNITY_ENABLE_DOUBLE is not set CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y +CONFIG_USB_HOST_HUBS_SUPPORTED=y diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults index 990777805d..a3e635c9d8 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults @@ -2,3 +2,4 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_USB_HOST_HUBS_SUPPORTED=y diff --git a/examples/peripherals/usb/host/hid/sdkconfig.defaults b/examples/peripherals/usb/host/hid/sdkconfig.defaults new file mode 100644 index 0000000000..bf1b553b23 --- /dev/null +++ b/examples/peripherals/usb/host/hid/sdkconfig.defaults @@ -0,0 +1,4 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_USB_HOST_HUBS_SUPPORTED=y diff --git a/examples/peripherals/usb/host/msc/main/msc_example_main.c b/examples/peripherals/usb/host/msc/main/msc_example_main.c index eb934a5681..630c95e6b1 100644 --- a/examples/peripherals/usb/host/msc/main/msc_example_main.c +++ b/examples/peripherals/usb/host/msc/main/msc_example_main.c @@ -29,6 +29,11 @@ static const char *TAG = "example"; #define APP_QUIT_PIN GPIO_NUM_0 // BOOT button on most boards #define BUFFER_SIZE 4096 // The read/write performance can be improved with larger buffer for the cost of RAM, 4kB is enough for most usecases +// IMPORTANT NOTE +// MSC Class Driver is not fully support connecting devices through external Hub. +// TODO: Remove this line after MSC Class Driver will support it +static bool dev_present = false; + /** * @brief Application Queue and its messages ID */ @@ -78,7 +83,7 @@ static void gpio_cb(void *arg) static void msc_event_cb(const msc_host_event_t *event, void *arg) { if (event->event == MSC_DEVICE_CONNECTED) { - ESP_LOGI(TAG, "MSC device connected"); + ESP_LOGI(TAG, "MSC device connected (usb_addr=%d)", event->device.address); app_message_t message = { .id = APP_DEVICE_CONNECTED, .data.new_dev_address = event->device.address, @@ -274,47 +279,56 @@ void app_main(void) xQueueReceive(app_queue, &msg, portMAX_DELAY); if (msg.id == APP_DEVICE_CONNECTED) { - // 1. MSC flash drive connected. Open it and map it to Virtual File System - ESP_ERROR_CHECK(msc_host_install_device(msg.data.new_dev_address, &msc_device)); - const esp_vfs_fat_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 3, - .allocation_unit_size = 8192, - }; - ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle)); + if (dev_present) { + ESP_LOGW(TAG, "MSC Example handles only one device at a time"); + } else { + // 0. Change flag + dev_present = true; + // 1. MSC flash drive connected. Open it and map it to Virtual File System + ESP_ERROR_CHECK(msc_host_install_device(msg.data.new_dev_address, &msc_device)); + const esp_vfs_fat_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 3, + .allocation_unit_size = 8192, + }; + ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle)); - // 2. Print information about the connected disk - msc_host_device_info_t info; - ESP_ERROR_CHECK(msc_host_get_device_info(msc_device, &info)); - msc_host_print_descriptors(msc_device); - print_device_info(&info); + // 2. Print information about the connected disk + msc_host_device_info_t info; + ESP_ERROR_CHECK(msc_host_get_device_info(msc_device, &info)); + msc_host_print_descriptors(msc_device); + print_device_info(&info); - // 3. List all the files in root directory - ESP_LOGI(TAG, "ls command output:"); - struct dirent *d; - DIR *dh = opendir(MNT_PATH); - assert(dh); - while ((d = readdir(dh)) != NULL) { - printf("%s\n", d->d_name); + // 3. List all the files in root directory + ESP_LOGI(TAG, "ls command output:"); + struct dirent *d; + DIR *dh = opendir(MNT_PATH); + assert(dh); + while ((d = readdir(dh)) != NULL) { + printf("%s\n", d->d_name); + } + closedir(dh); + + // 4. The disk is mounted to Virtual File System, perform some basic demo file operation + file_operations(); + + // 5. Perform speed test + speed_test(); + + ESP_LOGI(TAG, "Example finished, you can disconnect the USB flash drive"); } - closedir(dh); - - // 4. The disk is mounted to Virtual File System, perform some basic demo file operation - file_operations(); - - // 5. Perform speed test - speed_test(); - - ESP_LOGI(TAG, "Example finished, you can disconnect the USB flash drive"); } if ((msg.id == APP_DEVICE_DISCONNECTED) || (msg.id == APP_QUIT)) { - if (vfs_handle) { - ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle)); - vfs_handle = NULL; - } - if (msc_device) { - ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device)); - msc_device = NULL; + if (dev_present) { + dev_present = false; + if (vfs_handle) { + ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle)); + vfs_handle = NULL; + } + if (msc_device) { + ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device)); + msc_device = NULL; + } } if (msg.id == APP_QUIT) { // This will cause the usb_task to exit diff --git a/examples/peripherals/usb/host/msc/sdkconfig.defaults b/examples/peripherals/usb/host/msc/sdkconfig.defaults new file mode 100644 index 0000000000..bf1b553b23 --- /dev/null +++ b/examples/peripherals/usb/host/msc/sdkconfig.defaults @@ -0,0 +1,4 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_USB_HOST_HUBS_SUPPORTED=y diff --git a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c index 594488df51..3b7077986f 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c @@ -13,21 +13,40 @@ #define CLIENT_NUM_EVENT_MSG 5 typedef enum { - ACTION_OPEN_DEV = 0x01, - ACTION_GET_DEV_INFO = 0x02, - ACTION_GET_DEV_DESC = 0x04, - ACTION_GET_CONFIG_DESC = 0x08, - ACTION_GET_STR_DESC = 0x10, - ACTION_CLOSE_DEV = 0x20, - ACTION_EXIT = 0x40, - ACTION_RECONNECT = 0x80, + ACTION_OPEN_DEV = (1 << 0), + ACTION_GET_DEV_INFO = (1 << 1), + ACTION_GET_DEV_DESC = (1 << 2), + ACTION_GET_CONFIG_DESC = (1 << 3), + ACTION_GET_STR_DESC = (1 << 4), + ACTION_CLOSE_DEV = (1 << 5), } action_t; +#define DEV_MAX_COUNT 128 + typedef struct { usb_host_client_handle_t client_hdl; uint8_t dev_addr; usb_device_handle_t dev_hdl; - uint32_t actions; + action_t actions; +} usb_device_t; + +typedef struct { + struct { + union { + struct { + uint8_t unhandled_devices: 1; /**< Device has unhandled devices */ + uint8_t shutdown: 1; /**< */ + uint8_t reserved6: 6; /**< Reserved */ + }; + uint8_t val; /**< Class drivers' flags value */ + } flags; /**< Class drivers' flags */ + usb_device_t device[DEV_MAX_COUNT]; /**< Class drivers' static array of devices */ + } mux_protected; /**< Mutex protected members. Must be protected by the Class mux_lock when accessed */ + + struct { + usb_host_client_handle_t client_hdl; + SemaphoreHandle_t mux_lock; /**< Mutex for protected members */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ } class_driver_t; static const char *TAG = "CLASS"; @@ -38,79 +57,94 @@ static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void * class_driver_t *driver_obj = (class_driver_t *)arg; switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: - if (driver_obj->dev_addr == 0) { - driver_obj->dev_addr = event_msg->new_dev.address; - //Open the device next - driver_obj->actions |= ACTION_OPEN_DEV; - } + // Save the device address + xSemaphoreTake(driver_obj->constant.mux_lock, portMAX_DELAY); + driver_obj->mux_protected.device[event_msg->new_dev.address].dev_addr = event_msg->new_dev.address; + driver_obj->mux_protected.device[event_msg->new_dev.address].dev_hdl = NULL; + // Open the device next + driver_obj->mux_protected.device[event_msg->new_dev.address].actions |= ACTION_OPEN_DEV; + // Set flag + driver_obj->mux_protected.flags.unhandled_devices = 1; + xSemaphoreGive(driver_obj->constant.mux_lock); break; case USB_HOST_CLIENT_EVENT_DEV_GONE: - if (driver_obj->dev_hdl != NULL) { - //Cancel any other actions and close the device next - driver_obj->actions = ACTION_CLOSE_DEV; + // Cancel any other actions and close the device next + xSemaphoreTake(driver_obj->constant.mux_lock, portMAX_DELAY); + for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) { + if (driver_obj->mux_protected.device[i].dev_hdl == event_msg->dev_gone.dev_hdl) { + driver_obj->mux_protected.device[i].actions = ACTION_CLOSE_DEV; + // Set flag + driver_obj->mux_protected.flags.unhandled_devices = 1; + } } + xSemaphoreGive(driver_obj->constant.mux_lock); break; default: - //Should never occur + // Should never occur abort(); } } -static void action_open_dev(class_driver_t *driver_obj) +static void action_open_dev(usb_device_t *device_obj) { - assert(driver_obj->dev_addr != 0); - ESP_LOGI(TAG, "Opening device at address %d", driver_obj->dev_addr); - ESP_ERROR_CHECK(usb_host_device_open(driver_obj->client_hdl, driver_obj->dev_addr, &driver_obj->dev_hdl)); - //Get the device's information next - driver_obj->actions &= ~ACTION_OPEN_DEV; - driver_obj->actions |= ACTION_GET_DEV_INFO; + assert(device_obj->dev_addr != 0); + ESP_LOGI(TAG, "Opening device at address %d", device_obj->dev_addr); + ESP_ERROR_CHECK(usb_host_device_open(device_obj->client_hdl, device_obj->dev_addr, &device_obj->dev_hdl)); + // Get the device's information next + device_obj->actions |= ACTION_GET_DEV_INFO; } -static void action_get_info(class_driver_t *driver_obj) +static void action_get_info(usb_device_t *device_obj) { - assert(driver_obj->dev_hdl != NULL); + assert(device_obj->dev_hdl != NULL); ESP_LOGI(TAG, "Getting device information"); usb_device_info_t dev_info; - ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + ESP_ERROR_CHECK(usb_host_device_info(device_obj->dev_hdl, &dev_info)); ESP_LOGI(TAG, "\t%s speed", (char *[]) { "Low", "Full", "High" }[dev_info.speed]); - ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue); + ESP_LOGI(TAG, "\tParent info:"); + if (dev_info.parent.dev_hdl) { + usb_device_info_t parent_dev_info; + ESP_ERROR_CHECK(usb_host_device_info(dev_info.parent.dev_hdl, &parent_dev_info)); + ESP_LOGI(TAG, "\t\tBus addr: %d", parent_dev_info.dev_addr); + ESP_LOGI(TAG, "\t\tPort: %d", dev_info.parent.port_num); - //Get the device descriptor next - driver_obj->actions &= ~ACTION_GET_DEV_INFO; - driver_obj->actions |= ACTION_GET_DEV_DESC; + } else { + ESP_LOGI(TAG, "\t\tPort: ROOT"); + } + ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue); + // Get the device descriptor next + device_obj->actions |= ACTION_GET_DEV_DESC; } -static void action_get_dev_desc(class_driver_t *driver_obj) +static void action_get_dev_desc(usb_device_t *device_obj) { - assert(driver_obj->dev_hdl != NULL); + assert(device_obj->dev_hdl != NULL); ESP_LOGI(TAG, "Getting device descriptor"); const usb_device_desc_t *dev_desc; - ESP_ERROR_CHECK(usb_host_get_device_descriptor(driver_obj->dev_hdl, &dev_desc)); + ESP_ERROR_CHECK(usb_host_get_device_descriptor(device_obj->dev_hdl, &dev_desc)); usb_print_device_descriptor(dev_desc); - //Get the device's config descriptor next - driver_obj->actions &= ~ACTION_GET_DEV_DESC; - driver_obj->actions |= ACTION_GET_CONFIG_DESC; + // Get the device's config descriptor next + device_obj->actions |= ACTION_GET_CONFIG_DESC; } -static void action_get_config_desc(class_driver_t *driver_obj) +static void action_get_config_desc(usb_device_t *device_obj) { - assert(driver_obj->dev_hdl != NULL); + assert(device_obj->dev_hdl != NULL); ESP_LOGI(TAG, "Getting config descriptor"); const usb_config_desc_t *config_desc; - ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(driver_obj->dev_hdl, &config_desc)); + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(device_obj->dev_hdl, &config_desc)); usb_print_config_descriptor(config_desc, NULL); - //Get the device's string descriptors next - driver_obj->actions &= ~ACTION_GET_CONFIG_DESC; - driver_obj->actions |= ACTION_GET_STR_DESC; + // Get the device's string descriptors next + device_obj->actions |= ACTION_GET_STR_DESC; } -static void action_get_str_desc(class_driver_t *driver_obj) +static void action_get_str_desc(usb_device_t *device_obj) { - assert(driver_obj->dev_hdl != NULL); + assert(device_obj->dev_hdl != NULL); usb_device_info_t dev_info; - ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + ESP_ERROR_CHECK(usb_host_device_info(device_obj->dev_hdl, &dev_info)); if (dev_info.str_desc_manufacturer) { ESP_LOGI(TAG, "Getting Manufacturer string descriptor"); usb_print_string_descriptor(dev_info.str_desc_manufacturer); @@ -123,25 +157,59 @@ static void action_get_str_desc(class_driver_t *driver_obj) ESP_LOGI(TAG, "Getting Serial Number string descriptor"); usb_print_string_descriptor(dev_info.str_desc_serial_num); } - //Nothing to do until the device disconnects - driver_obj->actions &= ~ACTION_GET_STR_DESC; } -static void action_close_dev(class_driver_t *driver_obj) +static void action_close_dev(usb_device_t *device_obj) { - ESP_ERROR_CHECK(usb_host_device_close(driver_obj->client_hdl, driver_obj->dev_hdl)); - driver_obj->dev_hdl = NULL; - driver_obj->dev_addr = 0; - //We need to connect a new device - driver_obj->actions &= ~ACTION_CLOSE_DEV; - driver_obj->actions |= ACTION_RECONNECT; + ESP_ERROR_CHECK(usb_host_device_close(device_obj->client_hdl, device_obj->dev_hdl)); + device_obj->dev_hdl = NULL; + device_obj->dev_addr = 0; +} + +static void class_driver_device_handle(usb_device_t *device_obj) +{ + uint8_t actions = device_obj->actions; + device_obj->actions = 0; + + while (actions) { + if (actions & ACTION_OPEN_DEV) { + action_open_dev(device_obj); + } + if (actions & ACTION_GET_DEV_INFO) { + action_get_info(device_obj); + } + if (actions & ACTION_GET_DEV_DESC) { + action_get_dev_desc(device_obj); + } + if (actions & ACTION_GET_CONFIG_DESC) { + action_get_config_desc(device_obj); + } + if (actions & ACTION_GET_STR_DESC) { + action_get_str_desc(device_obj); + } + if (actions & ACTION_CLOSE_DEV) { + action_close_dev(device_obj); + } + + actions = device_obj->actions; + device_obj->actions = 0; + } } void class_driver_task(void *arg) { class_driver_t driver_obj = {0}; + usb_host_client_handle_t class_driver_client_hdl = NULL; ESP_LOGI(TAG, "Registering Client"); + + SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); + if (mux_lock == NULL) { + ESP_LOGE(TAG, "Unable to create class driver mutex"); + vTaskDelete(NULL); + return; + } + usb_host_client_config_t client_config = { .is_synchronous = false, //Synchronous clients currently not supported. Set this to false .max_num_event_msg = CLIENT_NUM_EVENT_MSG, @@ -150,52 +218,62 @@ void class_driver_task(void *arg) .callback_arg = (void *) &driver_obj, }, }; - ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl)); + ESP_ERROR_CHECK(usb_host_client_register(&client_config, &class_driver_client_hdl)); + + driver_obj.constant.mux_lock = mux_lock; + driver_obj.constant.client_hdl = class_driver_client_hdl; + + for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) { + driver_obj.mux_protected.device[i].client_hdl = class_driver_client_hdl; + } + s_driver_obj = &driver_obj; while (1) { - if (driver_obj.actions == 0) { - usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY); + // Driver has unhandled devices, handle all devices first + if (driver_obj.mux_protected.flags.unhandled_devices) { + xSemaphoreTake(driver_obj.constant.mux_lock, portMAX_DELAY); + for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) { + if (driver_obj.mux_protected.device[i].actions) { + class_driver_device_handle(&driver_obj.mux_protected.device[i]); + } + } + driver_obj.mux_protected.flags.unhandled_devices = 0; + xSemaphoreGive(driver_obj.constant.mux_lock); } else { - if (driver_obj.actions & ACTION_OPEN_DEV) { - action_open_dev(&driver_obj); - } - if (driver_obj.actions & ACTION_GET_DEV_INFO) { - action_get_info(&driver_obj); - } - if (driver_obj.actions & ACTION_GET_DEV_DESC) { - action_get_dev_desc(&driver_obj); - } - if (driver_obj.actions & ACTION_GET_CONFIG_DESC) { - action_get_config_desc(&driver_obj); - } - if (driver_obj.actions & ACTION_GET_STR_DESC) { - action_get_str_desc(&driver_obj); - } - if (driver_obj.actions & ACTION_CLOSE_DEV) { - action_close_dev(&driver_obj); - } - if (driver_obj.actions & ACTION_EXIT) { + // Driver is active, handle client events + if (driver_obj.mux_protected.flags.shutdown == 0) { + usb_host_client_handle_events(class_driver_client_hdl, portMAX_DELAY); + } else { + // Shutdown the driver break; } - if (driver_obj.actions & ACTION_RECONNECT) { - driver_obj.actions = 0; - } } } - ESP_LOGI(TAG, "Deregistering Client"); - ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl)); - vTaskSuspend(NULL); + ESP_LOGI(TAG, "Deregistering Class Client"); + ESP_ERROR_CHECK(usb_host_client_deregister(class_driver_client_hdl)); + if (mux_lock != NULL) { + vSemaphoreDelete(mux_lock); + } + vTaskDelete(NULL); } void class_driver_client_deregister(void) { - if (s_driver_obj->dev_hdl != NULL) { - s_driver_obj->actions = ACTION_CLOSE_DEV; + // Mark all opened devices + xSemaphoreTake(s_driver_obj->constant.mux_lock, portMAX_DELAY); + for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) { + if (s_driver_obj->mux_protected.device[i].dev_hdl != NULL) { + // Mark device to close + s_driver_obj->mux_protected.device[i].actions |= ACTION_CLOSE_DEV; + // Set flag + s_driver_obj->mux_protected.flags.unhandled_devices = 1; + } } - s_driver_obj->actions |= ACTION_EXIT; + s_driver_obj->mux_protected.flags.shutdown = 1; + xSemaphoreGive(s_driver_obj->constant.mux_lock); // Unblock, exit the loop and proceed to deregister client - ESP_ERROR_CHECK(usb_host_client_unblock(s_driver_obj->client_hdl)); + ESP_ERROR_CHECK(usb_host_client_unblock(s_driver_obj->constant.client_hdl)); } diff --git a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c index e6c2a8df92..f1a2dc348a 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -120,26 +120,26 @@ static void usb_host_lib_task(void *arg) xTaskNotifyGive(arg); bool has_clients = true; - bool has_devices = true; - while (has_clients || has_devices) { + bool has_devices = false; + while (has_clients) { uint32_t event_flags; ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags)); if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_LOGI(TAG, "No more clients"); - has_clients = false; + ESP_LOGI(TAG, "Get FLAGS_NO_CLIENTS"); if (ESP_OK == usb_host_device_free_all()) { - ESP_LOGI(TAG, "All devices marked as free"); + ESP_LOGI(TAG, "All devices marked as free, no need to wait FLAGS_ALL_FREE event"); + has_clients = false; } else { - ESP_LOGI(TAG, "Wait for the ALL FREE EVENT"); + ESP_LOGI(TAG, "Wait for the FLAGS_ALL_FREE"); + has_devices = true; } } - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - ESP_LOGI(TAG, "No more devices"); - has_devices = false; + if (has_devices && event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "Get FLAGS_ALL_FREE"); + has_clients = false; } - } - ESP_LOGI(TAG, "No more clients and devices"); + ESP_LOGI(TAG, "No more clients and devices, uninstall USB Host library"); //Uninstall the USB Host Library ESP_ERROR_CHECK(usb_host_uninstall()); @@ -167,7 +167,7 @@ void app_main(void) TaskHandle_t host_lib_task_hdl, class_driver_task_hdl; - //Create usb host lib task + // Create usb host lib task BaseType_t task_created; task_created = xTaskCreatePinnedToCore(usb_host_lib_task, "usb_host", @@ -178,19 +178,20 @@ void app_main(void) 0); assert(task_created == pdTRUE); - //Wait unit the USB host library is installed + // Wait unit the USB host library is installed ulTaskNotifyTake(false, 1000); - //Create class driver task + // Create class driver task task_created = xTaskCreatePinnedToCore(class_driver_task, "class", - 4096, + 5 * 1024, NULL, CLASS_TASK_PRIORITY, &class_driver_task_hdl, 0); assert(task_created == pdTRUE); - vTaskDelay(10); //Add a short delay to let the tasks run + // Add a short delay to let the tasks run + vTaskDelay(10); while (1) { if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY)) { @@ -198,22 +199,20 @@ void app_main(void) // User pressed button usb_host_lib_info_t lib_info; ESP_ERROR_CHECK(usb_host_lib_info(&lib_info)); - if (lib_info.num_devices == 0) { - // End while cycle - break; - } else { - ESP_LOGW(TAG, "To shutdown example, remove all USB devices and press button again."); - // Keep polling + if (lib_info.num_devices != 0) { + ESP_LOGW(TAG, "Shutdown with attached devices."); } + // End while cycle + break; } } } - //Deregister client + // Deregister client class_driver_client_deregister(); vTaskDelay(10); - //Delete the tasks + // Delete the tasks vTaskDelete(class_driver_task_hdl); vTaskDelete(host_lib_task_hdl); diff --git a/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults b/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults new file mode 100644 index 0000000000..bf1b553b23 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults @@ -0,0 +1,4 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_USB_HOST_HUBS_SUPPORTED=y From 79d7c2331569816aa8d8cd64d2969382120345bd Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 9 Oct 2024 18:40:50 +0800 Subject: [PATCH 3/5] fix(usb_host_lib): Returned task suspend --- .../peripherals/usb/host/usb_host_lib/main/class_driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c index 3b7077986f..5a89e40dea 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c @@ -206,7 +206,7 @@ void class_driver_task(void *arg) SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); if (mux_lock == NULL) { ESP_LOGE(TAG, "Unable to create class driver mutex"); - vTaskDelete(NULL); + vTaskSuspend(NULL); return; } @@ -256,7 +256,7 @@ void class_driver_task(void *arg) if (mux_lock != NULL) { vSemaphoreDelete(mux_lock); } - vTaskDelete(NULL); + vTaskSuspend(NULL); } void class_driver_client_deregister(void) From 63f9104669c6c8cb9f743492094c81a43f8768f8 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Mon, 21 Oct 2024 16:07:18 +0800 Subject: [PATCH 4/5] fix(ext_port): Added port recovery delay --- components/usb/Kconfig | 18 +++++++++++++++--- components/usb/ext_port.c | 38 ++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/components/usb/Kconfig b/components/usb/Kconfig index a527613c7f..376b7fc58c 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -133,7 +133,19 @@ menu "USB-OTG" The default value is 1. - config USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE + config USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS + int "Reset recovery delay in ms" + default 30 + help + After a port stops driving the reset signal, the USB 2.0 specification requires that + the "USB System Software guarantees a minimum of 10 ms for reset recovery" before the + attached device is expected to respond to data transfers (see USB 2.0 chapter 7.1.7.3 for + more details). + The device may ignore any data transfers during the recovery interval. + + The default value is set to 30 ms to be safe. + + config USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE bool "Custom bPwrOn2PwrGood value" default n help @@ -143,8 +155,8 @@ menu "USB-OTG" When enabled, applies the custom PwrOn2PwrGood delay. When disabled, applies the PwrOn2PwrGood value from the Hub Descriptor. - config USB_HOST_EXT_PORT_CUSTOM_RESET_MS - depends on USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE + config USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS + depends on USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE int "PwrOn2PwrGood delay in ms" default 100 range 0 5000 diff --git a/components/usb/ext_port.c b/components/usb/ext_port.c index 541eb60a50..efce9ed7de 100644 --- a/components/usb/ext_port.c +++ b/components/usb/ext_port.c @@ -25,9 +25,9 @@ #define EXT_PORT_RESET_ATTEMPTS 1 #endif // Delay in ms after sending the SetFeature() class specific request -#define EXT_PORT_RESET_CUSTOM_DELAY CONFIG_USB_HOST_EXT_PORT_CUSTOM_RESET_ENABLE -#define EXT_PORT_RESET_CUSTOM_DELAY_MS CONFIG_USB_HOST_EXT_PORT_CUSTOM_RESET_MS -#define EXT_PORT_RESET_DEFAULT_DELAY_MS 100 +#define EXT_PORT_RESET_RECOVERY_DELAY_MS CONFIG_USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS +#define EXT_PORT_POWER_ON_CUSTOM_DELAY CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE +#define EXT_PORT_POWER_ON_CUSTOM_DELAY_MS CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS /** * @brief External Port driver action flags @@ -80,7 +80,7 @@ struct ext_port_s { // Port related constant members ext_hub_handle_t ext_hub_hdl; /**< Ports' parent External Hub handle */ uint8_t port_num; /**< Ports' parent External Hub Port number */ - int reset_delay_ms; /**< Ports' Power on time to Power Good, ms */ + int power_on_delay_ms; /**< Ports' Power on time to Power Good, ms */ } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ }; @@ -262,9 +262,17 @@ static esp_err_t port_set_feature(ext_port_t *ext_port, const usb_hub_port_featu } // Every set feature requires status update ext_port->flags.status_outdated = 1; - // PowerOn to PowerGood delay for port - if (feature == USB_FEATURE_PORT_RESET) { - vTaskDelay(pdMS_TO_TICKS(ext_port->constant.reset_delay_ms)); + switch (feature) { + case USB_FEATURE_PORT_POWER: + // PowerOn to PowerGood delay for port + vTaskDelay(pdMS_TO_TICKS(ext_port->constant.power_on_delay_ms)); + break; + case USB_FEATURE_PORT_RESET: + // Port has reset, give the port some time to recover + vTaskDelay(pdMS_TO_TICKS(EXT_PORT_RESET_RECOVERY_DELAY_MS)); + break; + default: + break; } return ret; } @@ -451,13 +459,13 @@ static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t pa ext_port->constant.parent_dev_addr = parent_dev_addr; ext_port->constant.ext_hub_hdl = ext_hub_hdl; ext_port->constant.port_num = parent_port_num; -#if (EXT_PORT_RESET_CUSTOM_DELAY) - ext_port->constant.reset_delay_ms = EXT_PORT_RESET_CUSTOM_DELAY_MS; +#if (EXT_PORT_POWER_ON_CUSTOM_DELAY) + ext_port->constant.power_on_delay_ms = EXT_PORT_POWER_ON_CUSTOM_DELAY_MS; #else - ext_port->constant.reset_delay_ms = (port_delay_ms == 0) - ? EXT_PORT_RESET_DEFAULT_DELAY_MS - : port_delay_ms; -#endif // EXT_PORT_POWER_ON_CUSTOM + // We don't need any additional delay in case port_delay_ms == 0, because this usually means + // that parent Hub device has no power switches + ext_port->constant.power_on_delay_ms = port_delay_ms; +#endif // EXT_PORT_POWER_ON_CUSTOM_DELAY ext_port->state = USB_PORT_STATE_NOT_CONFIGURED; ext_port->dev_state = PORT_DEV_NOT_PRESENT; @@ -465,7 +473,7 @@ static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t pa ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port has been added (PwrOn2PwrGood=%d ms)", ext_port->constant.parent_dev_addr, ext_port->constant.port_num, - ext_port->constant.reset_delay_ms); + ext_port->constant.power_on_delay_ms); *port_obj = ext_port; return ESP_OK; @@ -555,8 +563,6 @@ static bool handle_port_status(ext_port_t *ext_port) ESP_LOGD(EXT_PORT_TAG, "[%d:%d] Port still in reset, wait and repeat get status...", ext_port->constant.parent_dev_addr, ext_port->constant.port_num); - // PowerOn to PowerGood delay for port - vTaskDelay(pdMS_TO_TICKS(ext_port->constant.reset_delay_ms)); port_request_status(ext_port); need_processing = true; } From b15e83f6e0ee4d4a7821ae51cb0b7376d17420c0 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Fri, 25 Oct 2024 16:10:32 +0800 Subject: [PATCH 5/5] refactor(usb_host): Removed error in enum when stalled, added hcd_dwc no more free channels --- components/usb/enum.c | 20 +++++++++++++------- components/usb/hcd_dwc.c | 2 ++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/usb/enum.c b/components/usb/enum.c index ec18294c51..2024c09ee5 100644 --- a/components/usb/enum.c +++ b/components/usb/enum.c @@ -765,9 +765,15 @@ static esp_err_t control_response_handling(enum_stage_t stage) usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer; if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(ENUM_TAG, "Bad transfer status %d: %s", - ctrl_xfer->status, - enum_stage_strings[stage]); + if (ctrl_xfer->status == USB_TRANSFER_STATUS_STALL && + stage >= ENUM_STAGE_CHECK_SHORT_LANGID_TABLE && + stage <= ENUM_STAGE_CHECK_FULL_SER_STR_DESC) { + // String Descriptor request could be STALLed, if the device doesn't have them + } else { + ESP_LOGE(ENUM_TAG, "Bad transfer status %d: %s", + ctrl_xfer->status, + enum_stage_strings[stage]); + } return ret; } @@ -1015,10 +1021,6 @@ static bool set_next_stage(bool last_stage_pass) next_stage = last_stage + 1; } } else { - ESP_LOGE(ENUM_TAG, "[%d:%d] %s FAILED", - p_enum_driver->single_thread.parent_dev_addr, - p_enum_driver->single_thread.parent_port_num, - enum_stage_strings[last_stage]); // These stages cannot fail assert(last_stage != ENUM_STAGE_SET_ADDR_RECOVERY && last_stage != ENUM_STAGE_SELECT_CONFIG && @@ -1055,6 +1057,10 @@ static bool set_next_stage(bool last_stage_pass) break; default: // Stage is not allowed to failed. Cancel enumeration. + ESP_LOGE(ENUM_TAG, "[%d:%d] %s FAILED", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + enum_stage_strings[last_stage]); next_stage = ENUM_STAGE_CANCEL; break; } diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 6095a7fb2f..8754f273cb 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -1824,6 +1824,8 @@ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pi bool chan_allocated = usb_dwc_hal_chan_alloc(port->hal, pipe->chan_obj, (void *) pipe); if (!chan_allocated) { HCD_EXIT_CRITICAL(); + // The only reason why alloc channel could return false is no more free channels + ESP_LOGE(HCD_DWC_TAG, "No more HCD channels available"); ret = ESP_ERR_NOT_SUPPORTED; goto err; }