diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 774177d033..9f678163cd 100644 --- a/components/usb/CMakeLists.txt +++ b/components/usb/CMakeLists.txt @@ -14,6 +14,7 @@ set(priv_requires esp_driver_gpio esp_mm) # usb_phy driver relies on gpio drive if(CONFIG_SOC_USB_OTG_SUPPORTED) list(APPEND srcs "hcd_dwc.c" + "enum.c" "hub.c" "usb_helpers.c" "usb_host.c" @@ -28,6 +29,10 @@ if(CONFIG_SOC_USB_OTG_SUPPORTED) list(APPEND priv_includes "private_include") endif() +if(CONFIG_USB_HOST_HUBS_SUPPORTED) + list(APPEND srcs "ext_hub.c") +endif() + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include} PRIV_INCLUDE_DIRS ${priv_includes} diff --git a/components/usb/Kconfig b/components/usb/Kconfig index ac71a7125e..1c333f870b 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -46,73 +46,85 @@ menu "USB-OTG" bool "Periodic OUT" endchoice - menu "Root Hub configuration" + menu "Hub Driver Configuration" - config USB_HOST_DEBOUNCE_DELAY_MS - int "Debounce delay in ms" - default 250 + menu "Root Port configuration" + + config USB_HOST_DEBOUNCE_DELAY_MS + int "Debounce delay in ms" + default 250 + help + On connection of a USB device, the USB 2.0 specification requires + a "debounce interval with a minimum duration of 100ms" to allow the connection to stabilize + (see USB 2.0 chapter 7.1.7.3 for more details). + During the debounce interval, no new connection/disconnection events are registered. + + The default value is set to 250 ms to be safe. + + config USB_HOST_RESET_HOLD_MS + int "Reset hold in ms" + default 30 + help + The reset signaling can be generated on any Hub or Host Controller port by request from + the USB System Software. The USB 2.0 specification requires that "the reset signaling must + be driven for a minimum of 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). + After the reset, the hub port will transition to the Enabled state (refer to Section 11.5). + + The default value is set to 30 ms to be safe. + + config USB_HOST_RESET_RECOVERY_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_SET_ADDR_RECOVERY_MS + int "SetAddress() recovery time in ms" + default 10 + help + "After successful completion of the Status stage, the device is allowed a SetAddress() + recovery interval of 2 ms. At the end of this interval, the device must be able to accept + Setup packets addressed to the new address. Also, at the end of the recovery interval, the + device must not respond to tokens sent to the old address (unless, of course, the old and new + address is the same)." See USB 2.0 chapter 9.2.6.3 for more details. + + The default value is set to 10 ms to be safe. + + endmenu #Root Hub configuration + + config USB_HOST_HUBS_SUPPORTED + bool "Support Hubs" + default n help - On connection of a USB device, the USB 2.0 specification requires a "debounce interval with a minimum - duration of 100ms" to allow the connection to stabilize (see USB 2.0 chapter 7.1.7.3 for more details). - During the debounce interval, no new connection/disconnection events are registered. + Enables support of external Hubs. - The default value is set to 250 ms to be safe. - - config USB_HOST_RESET_HOLD_MS - int "Reset hold in ms" - default 30 + config USB_HOST_HUB_MULTI_LEVEL + depends on USB_HOST_HUBS_SUPPORTED + bool "Support multiple Hubs" + default y help - The reset signaling can be generated on any Hub or Host Controller port by request from the USB System - Software. The USB 2.0 specification requires that "the reset signaling must be driven for a minimum of - 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). After the reset, the hub port will transition to - the Enabled state (refer to Section 11.5). + Enables support for connecting multiple Hubs simultaneously. - The default value is set to 30 ms to be safe. - - config USB_HOST_RESET_RECOVERY_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_SET_ADDR_RECOVERY_MS - int "SetAddress() recovery time in ms" - default 10 - help - "After successful completion of the Status stage, the device is allowed a SetAddress() recovery - interval of 2 ms. At the end of this interval, the device must be able to accept Setup packets - addressed to the new address. Also, at the end of the recovery interval, the device must not respond to - tokens sent to the old address (unless, of course, the old and new address is the same)." See USB 2.0 - chapter 9.2.6.3 for more details. - - The default value is set to 10 ms to be safe. - - endmenu #Root Hub configuration + endmenu #Hub Driver Configuration config USB_HOST_ENABLE_ENUM_FILTER_CALLBACK bool "Enable enumeration filter callback" default n help - The enumeration filter callback is called before enumeration of each newly attached device. This callback - allows users to control whether a device should be enumerated, and what configuration number to use when - enumerating a device. + The enumeration filter callback is called before enumeration of each newly attached device. + This callback allows users to control whether a device should be enumerated, and what configuration + number to use when enumerating a device. If enabled, the enumeration filter callback can be set via 'usb_host_config_t' when calling 'usb_host_install()'. - config USB_HOST_EXT_HUB_SUPPORT - depends on IDF_EXPERIMENTAL_FEATURES - bool "Support USB HUB (Experimental)" - default n - help - Feature is under development. - # Hidden or compatibility options config USB_OTG_SUPPORTED # Invisible config kept for compatibility diff --git a/components/usb/enum.c b/components/usb/enum.c new file mode 100644 index 0000000000..29711ee9f1 --- /dev/null +++ b/components/usb/enum.c @@ -0,0 +1,1379 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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 "usbh.h" +#include "enum.h" +#include "usb/usb_helpers.h" + +#define SET_ADDR_RECOVERY_INTERVAL_MS CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS + +#define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE +#define ENUM_INIT_VALUE_DEV_ADDR 1 // Init value for device address +#define ENUM_DEFAULT_CONFIGURATION_VALUE 1 // Default configuration value for SetConfiguration() request +#define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) +#define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device +#define ENUM_WORST_CASE_MPS_FS_HS 64 // The worst case MPS of EP0 for a FS/HS device +#define ENUM_LANGID 0x409 // Current enumeration only supports English (United States) string descriptors +#define ENUM_MAX_ADDRESS (127) // Maximal device address value + +/** + * @brief Stages of device enumeration listed in their order of execution + * + * Entry: + * - These stages MUST BE LISTED IN THE ORDER OF THEIR EXECUTION as the enumeration will simply increment the current stage + * - If an error occurs at any stage, ENUM_STAGE_CANCEL acts as a common exit stage on failure + * - Must start with 0 as enum is also used as an index + * - The short descriptor stages are used to fetch the start particular descriptors that don't have a fixed length in order to determine the full descriptors length + * - Any state of Get String Descriptor could be STALLed by the device. In that case we just don't fetch them and treat enumeration as successful + */ +typedef enum { + ENUM_STAGE_IDLE = 0, /**< There is no device awaiting enumeration */ + // Basic Device enumeration + ENUM_STAGE_GET_SHORT_DEV_DESC, /**< Getting short dev desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ + ENUM_STAGE_CHECK_SHORT_DEV_DESC, /**< Save bMaxPacketSize0 from the short dev desc. Update the MPS of the enum pipe */ + ENUM_STAGE_SECOND_RESET, /**< Reset the device again (Workaround for old USB devices that get confused by the previous short dev desc request). */ + ENUM_STAGE_SECOND_RESET_COMPLETE, /**< Reset completed, re-trigger the FSM */ + ENUM_STAGE_SET_ADDR, /**< Send SET_ADDRESS request */ + ENUM_STAGE_CHECK_ADDR, /**< Update the enum pipe's target address */ + ENUM_STAGE_SET_ADDR_RECOVERY, /**< Wait SET ADDRESS recovery interval at least for 2ms due to usb_20, chapter 9.2.6.3 */ + ENUM_STAGE_GET_FULL_DEV_DESC, /**< Get the full dev desc */ + ENUM_STAGE_CHECK_FULL_DEV_DESC, /**< Check the full dev desc, fill it into the device object in USBH. Save the string descriptor indexes*/ + ENUM_STAGE_SELECT_CONFIG, /**< Select configuration: select default ENUM_DEFAULT_CONFIGURATION_VALUE value or use callback if ENABLE_ENUM_FILTER_CALLBACK enabled */ + ENUM_STAGE_GET_SHORT_CONFIG_DESC, /**< Getting a short config desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ + ENUM_STAGE_CHECK_SHORT_CONFIG_DESC, /**< Save wTotalLength of the short config desc */ + ENUM_STAGE_GET_FULL_CONFIG_DESC, /**< Get the full config desc (wLength is the saved wTotalLength) */ + ENUM_STAGE_CHECK_FULL_CONFIG_DESC, /**< Check the full config desc, fill it into the device object in USBH */ + // Get String Descriptors + ENUM_STAGE_GET_SHORT_LANGID_TABLE, /**< Get the header of the LANGID table string descriptor */ + ENUM_STAGE_CHECK_SHORT_LANGID_TABLE, /**< Save the bLength of the LANGID table string descriptor */ + ENUM_STAGE_GET_FULL_LANGID_TABLE, /**< Get the full LANGID table string descriptor */ + ENUM_STAGE_CHECK_FULL_LANGID_TABLE, /**< Check whether ENUM_LANGID is in the LANGID table */ + ENUM_STAGE_GET_SHORT_MANU_STR_DESC, /**< Get the header of the iManufacturer string descriptor */ + ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC, /**< Save the bLength of the iManufacturer string descriptor */ + ENUM_STAGE_GET_FULL_MANU_STR_DESC, /**< Get the full iManufacturer string descriptor */ + ENUM_STAGE_CHECK_FULL_MANU_STR_DESC, /**< Check and fill the full iManufacturer string descriptor */ + ENUM_STAGE_GET_SHORT_PROD_STR_DESC, /**< Get the header of the string descriptor at index iProduct */ + ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC, /**< Save the bLength of the iProduct string descriptor */ + ENUM_STAGE_GET_FULL_PROD_STR_DESC, /**< Get the full iProduct string descriptor */ + ENUM_STAGE_CHECK_FULL_PROD_STR_DESC, /**< Check and fill the full iProduct string descriptor */ + ENUM_STAGE_GET_SHORT_SER_STR_DESC, /**< Get the header of the string descriptor at index iSerialNumber */ + ENUM_STAGE_CHECK_SHORT_SER_STR_DESC, /**< Save the bLength of the iSerialNumber string descriptor */ + ENUM_STAGE_GET_FULL_SER_STR_DESC, /**< Get the full iSerialNumber string descriptor */ + ENUM_STAGE_CHECK_FULL_SER_STR_DESC, /**< Check and fill the full iSerialNumber string descriptor */ + // Set Configuration + ENUM_STAGE_SET_CONFIG, /**< Send SET_CONFIGURATION request */ + ENUM_STAGE_CHECK_CONFIG, /**< Check that SET_CONFIGURATION request was successful */ + // Terminal stages + ENUM_STAGE_COMPLETE, /**< Successful enumeration complete. */ + ENUM_STAGE_CANCEL, /**< Cancel enumeration. Free device resources */ +} enum_stage_t; + +const char *const enum_stage_strings[] = { + "IDLE", + "GET_SHORT_DEV_DESC", + "CHECK_SHORT_DEV_DESC", + "SECOND_RESET", + "SECOND_RESET_COMPLETE", + "SET_ADDR", + "CHECK_ADDR", + "SET_ADDR_RECOVERY", + "GET_FULL_DEV_DESC", + "CHECK_FULL_DEV_DESC", + "SELECT_CONFIG", + "GET_SHORT_CONFIG_DESC", + "CHECK_SHORT_CONFIG_DESC", + "GET_FULL_CONFIG_DESC", + "CHECK_FULL_CONFIG_DESC", + "GET_SHORT_LANGID_TABLE", + "CHECK_SHORT_LANGID_TABLE", + "GET_FULL_LANGID_TABLE", + "CHECK_FULL_LANGID_TABLE", + "GET_SHORT_MANU_STR_DESC", + "CHECK_SHORT_MANU_STR_DESC", + "GET_FULL_MANU_STR_DESC", + "CHECK_FULL_MANU_STR_DESC", + "GET_SHORT_PROD_STR_DESC", + "CHECK_SHORT_PROD_STR_DESC", + "GET_FULL_PROD_STR_DESC", + "CHECK_FULL_PROD_STR_DESC", + "GET_SHORT_SER_STR_DESC", + "CHECK_SHORT_SER_STR_DESC", + "GET_FULL_SER_STR_DESC", + "CHECK_FULL_SER_STR_DESC", + "SET_CONFIG", + "CHECK_CONFIG", + "COMPLETE", + "CANCEL", +}; + +typedef struct { + // Constant + uint8_t new_dev_addr; /**< Device address that should be assigned during enumeration */ + uint8_t bMaxPacketSize0; /**< Max packet size of the device's EP0. Read from bMaxPacketSize0 field of device descriptor */ + uint16_t wTotalLength; /**< Total length of device's configuration descriptor. Read from wTotalLength field of config descriptor */ + uint8_t iManufacturer; /**< Index of the Manufacturer string descriptor */ + uint8_t iProduct; /**< Index of the Product string descriptor */ + uint8_t iSerialNumber; /**< Index of the Serial Number string descriptor */ + uint8_t str_desc_bLength; /**< Saved bLength from getting a short string descriptor */ + uint8_t bConfigurationValue; /**< Device's current configuration number */ +} enum_device_params_t; + +typedef struct { + struct { + // Device related objects, initialized at start of a particular enumeration + unsigned int dev_uid; /**< Unique device ID being enumerated */ + usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */ + // Parent info for optimization and more clean debug output + usb_device_handle_t parent_dev_hdl; /**< Device's parent handle */ + uint8_t parent_dev_addr; /**< Device's parent address */ + uint8_t parent_port_num; /**< Device's parent port number */ + // Parameters, updated during enumeration + enum_stage_t stage; /**< Current enumeration stage */ + enum_device_params_t dev_params; /**< Parameters of device under enumeration */ + int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */ + uint8_t next_dev_addr; /**< Device address for device under enumeration */ + } single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */ + + struct { + // Internal objects + urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */ + // Callbacks + usb_proc_req_cb_t proc_req_cb; /**< USB Host process request callback. Refer to proc_req_callback() in usb_host.c */ + void *proc_req_cb_arg; /**< USB Host process request callback argument */ + enum_event_cb_t enum_event_cb; /**< Enumeration driver event callback */ + void *enum_event_cb_arg; /**< Enumeration driver event callback argument */ +#if ENABLE_ENUM_FILTER_CALLBACK + usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ + void *enum_filter_cb_arg; /**< Set device configuration callback argument */ +#endif // ENABLE_ENUM_FILTER_CALLBACK + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +} enum_driver_t; + +static enum_driver_t *p_enum_driver = NULL; + +const char *ENUM_TAG = "ENUM"; + +// ----------------------------------------------------------------------------- +// ---------------------------- Helpers ---------------------------------------- +// ----------------------------------------------------------------------------- +#define ENUM_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +// ----------------------------------------------------------------------------- +// ------------------------ Private functions ---------------------------------- +// ----------------------------------------------------------------------------- +static inline uint8_t get_next_dev_addr(void) +{ + uint8_t ret = 0; + + p_enum_driver->single_thread.next_dev_addr++; + if (p_enum_driver->single_thread.next_dev_addr > ENUM_MAX_ADDRESS) { + p_enum_driver->single_thread.next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR; + } + ret = p_enum_driver->single_thread.next_dev_addr; + + return ret; +} + +static uint8_t get_next_free_dev_addr(void) +{ + usb_device_handle_t dev_hdl; + uint8_t new_dev_addr = p_enum_driver->single_thread.next_dev_addr; + + while (1) { + if (usbh_devs_open(new_dev_addr, &dev_hdl) == ESP_ERR_NOT_FOUND) { + break; + } + // We have a device with the same address on a bus, close device and request new addr + usbh_dev_close(dev_hdl); + new_dev_addr = get_next_dev_addr(); + } + // Sanity check + assert(new_dev_addr != 0); + return new_dev_addr; +} + +/** + * @brief Get Configuration descriptor index + * + * For Configuration descriptor bConfigurationValue and index are not the same and + * should be different for SetConfiguration() and GetDescriptor() requests. + * GetDescriptor(): index from 0 to one less than the bNumConfigurations (refer to section 9.4.3 Get Descriptor) + * SetConfiguration(): bConfigurationValue field used as a parameter to the SetConfiguration() request. (refer to section 9.6.3 Configuration) + * + * @return uint8_t + */ +static inline uint8_t get_configuration_descriptor_index(uint8_t bConfigurationValue) +{ + return (bConfigurationValue == 0) ? bConfigurationValue : (bConfigurationValue - 1); +} + +/** + * @brief Select active configuration + * + * During enumeration process, device objects could have several configuration that can be activated + * To be able to select configuration this call should be used + * This will call the enumeration filter callback (if enabled) and set the bConfigurationValue for the upcoming SetConfiguration() command + * + * @return esp_err_t + */ +static esp_err_t select_active_configuration(void) +{ + // This configuration value must be zero or match a configuration value from a configuration descriptor. + // If the configuration value is zero, the device is placed in its Address state. + // But some devices STALLed get configuration descriptor with bConfigurationValue = 1, even they have one configuration with bValue = 1. + uint8_t bConfigurationValue = ENUM_DEFAULT_CONFIGURATION_VALUE; + +#if ENABLE_ENUM_FILTER_CALLBACK + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + const usb_device_desc_t *dev_desc; + ESP_ERROR_CHECK(usbh_dev_get_desc(dev_hdl, &dev_desc)); + + bool enum_proceed = false; + // Sanity check + assert(dev_desc); + + if (p_enum_driver->constant.enum_filter_cb) { + enum_proceed = p_enum_driver->constant.enum_filter_cb(dev_desc, &bConfigurationValue); + } + + // User's request NOT to enumerate the USB device + if (!enum_proceed) { + ESP_LOGW(ENUM_TAG, "[%d:%d] Abort request of enumeration process (%#x:%#x)", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + dev_desc->idProduct, + dev_desc->idVendor); + enum_cancel(p_enum_driver->single_thread.dev_uid); + return ESP_OK; + } + + // Set configuration descriptor + if ((bConfigurationValue == 0) || (bConfigurationValue > dev_desc->bNumConfigurations)) { + ESP_LOGE(ENUM_TAG, "Invalid bConfigurationValue (%d) provided by user, using default", bConfigurationValue); + bConfigurationValue = ENUM_DEFAULT_CONFIGURATION_VALUE; + } +#endif // ENABLE_ENUM_FILTER_CALLBACK + + ESP_LOGD(ENUM_TAG, "Selected bConfigurationValue=%d", bConfigurationValue); + p_enum_driver->single_thread.dev_params.bConfigurationValue = bConfigurationValue; + return ESP_OK; +} + +static esp_err_t second_reset_request(void) +{ + // Notify USB Host + enum_event_data_t event_data = { + .event = ENUM_EVENT_RESET_REQUIRED, + .reset_req = { + .parent_dev_hdl = p_enum_driver->single_thread.parent_dev_hdl, + .parent_port_num = p_enum_driver->single_thread.parent_port_num, + }, + }; + p_enum_driver->constant.enum_event_cb(&event_data, p_enum_driver->constant.enum_event_cb_arg); + + return ESP_OK; +} + +/** + * @brief Get index and langid + * + * Returns index and langid, based on enumerator stage. + * + * @param[in] stage Stage + * @param[out] index String index + * @param[out] langid String langid + */ +static inline void get_index_langid_for_stage(enum_stage_t stage, uint8_t *index, uint16_t *langid) +{ + switch (stage) { + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + *index = 0; // The LANGID table uses an index of 0 + *langid = 0; // Getting the LANGID table itself should use a LANGID of 0 + break; + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + *index = p_enum_driver->single_thread.dev_params.iManufacturer; + *langid = ENUM_LANGID; // Use the default LANGID + break; + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + *index = p_enum_driver->single_thread.dev_params.iProduct; + *langid = ENUM_LANGID; // Use the default LANGID + break; + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + *index = p_enum_driver->single_thread.dev_params.iSerialNumber; + *langid = ENUM_LANGID; // Use the default LANGID + break; + default: + // Should not occur + abort(); + break; + } +} + +/** + * @brief Control request: General + * + * Prepares the Control request byte-data transfer for current stage of the enumerator + * + * @param[in] stage Enumeration stage + */ +static void control_request_general(enum_stage_t stage) +{ + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + uint8_t ctrl_ep_mps = p_enum_driver->single_thread.dev_params.bMaxPacketSize0; + uint16_t wTotalLength = p_enum_driver->single_thread.dev_params.wTotalLength; + uint8_t bConfigurationValue = p_enum_driver->single_thread.dev_params.bConfigurationValue; + uint8_t desc_index = get_configuration_descriptor_index(bConfigurationValue); + + switch (stage) { + case ENUM_STAGE_GET_SHORT_DEV_DESC: { + // Initialize a short device descriptor request + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)transfer->data_buffer); + ((usb_setup_packet_t *)transfer->data_buffer)->wLength = ENUM_SHORT_DESC_REQ_LEN; + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(ENUM_SHORT_DESC_REQ_LEN, ctrl_ep_mps); + // IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + ENUM_SHORT_DESC_REQ_LEN; + break; + } + case ENUM_STAGE_SET_ADDR: { + p_enum_driver->single_thread.dev_params.new_dev_addr = get_next_free_dev_addr(); + USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)transfer->data_buffer, p_enum_driver->single_thread.dev_params.new_dev_addr); + transfer->num_bytes = sizeof(usb_setup_packet_t); // No data stage + p_enum_driver->single_thread.expect_num_bytes = 0; // OUT transfer. No need to check number of bytes returned + break; + } + case ENUM_STAGE_GET_FULL_DEV_DESC: { + USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_device_desc_t), ctrl_ep_mps); + // IN data stage should return exactly sizeof(usb_device_desc_t) bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t); + break; + } + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: { + // Get a short config descriptor at descriptor index + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, desc_index, ENUM_SHORT_DESC_REQ_LEN); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(ENUM_SHORT_DESC_REQ_LEN, ctrl_ep_mps); + // IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + ENUM_SHORT_DESC_REQ_LEN; + break; + } + case ENUM_STAGE_GET_FULL_CONFIG_DESC: { + // Get the full configuration descriptor at descriptor index, requesting its exact length. + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, desc_index, wTotalLength); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(wTotalLength, ctrl_ep_mps); + // IN data stage should return exactly wTotalLength bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + wTotalLength; + break; + } + case ENUM_STAGE_SET_CONFIG: { + USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)transfer->data_buffer, bConfigurationValue); + transfer->num_bytes = sizeof(usb_setup_packet_t); // No data stage + p_enum_driver->single_thread.expect_num_bytes = 0; // OUT transfer. No need to check number of bytes returned + break; + } + default: + // Should never occur + p_enum_driver->single_thread.expect_num_bytes = 0; + abort(); + break; + } +} + +/** + * @brief Control request: String + * + * Prepares the Control request string-data transfer for current stage of the enumerator + * + * @param[in] stage Enumeration stage + */ +static void control_request_string(enum_stage_t stage) +{ + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + uint8_t ctrl_ep_mps = p_enum_driver->single_thread.dev_params.bMaxPacketSize0; + uint8_t bLength = p_enum_driver->single_thread.dev_params.str_desc_bLength; + uint8_t index = 0; + uint16_t langid = 0; + + get_index_langid_for_stage(stage, &index, &langid); + + switch (stage) { + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: { + // Get only the header of the string descriptor + USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)transfer->data_buffer, index, langid, sizeof(usb_str_desc_t)); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_str_desc_t), ctrl_ep_mps); + // IN data stage should return exactly sizeof(usb_str_desc_t) bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_str_desc_t); + break; + } + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: { + // Get the full string descriptor at a particular index, requesting the descriptors exact length + USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)transfer->data_buffer, index, langid, bLength); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(bLength, ctrl_ep_mps); + // IN data stage should return exactly str_desc_bLength bytes + p_enum_driver->single_thread.expect_num_bytes = sizeof(usb_setup_packet_t) + bLength; + break; + } + default: + // Should never occur + p_enum_driver->single_thread.expect_num_bytes = 0; + abort(); + break; + } +} + +/** + * @brief Parse short Device descriptor + * + * Parses short device descriptor response + * Configures the EP0 MPS for device object under enumeration + */ +static esp_err_t parse_short_dev_desc(void) +{ + esp_err_t ret = ESP_OK; + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer; + const usb_device_desc_t *dev_desc = (usb_device_desc_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + // Check if the returned descriptor has correct type + if (dev_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_DEVICE) { + ESP_LOGE(ENUM_TAG, "Short dev desc has wrong bDescriptorType"); + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + // Update and save actual MPS of the default pipe + ret = usbh_dev_set_ep0_mps(dev_hdl, dev_desc->bMaxPacketSize0); + if (ret != ESP_OK) { + ESP_LOGE(ENUM_TAG, "Failed to update MPS"); + goto exit; + } + // Save the actual MPS of EP0 in enum driver context + p_enum_driver->single_thread.dev_params.bMaxPacketSize0 = dev_desc->bMaxPacketSize0; + +exit: + return ret; +} + +static esp_err_t check_addr(void) +{ + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + uint8_t assign_addr = p_enum_driver->single_thread.dev_params.new_dev_addr; + + ESP_LOGD(ENUM_TAG, "Assign address (dev_addr=%d)", assign_addr); + + esp_err_t ret = usbh_dev_set_addr(dev_hdl, assign_addr); + if (ret != ESP_OK) { + ESP_LOGE(ENUM_TAG, "Error during assign device address"); + } + + return ret; +} + +/** + * @brief Parse full Device descriptor response + * + * Parses full device descriptor response + * Set device descriptor for device object under enumeration + */ +static esp_err_t parse_full_dev_desc(void) +{ + esp_err_t ret = ESP_OK; + + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer; + const usb_device_desc_t *dev_desc = (usb_device_desc_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + // Check if the returned descriptor has correct type + if (dev_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_DEVICE) { + ESP_LOGE(ENUM_TAG, "Full dev desc has wrong bDescriptorType"); + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + // Save string parameters + p_enum_driver->single_thread.dev_params.iManufacturer = dev_desc->iManufacturer; + p_enum_driver->single_thread.dev_params.iProduct = dev_desc->iProduct; + p_enum_driver->single_thread.dev_params.iSerialNumber = dev_desc->iSerialNumber; + + // Device has more than one configuration + if (dev_desc->bNumConfigurations > 1) { + ESP_LOGW(ENUM_TAG, "Device has more than 1 configuration"); + } + + // Allocate Device descriptor and set it's value to device object + ret = usbh_dev_set_desc(dev_hdl, dev_desc); + +exit: + return ret; +} + +/** + * @brief Parse short Configuration descriptor + * + * Parses short Configuration descriptor response + * Set the length to request full Configuration descriptor + */ +static esp_err_t parse_short_config_desc(void) +{ + esp_err_t ret; + usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer; + const usb_config_desc_t *config_desc = (usb_config_desc_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + // Check if the returned descriptor is corrupted + if (config_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_CONFIGURATION) { + ESP_LOGE(ENUM_TAG, "Short config desc has wrong bDescriptorType"); + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + +#if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT16_MAX) // Suppress -Wtype-limits warning due to uint16_t wTotalLength + // Check if the descriptor is too long to be supported + if (config_desc->wTotalLength > ENUM_CTRL_TRANSFER_MAX_DATA_LEN) { + ESP_LOGE(ENUM_TAG, "Configuration descriptor larger than control transfer max length"); + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } +#endif + // Set the configuration descriptor's full length + p_enum_driver->single_thread.dev_params.wTotalLength = config_desc->wTotalLength; + ret = ESP_OK; + +exit: + return ret; +} + +/** + * @brief Parse full Configuration descriptor + * + * Parses full Configuration descriptor response + * Set the Configuration descriptor to device object under enumeration + */ +static esp_err_t parse_full_config_desc(void) +{ + esp_err_t ret; + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer; + const usb_config_desc_t *config_desc = (usb_config_desc_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + // Check if the returned descriptor is corrupted + if (config_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_CONFIGURATION) { + ESP_LOGE(ENUM_TAG, "Full config desc has wrong bDescriptorType"); + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + // Allocate Configuration descriptor and set it's value to device object + ret = usbh_dev_set_config_desc(dev_hdl, config_desc); + +exit: + return ret; +} + +/** + * @brief Parse short String descriptor + * + * Parses short String descriptor response + * Set the length to request full String descriptor + */ +static esp_err_t parse_short_str_desc(void) +{ + esp_err_t ret; + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + const usb_str_desc_t *str_desc = (usb_str_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + + //Check if the returned descriptor is supported or corrupted + if (str_desc->bDescriptorType == 0) { + ESP_LOGE(ENUM_TAG, "String desc not supported"); + ret = ESP_ERR_NOT_SUPPORTED; + goto exit; + } else if (str_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_STRING) { + ESP_LOGE(ENUM_TAG, "Short string desc corrupt"); + ret = ESP_ERR_INVALID_RESPONSE; + goto exit; + } +#if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT8_MAX) //Suppress -Wtype-limits warning due to uint8_t bLength + //Check if the descriptor is too long to be supported + if (str_desc->bLength > (uint32_t)ENUM_CTRL_TRANSFER_MAX_DATA_LEN) { + ESP_LOGE(ENUM_TAG, "String descriptor larger than control transfer max length"); + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } +#endif + // Set the descriptor's full length + p_enum_driver->single_thread.dev_params.str_desc_bLength = str_desc->bLength; + ret = ESP_OK; + +exit: + return ret; +} + +/** + * @brief Parse Language ID table + * + * Parses Language ID table response + * Searches Language ID table for LangID = 0x0409 + */ +static esp_err_t parse_langid_table(void) +{ + esp_err_t ret; + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + const usb_str_desc_t *str_desc = (usb_str_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + + //Scan the LANGID table for our target LANGID + ret = ESP_ERR_NOT_FOUND; + int langid_table_num_entries = (str_desc->bLength - sizeof(usb_str_desc_t)) / 2; // Each LANGID is 2 bytes + for (int i = 0; i < langid_table_num_entries; i++) { // Each LANGID is 2 bytes + if (str_desc->wData[i] == ENUM_LANGID) { + ret = ESP_OK; + break; + } + } + if (ret != ESP_OK) { + ESP_LOGE(ENUM_TAG, "LANGID %#x not found", ENUM_LANGID); + } + + return ret; +} + +/** + * @brief Get String index number + * + * Returns string number index (0, 1 or 2) based on stage + */ +static inline int get_str_index(enum_stage_t stage) +{ + switch (stage) { + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + return 0; + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + return 1; + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + return 2; + default: + break; + } + // Should never occurred + abort(); + return -1; +} + +/** + * @brief Parse full String descriptor + * + * Set String descriptor to the device object under enumeration + */ +static esp_err_t parse_full_str_desc(void) +{ + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + const usb_str_desc_t *str_desc = (usb_str_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + + return usbh_dev_set_str_desc(dev_hdl, str_desc, get_str_index(p_enum_driver->single_thread.stage)); +} + +static esp_err_t check_config(void) +{ + // Nothing to parse after a SET_CONFIG request + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// ---------------------- Stage handle functions ------------------------------- +// ----------------------------------------------------------------------------- + +/** + * @brief Control request stage + * + * Based on the stage, does prepare General or String Control request + * + * @param[in] stage Enumeration stage + */ +static esp_err_t control_request(enum_stage_t stage) +{ + esp_err_t ret; + + switch (stage) { + case ENUM_STAGE_GET_SHORT_DEV_DESC: + case ENUM_STAGE_SET_ADDR: + case ENUM_STAGE_GET_FULL_DEV_DESC: + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: + case ENUM_STAGE_GET_FULL_CONFIG_DESC: + case ENUM_STAGE_SET_CONFIG: + control_request_general(stage); + break; + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + control_request_string(stage); + break; + default: // Should never occur + ret = ESP_ERR_INVALID_STATE; + abort(); + break; + } + + ret = usbh_dev_submit_ctrl_urb(p_enum_driver->single_thread.dev_hdl, p_enum_driver->constant.urb); + if (ret != ESP_OK) { + ESP_LOGE(ENUM_TAG, "[%d:%d] Control transfer submit error (%#x), stage '%s'", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + ret, + enum_stage_strings[stage]); + } + + return ret; +} + +/** + * @brief Control request response handling stage + * + * Based on the stage, does parse the response data + * + * @param[in] stage Enumeration stage + */ +static esp_err_t control_response_handling(enum_stage_t stage) +{ + esp_err_t ret = ESP_FAIL; + // Check transfer status + int expected_num_bytes = p_enum_driver->single_thread.expect_num_bytes; + 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]); + return ret; + } + + // Transfer completed, verbose data in ESP_LOG_VERBOSE is set + ESP_LOG_BUFFER_HEXDUMP(ENUM_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + + // Check Control IN transfer returned the expected correct number of bytes + if (expected_num_bytes != 0 && expected_num_bytes != ctrl_xfer->actual_num_bytes) { + ESP_LOGW(ENUM_TAG, "[%d:%d] Unexpected (%d) device response length (expected %d)", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + ctrl_xfer->actual_num_bytes, + expected_num_bytes); + if (ctrl_xfer->actual_num_bytes < expected_num_bytes) { + // The device returned less bytes than requested. We cannot continue. + ESP_LOGE(ENUM_TAG, "Device returned less bytes than requested"); + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } + // The device returned more bytes than requested. + // This violates the USB specs chapter 9.3.5, but we can continue + } + + switch (stage) { + case ENUM_STAGE_CHECK_SHORT_DEV_DESC: + ret = parse_short_dev_desc(); + break; + case ENUM_STAGE_CHECK_ADDR: + ret = check_addr(); + break; + case ENUM_STAGE_CHECK_FULL_DEV_DESC: + ret = parse_full_dev_desc(); + break; + case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: + ret = parse_short_config_desc(); + break; + case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: + ret = parse_full_config_desc(); + break; + case ENUM_STAGE_CHECK_CONFIG: + ret = check_config(); + break; + case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: + case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: + ret = parse_short_str_desc(); + break; + case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: + ret = parse_langid_table(); + break; + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + ret = parse_full_str_desc(); + break; + default: + // Should never occurred + ret = ESP_ERR_INVALID_STATE; + abort(); + break; + } + +exit: + return ret; +} + +/** + * @brief Cancel stage + * + * Force shutdown device object under enumeration + */ +static esp_err_t stage_cancel(void) +{ + // There should be device under enumeration + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + usb_device_handle_t parent_dev_hdl = p_enum_driver->single_thread.parent_dev_hdl; + uint8_t parent_port_num = p_enum_driver->single_thread.parent_port_num; + + if (dev_hdl) { + ESP_ERROR_CHECK(usbh_dev_enum_unlock(dev_hdl)); + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + } + + // Clean up variables device from enumerator + p_enum_driver->single_thread.dev_uid = 0; + p_enum_driver->single_thread.dev_hdl = NULL; + p_enum_driver->single_thread.parent_dev_hdl = NULL; + p_enum_driver->single_thread.parent_dev_addr = 0; + p_enum_driver->single_thread.parent_port_num = 0; + + p_enum_driver->constant.urb->transfer.context = NULL; + + // Propagate the event + enum_event_data_t event_data = { + .event = ENUM_EVENT_CANCELED, + .canceled = { + .parent_dev_hdl = parent_dev_hdl, + .parent_port_num = parent_port_num, + }, + }; + p_enum_driver->constant.enum_event_cb(&event_data, p_enum_driver->constant.enum_event_cb_arg); + return ESP_OK; +} + +/** + * @brief Complete stage + * + * Closes device object under enumeration + */ +static esp_err_t stage_complete(void) +{ + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + usb_device_handle_t parent_dev_hdl = p_enum_driver->single_thread.parent_dev_hdl; + uint8_t parent_dev_addr = p_enum_driver->single_thread.parent_dev_addr; + uint8_t parent_port_num = p_enum_driver->single_thread.parent_port_num; + uint8_t dev_addr = 0; + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr)); + + // Close device + ESP_ERROR_CHECK(usbh_dev_enum_unlock(dev_hdl)); + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + + // Release device from enumerator + p_enum_driver->single_thread.dev_uid = 0; + p_enum_driver->single_thread.dev_hdl = NULL; + p_enum_driver->single_thread.parent_dev_hdl = NULL; + p_enum_driver->single_thread.parent_dev_addr = 0; + p_enum_driver->single_thread.parent_port_num = 0; + + // Release device from enumerator + p_enum_driver->constant.urb->transfer.context = NULL; + + // Flush device params + memset(&p_enum_driver->single_thread.dev_params, 0, sizeof(enum_device_params_t)); + p_enum_driver->single_thread.expect_num_bytes = 0; + + // Increase device address to use new value during the next enumeration process + get_next_dev_addr(); + + ESP_LOGD(ENUM_TAG, "[%d:%d] Processing complete, new device address %d", + parent_dev_addr, + parent_port_num, + dev_addr); + + enum_event_data_t event_data = { + .event = ENUM_EVENT_COMPLETED, + .complete = { + .dev_hdl = dev_hdl, + .dev_addr = dev_addr, + .parent_dev_hdl = parent_dev_hdl, + .parent_port_num = parent_port_num, + }, + }; + p_enum_driver->constant.enum_event_cb(&event_data, p_enum_driver->constant.enum_event_cb_arg); + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// -------------------------- State Machine ------------------------------------ +// ----------------------------------------------------------------------------- + +/** + * @brief Returns the process requirement flag for the stage + * + * When stage doesn't have a control transfer callback which request processing, request process should be triggered immediately + * + * @param[in] stage Enumeration process stage + * @return Processing for the stage is: + * @retval true Required + * @retval false Not required + */ +static bool stage_need_process(enum_stage_t stage) +{ + bool need_process_cb = false; + + switch (stage) { + // Transfer submission stages + // Stages have transfer completion callback which will re-trigger the processing + case ENUM_STAGE_GET_SHORT_DEV_DESC: + case ENUM_STAGE_SET_ADDR: + case ENUM_STAGE_GET_FULL_DEV_DESC: + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: + case ENUM_STAGE_GET_FULL_CONFIG_DESC: + case ENUM_STAGE_SET_CONFIG: + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + // Other stages + // Stages, require the re-triggering the processing + case ENUM_STAGE_SECOND_RESET: + case ENUM_STAGE_SET_ADDR_RECOVERY: + case ENUM_STAGE_SELECT_CONFIG: + case ENUM_STAGE_COMPLETE: + case ENUM_STAGE_CANCEL: + need_process_cb = true; + break; + default: + break; + } + + return need_process_cb; +} + +/** + * @brief Set next stage + * + * Does set next stage, based on the successful completion of last stage + * Some stages (i.e., string descriptors) are skipped if the device doesn't support them + * Some stages (i.e. string descriptors) are allowed to fail + * + * @param[in] last_stage_pass Flag of successful completion last stage + * + * @return Processing for the next stage is: + * @retval true Required + * @retval false Not required + */ +static bool set_next_stage(bool last_stage_pass) +{ + enum_stage_t last_stage = p_enum_driver->single_thread.stage; + enum_stage_t next_stage; + + while (1) { + bool stage_skip = false; + + // Find the next stage + if (last_stage_pass) { + // Last stage was successful + ESP_LOGD(ENUM_TAG, "[%d:%d] %s OK", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + enum_stage_strings[last_stage]); + // Get next stage + if (last_stage == ENUM_STAGE_COMPLETE || + last_stage == ENUM_STAGE_CANCEL) { + // Stages are terminal, move state machine to IDLE + next_stage = ENUM_STAGE_IDLE; + } else { + // Simply increment to get the next stage + 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 && + last_stage != ENUM_STAGE_SECOND_RESET && + last_stage != ENUM_STAGE_SECOND_RESET_COMPLETE && + last_stage != ENUM_STAGE_COMPLETE && + last_stage != ENUM_STAGE_CANCEL); + + // Last stage failed + switch (last_stage) { + // Stages that are allowed to fail skip to the next appropriate stage + case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: + case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: + // Couldn't get LANGID, skip the rest of the string descriptors + case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + // iSerialNumber string failed. Jump to Set Configuration and complete enumeration process. + next_stage = ENUM_STAGE_SET_CONFIG; + break; + case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + // iManufacturer string failed. Get iProduct string next + next_stage = ENUM_STAGE_GET_SHORT_PROD_STR_DESC; + break; + case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + // iProduct string failed. Get iSerialNumber string next + next_stage = ENUM_STAGE_GET_SHORT_SER_STR_DESC; + break; + case ENUM_STAGE_COMPLETE: + case ENUM_STAGE_CANCEL: + // These stages should never fail + abort(); + break; + default: + // Stage is not allowed to failed. Cancel enumeration. + next_stage = ENUM_STAGE_CANCEL; + break; + } + } + + // Check if the next stage should be skipped + switch (next_stage) { + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + // Device doesn't support iManufacturer string + if (p_enum_driver->single_thread.dev_params.iManufacturer == 0) { + ESP_LOGD(ENUM_TAG, "String iManufacturer not set, skip"); + stage_skip = true; + } + break; + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + // Device doesn't support iProduct string + if (p_enum_driver->single_thread.dev_params.iProduct == 0) { + ESP_LOGD(ENUM_TAG, "String iProduct not set, skip"); + stage_skip = true; + } + break; + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + // Device doesn't support iSerialNumber string + if (p_enum_driver->single_thread.dev_params.iSerialNumber == 0) { + ESP_LOGD(ENUM_TAG, "String iSerialNumber not set, skip"); + stage_skip = true; + } + break; + default: + break; + } + + if (stage_skip) { + // Loop back around to get the next stage again + last_stage = next_stage; + } else { + break; + } + } + p_enum_driver->single_thread.stage = next_stage; + return stage_need_process(next_stage); +} + +/** + * @brief Control transfer completion callback + * + * Is called by lower logic when transfer is completed with or without error + * + * @param[in] ctrl_xfer Pointer to a transfer buffer + */ +static void enum_control_transfer_complete(usb_transfer_t *ctrl_xfer) +{ + // Sanity checks + assert(ctrl_xfer); + assert(ctrl_xfer->context); + assert(p_enum_driver->single_thread.dev_hdl == ctrl_xfer->context); + + // Request processing + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); +} + +// ----------------------------------------------------------------------------- +// -------------------------- Public API --------------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t enum_install(enum_config_t *config, void **client_ret) +{ + ENUM_CHECK(p_enum_driver == NULL, ESP_ERR_INVALID_STATE); + ENUM_CHECK(config != NULL, ESP_ERR_INVALID_ARG); + + esp_err_t ret; + enum_driver_t *enum_drv = heap_caps_calloc(1, sizeof(enum_driver_t), MALLOC_CAP_DEFAULT); + ENUM_CHECK(enum_drv, ESP_ERR_NO_MEM); + + // Initialize ENUM objects + urb_t *urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0); + if (urb == NULL) { + ret = ESP_ERR_NOT_FINISHED; + goto alloc_err; + } + + // Setup urb + urb->usb_host_client = (void *) enum_drv; // Client is an address of the enum driver object + urb->transfer.callback = enum_control_transfer_complete; + enum_drv->constant.urb = urb; + // Save callbacks + enum_drv->constant.proc_req_cb = config->proc_req_cb; + enum_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg; + enum_drv->constant.enum_event_cb = config->enum_event_cb; + enum_drv->constant.enum_event_cb_arg = config->enum_event_cb_arg; +#if ENABLE_ENUM_FILTER_CALLBACK + enum_drv->constant.enum_filter_cb = config->enum_filter_cb; + enum_drv->constant.enum_filter_cb_arg = config->enum_filter_cb_arg; +#endif // ENABLE_ENUM_FILTER_CALLBACK + + enum_drv->single_thread.next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR; + enum_drv->single_thread.stage = ENUM_STAGE_IDLE; + + // Enumeration driver is single_threaded + if (p_enum_driver != NULL) { + ret = ESP_ERR_NOT_FINISHED; + goto err; + } + p_enum_driver = enum_drv; + // Write-back client_ret pointer + *client_ret = (void *)enum_drv; + return ESP_OK; + +err: + urb_free(urb); +alloc_err: + heap_caps_free(enum_drv); + return ret; +} + +esp_err_t enum_uninstall(void) +{ + ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + // Enumeration driver is single_threaded + enum_driver_t *enum_drv = p_enum_driver; + p_enum_driver = NULL; + // Free resources + urb_free(enum_drv->constant.urb); + heap_caps_free(enum_drv); + return ESP_OK; +} + +esp_err_t enum_start(unsigned int uid) +{ + ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + + esp_err_t ret = ESP_FAIL; + + // Open device and lock it for enumeration process + usb_device_handle_t dev_hdl; + ret = usbh_devs_open(0, &dev_hdl); + if (ret != ESP_OK) { + return ret; + } + ESP_ERROR_CHECK(usbh_dev_enum_lock(dev_hdl)); + + // Get device info + usb_device_info_t dev_info; + uint8_t parent_dev_addr = 0; + ESP_ERROR_CHECK(usbh_dev_get_info(dev_hdl, &dev_info)); + + if (dev_info.parent.dev_hdl) { + ESP_ERROR_CHECK(usbh_dev_get_addr(dev_info.parent.dev_hdl, &parent_dev_addr)); + } + + // Stage ENUM_STAGE_GET_SHORT_DEV_DESC + ESP_LOGD(ENUM_TAG, "[%d:%d] Start processing, device address %d", + parent_dev_addr, + dev_info.parent.port_num, + 0); + + p_enum_driver->single_thread.stage = ENUM_STAGE_GET_SHORT_DEV_DESC; + p_enum_driver->single_thread.dev_uid = uid; + p_enum_driver->single_thread.dev_hdl = dev_hdl; + p_enum_driver->single_thread.parent_dev_hdl = dev_info.parent.dev_hdl; + p_enum_driver->single_thread.parent_dev_addr = parent_dev_addr; + p_enum_driver->single_thread.parent_port_num = dev_info.parent.port_num; + // Save device handle to the URB transfer context + p_enum_driver->constant.urb->transfer.context = (void *) dev_hdl; + // Device params + memset(&p_enum_driver->single_thread.dev_params, 0, sizeof(enum_device_params_t)); + p_enum_driver->single_thread.dev_params.bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) + ? ENUM_WORST_CASE_MPS_LS + : ENUM_WORST_CASE_MPS_FS_HS; + + // Notify USB Host about starting enumeration process + enum_event_data_t event_data = { + .event = ENUM_EVENT_STARTED, + .started = { + .uid = uid, + .parent_dev_hdl = dev_info.parent.dev_hdl, + .parent_port_num = dev_info.parent.port_num, + }, + }; + p_enum_driver->constant.enum_event_cb(&event_data, p_enum_driver->constant.enum_event_cb_arg); + + // Request processing + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); + return ret; +} + +esp_err_t enum_proceed(unsigned int uid) +{ + ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + // Request processing + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); + return ESP_OK; +} + +esp_err_t enum_cancel(unsigned int uid) +{ + ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + + enum_stage_t old_stage = p_enum_driver->single_thread.stage; + + if (old_stage == ENUM_STAGE_IDLE || + old_stage == ENUM_STAGE_CANCEL) { + // Nothing to cancel + return ESP_OK; + } + + p_enum_driver->single_thread.stage = ENUM_STAGE_CANCEL; + + ESP_LOGV(ENUM_TAG, "[%d:%d] Cancel at %s", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + enum_stage_strings[old_stage]); + + if (stage_need_process(old_stage)) { + // These stages are required to trigger processing in the enum_process() + // This means, that there is no ongoing transfer and we can release the + // device from enumeration immediately + usb_device_handle_t dev_hdl = p_enum_driver->single_thread.dev_hdl; + if (dev_hdl) { + // Close the device + ESP_ERROR_CHECK(usbh_dev_enum_unlock(dev_hdl)); + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + p_enum_driver->single_thread.dev_hdl = NULL; + } + } + + // SECOND_RESET_COMPLETE is the exceptional stage, as it awaits the notification after port reset completion via the enum_proceed() call. + // Meanwhile, the device could be detached during the reset, thus the device disconnect comes instead of reset completion. + if (old_stage == ENUM_STAGE_SECOND_RESET_COMPLETE) { + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +esp_err_t enum_process(void) +{ + ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + + esp_err_t res = ESP_FAIL; + enum_stage_t stage = p_enum_driver->single_thread.stage; + + switch (stage) { + // Transfer submission stages + case ENUM_STAGE_GET_SHORT_DEV_DESC: + case ENUM_STAGE_SET_ADDR: + case ENUM_STAGE_GET_FULL_DEV_DESC: + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: + case ENUM_STAGE_GET_FULL_CONFIG_DESC: + case ENUM_STAGE_SET_CONFIG: + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + res = control_request(stage); + break; + // Recovery interval + case ENUM_STAGE_SET_ADDR_RECOVERY: + // Need a short delay before device is ready. Todo: IDF-7007 + vTaskDelay(pdMS_TO_TICKS(SET_ADDR_RECOVERY_INTERVAL_MS)); + res = ESP_OK; + break; + // Transfer check stages + case ENUM_STAGE_CHECK_SHORT_DEV_DESC: + case ENUM_STAGE_CHECK_ADDR: + case ENUM_STAGE_CHECK_FULL_DEV_DESC: + case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: + case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: + case ENUM_STAGE_CHECK_CONFIG: + case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: + case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: + case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + res = control_response_handling(stage); + break; + case ENUM_STAGE_SELECT_CONFIG: + res = select_active_configuration(); + break; + case ENUM_STAGE_SECOND_RESET: + // We need to wait Hub driver to finish port reset + res = second_reset_request(); + break; + case ENUM_STAGE_SECOND_RESET_COMPLETE: + // Second reset complete + res = ESP_OK; + break; + case ENUM_STAGE_CANCEL: + res = stage_cancel(); + break; + case ENUM_STAGE_COMPLETE: + res = stage_complete(); + break; + default: + // Should never occur + abort(); + break; + } + + // Set nest stage of enumeration process, based on the stage pass result + if (set_next_stage(res == ESP_OK)) { + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c new file mode 100644 index 0000000000..4bda2ca240 --- /dev/null +++ b/components/usb/ext_hub.c @@ -0,0 +1,1753 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#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 "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) +#define EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE + +/** + * @brief Device state + * + * Global state of the Device + */ +typedef enum { + EXT_HUB_STATE_ATTACHED, /**< Device attached, but not state is unknown (no: Hub Descriptor, Device status and Hub status) */ + EXT_HUB_STATE_CONFIGURED, /**< Device attached and configured (has Hub Descriptor, Device status and Hub status were requested )*/ + EXT_HUB_STATE_SUSPENDED, /**< Device suspended */ + EXT_HUB_STATE_FAILED /**< Device has internal error */ +} ext_hub_state_t; + +/** + * @brief Device stages + * + * During the lifecycle, Hub requires different actions. To implement interaction with external Hub the FSM, based on these stages is using. + * + * Entry: + * - Every new attached external Hub should start from EXT_HUB_STAGE_GET_HUB_DESCRIPTOR. Without Fetching Hub Descriptor, device is not configured and doesn't have any ports. + * - After handling the response during EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, the External Hub Driver configures the Device according to the data from Hub Descriptor. + * - After completion of any stage, the Device does back to the IDLE stage and waits for another request. Source of the request could be: EP1 INT callback (the Hub or Ports changes) or external call. + * - Stages, that don't required response handling could not end up with fail. + */ +typedef enum { + // Device in IDLE state + EXT_HUB_STAGE_IDLE = 0, /**< Device in idle state and do not fulfill any actions */ + // Stages, required response handling + EXT_HUB_STAGE_GET_DEVICE_STATUS, /**< Device requests Device Status. For more details, refer to 9.4.5 Get Status of usb_20 */ + EXT_HUB_STAGE_CHECK_DEVICE_STATUS, /**< Device received the Device Status and required its' handling */ + EXT_HUB_STAGE_GET_HUB_DESCRIPTOR, /**< Device requests Hub Descriptor. For more details, refer to 11.24.2.5 Get Hub Descriptor of usb_20 */ + EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, /**< Device received the Hub Descriptor and requires its' handling */ + EXT_HUB_STAGE_GET_HUB_STATUS, /**< Device requests Hub Status. For more details, refer to 11.24.2.6 Get Hub Status of usb_20 */ + EXT_HUB_STAGE_CHECK_HUB_STATUS, /**< Device received the Hub Status and requires its' handling */ + // Stages, don't required response handling + EXT_HUB_STAGE_PORT_FEATURE, /**< Device completed the Port Feature class-specific request (Set Feature or Clear Feature). For more details, refer to 11.24.2 Class-specific Requests of usb_20 */ + EXT_HUB_STAGE_PORT_STATUS_REQUEST, /**< Device completed the Port Get Status class-specific request. For more details, refer to 11.24.2 Class-specific Requests of usb_20 */ + EXT_HUB_STAGE_FAILURE /**< Device has internal error and requires handling */ +} ext_hub_stage_t; + +const char *const ext_hub_stage_strings[] = { + "IDLE", + "GET_DEVICE_STATUS", + "CHECK_DEVICE_STATUS", + "GET_HUB_DESCRIPTOR", + "CHECK_HUB_DESCRIPTOR", + "GET_HUB_STATUS", + "CHECK_HUB_STATUS", + "PORT_FEATURE", + "PORT_STATUS_REQUEST", + "FAILURE" +}; + +/** + * @brief Device action flags + */ +typedef enum { + DEV_ACTION_EP0_COMPLETE = (1 << 1), /**< Device complete one of stages, requires handling */ + DEV_ACTION_EP1_FLUSH = (1 << 2), /**< Device's Interrupt EP needs to be flushed */ + DEV_ACTION_EP1_DEQUEUE = (1 << 3), /**< Device's Interrupt EP needs to be dequeued */ + DEV_ACTION_EP1_CLEAR = (1 << 4), /**< Device's Interrupt EP needs to be cleared */ + DEV_ACTION_REQ = (1 << 5), /**< Device has new actions and required handling */ + DEV_ACTION_ERROR = (1 << 6), /**< Device encounters an error */ + DEV_ACTION_GONE = (1 << 7), /**< Device was gone */ + DEV_ACTION_RELEASE = (1 << 8), /**< Device was released */ + DEV_ACTION_FREE = (1 << 9), /**< Device should be freed */ +} dev_action_t; + +typedef struct ext_hub_s ext_hub_dev_t; + +/** + * @brief External Hub device configuration parameters for device allocation + */ +typedef struct { + usb_device_handle_t dev_hdl; /**< Device's handle */ + uint8_t dev_addr; /**< Device's bus address */ + const usb_intf_desc_t *iface_desc; /**< Device's Interface Descriptor pointer */ + const usb_ep_desc_t *ep_in_desc; /**< Device's IN Endpoint Descriptor pointer */ +} device_config_t; + +struct ext_hub_s { + struct { + TAILQ_ENTRY(ext_hub_s) tailq_entry; + union { + struct { + uint32_t in_pending_list: 1; /**< Device is in pending list */ + uint32_t waiting_release: 1; /**< Device waiting to be released */ + uint32_t waiting_children: 1; /**< Device is waiting children to be freed */ + uint32_t waiting_free: 1; /**< Device waiting to be freed */ + uint32_t is_gone: 1; /**< Device is gone */ + uint32_t reserved27: 27; /**< Reserved */ + }; + uint32_t val; /**< Device's flags value */ + } flags; + uint32_t action_flags; /**< Device's action flags */ + } dynamic; /**< Dynamic members require a critical section */ + + struct { + ext_hub_stage_t stage; /**< Device's stage */ + uint8_t maxchild; /**< Amount of allocated ports. Could be 0 for some Hubs. Is increased when new port is added and decreased when port has been freed. */ + ext_hub_state_t state; /**< Device's state */ + } single_thread; /**< Single thread members don't require a critical section, as long as they are never accessed from multiple threads */ + + struct { + // For optimisation & debug only + uint8_t iface_num; /**< Device's bInterfaceNum */ + // Driver purpose + uint8_t dev_addr; /**< Device's bus address */ + usb_device_handle_t dev_hdl; /**< Device's handle */ + urb_t *ctrl_urb; /**< Device's Control pipe transfer URB */ + urb_t *in_urb; /**< Device's Interrupt pipe URB */ + usbh_ep_handle_t ep_in_hdl; /**< Device's Interrupt EP handle */ + + usb_hub_descriptor_t *hub_desc; /**< Device's Hub descriptor pointer. Could be NULL when not requested */ + ext_port_hdl_t *ports; /**< Flexible array of Ports. Could be NULL, when maxchild is 0 */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +}; + +typedef struct { + struct { + TAILQ_HEAD(ext_hubs, ext_hub_s) ext_hubs_tailq; /**< Idle tailq */ + TAILQ_HEAD(ext_hubs_cb, ext_hub_s) ext_hubs_pending_tailq; /**< Pending tailq */ + } dynamic; /**< Dynamic members require a critical section */ + + struct { + ext_hub_cb_t proc_req_cb; /**< Process callback */ + void *proc_req_cb_arg; /**< Process callback argument */ + const ext_hub_port_driver_t* port_driver; /**< External Port Driver */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +} ext_hub_driver_t; + +static ext_hub_driver_t *p_ext_hub_driver = NULL; +static portMUX_TYPE ext_hub_driver_lock = portMUX_INITIALIZER_UNLOCKED; + +const char *EXT_HUB_TAG = "EXT_HUB"; + +// ----------------------------------------------------------------------------- +// ------------------------------- Helpers ------------------------------------- +// ----------------------------------------------------------------------------- + +#define EXT_HUB_ENTER_CRITICAL() portENTER_CRITICAL(&ext_hub_driver_lock) +#define EXT_HUB_EXIT_CRITICAL() portEXIT_CRITICAL(&ext_hub_driver_lock) +#define EXT_HUB_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&ext_hub_driver_lock) +#define EXT_HUB_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&ext_hub_driver_lock) + +#define EXT_HUB_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define EXT_HUB_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + EXT_HUB_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// ----------------------------------------------------------------------------- +// ----------------------- Forward declaration --------------------------------- +// ----------------------------------------------------------------------------- +static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags); +static void device_error(ext_hub_dev_t *ext_hub_dev); +static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length); + +// ----------------------------------------------------------------------------- +// ---------------------- Callbacks (implementation) --------------------------- +// ----------------------------------------------------------------------------- + +static bool interrupt_pipe_cb(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) +{ + uint32_t action_flags; + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)user_arg; + + switch (ep_event) { + case USBH_EP_EVENT_URB_DONE: { + // A interrupt transfer completed on EP1's pipe . We need to dequeue it + action_flags = DEV_ACTION_EP1_DEQUEUE; + break; + case USBH_EP_EVENT_ERROR_XFER: + case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL: + case USBH_EP_EVENT_ERROR_OVERFLOW: + // EP1's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again + action_flags = DEV_ACTION_EP1_FLUSH | + DEV_ACTION_EP1_DEQUEUE | + DEV_ACTION_EP1_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr); + } else { + ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr); + } + break; + case USBH_EP_EVENT_ERROR_STALL: + // EP1's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again + action_flags = DEV_ACTION_EP1_DEQUEUE | DEV_ACTION_EP1_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr); + } else { + ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr); + } + break; + } + default: + action_flags = 0; + break; + } + + EXT_HUB_ENTER_CRITICAL_SAFE(); + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, action_flags); + EXT_HUB_EXIT_CRITICAL_SAFE(); + + bool yield = false; + if (call_proc_req_cb) { + yield = p_ext_hub_driver->constant.proc_req_cb(in_isr, p_ext_hub_driver->constant.proc_req_cb_arg); + } + return yield; +} + +/** + * @brief Control transfer completion callback + * + * Is called by lower logic when transfer is completed with or without error + * + * @param[in] ctrl_xfer Pointer to a transfer buffer + */ +static void control_transfer_complete_cb(usb_transfer_t *ctrl_xfer) +{ + bool call_proc_req_cb = false; + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *) ctrl_xfer->context; + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_EP0_COMPLETE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void interrupt_transfer_complete_cb(usb_transfer_t *intr_xfer) +{ + assert(intr_xfer); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)intr_xfer->context; + assert(ext_hub_dev); + + switch (intr_xfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, intr_xfer->data_buffer, intr_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + device_status_change_handle(ext_hub_dev, intr_xfer->data_buffer, intr_xfer->actual_num_bytes); + break; + case USB_TRANSFER_STATUS_NO_DEVICE: + // Device was removed, nothing to do + break; + case USB_TRANSFER_STATUS_CANCELED: + // Cancellation due to USB Host uninstall routine + break; + default: + // Any other error + ESP_LOGE(EXT_HUB_TAG, "[%d] Interrupt transfer failed, status %d", ext_hub_dev->constant.dev_addr, intr_xfer->status); + device_error(ext_hub_dev); + break; + } + +} + +// ----------------------------------------------------------------------------- +// --------------------------- Internal Logic --------------------------------- +// ----------------------------------------------------------------------------- + +static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + if (action_flags == 0) { + return false; + } + bool call_proc_req_cb; + // Check if device is already on the callback list + if (!ext_hub_dev->dynamic.flags.in_pending_list) { + // Move device form idle device list to callback device list + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry); + ext_hub_dev->dynamic.action_flags |= action_flags; + ext_hub_dev->dynamic.flags.in_pending_list = 1; + call_proc_req_cb = true; + } else { + // The device is already on the callback list, thus a processing request is already pending. + ext_hub_dev->dynamic.action_flags |= action_flags; + call_proc_req_cb = false; + } + return call_proc_req_cb; +} + +static esp_err_t device_enable_int_ep(ext_hub_dev_t *ext_hub_dev) +{ + ESP_LOGD(EXT_HUB_TAG, "[%d] Enable EP IN", ext_hub_dev->constant.dev_addr); + esp_err_t ret = usbh_ep_enqueue_urb(ext_hub_dev->constant.ep_in_hdl, ext_hub_dev->constant.in_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit in urb: %s", esp_err_to_name(ret)); + } + return ret; +} + +static void device_has_changed(ext_hub_dev_t *ext_hub_dev) +{ + // TODO: IDF-10053 Hub status change handling + // After getting the IRQ about Hub status change we need to request status + // device_get_status(ext_hub_dev); + ESP_LOGW(EXT_HUB_TAG, "Hub status change has not been implemented yet"); + device_enable_int_ep(ext_hub_dev); +} + +// +// Figure 11-22. Hub and Port Status Change Bitmap +// Bit | N | ... | 3 | 2 | 1 | 0 | +// Value |1/0| ... |1/0|1/0|1/0|1/0| +// | | | | | +// | | | | +------ Hub change detected +// | | | +---------- Port 1 change detected +// | | +-------------- Port 2 change detected +// | +------------------ Port 3 change detected +// | ... +// +---------------------------- Port N change detected +// +static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length) +{ + uint32_t device_status = 0; + // Driver does not support Hubs with EP IN wMaxPacketSize > 4 + assert(length <= EXT_HUB_MAX_STATUS_BYTES_SIZE); + + for (uint32_t i = 0; i < length; i++) { + device_status |= (uint32_t)(data[i] << i); + } + + if (device_status) { + // If device has a status change, we will re-trigger back the transfer + // after all handling will be done + if (device_status & EXT_HUB_STATUS_CHANGE_FLAG) { + // Check device status + device_has_changed(ext_hub_dev); + } + // HINTs: + // - every byte of Data IN has 8 bits of possible port statuses bits: (length * 8) + // - very first bit of status is used for Hub status: (length * 8) - 1 + for (uint8_t i = 0; i < (length * 8) - 1; i++) { + // Check ports statuses + if (device_status & (EXT_HUB_STATUS_PORT1_CHANGE_FLAG << i)) { + assert(i < ext_hub_dev->single_thread.maxchild); // Port should be in range + assert(p_ext_hub_driver->constant.port_driver); // Port driver call should be valid + // Request Port status to handle changes + p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[i]); + } + } + } else { + // Device status is 0. Could happen when HS Hub with HS device is connected to HS Hub, connected to FS root port + // Re-trigger transfer right now + device_enable_int_ep(ext_hub_dev); + } +} + +static void device_error(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_ERROR); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static esp_err_t device_port_new(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx) +{ + 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]); + 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; + } + + ext_hub_dev->single_thread.maxchild++; + +fail: + return ret; +} + +static esp_err_t device_port_free(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx) +{ + EXT_HUB_CHECK(ext_hub_dev->constant.ports[port_idx] != NULL, ESP_ERR_INVALID_STATE); + + bool call_proc_req_cb = false; + bool all_ports_freed = false; + + 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]); + + 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)); + goto exit; + } + + ext_hub_dev->constant.ports[port_idx] = NULL; + ext_hub_dev->single_thread.maxchild--; + + EXT_HUB_ENTER_CRITICAL(); + if (ext_hub_dev->dynamic.flags.is_gone) { + all_ports_freed = (ext_hub_dev->single_thread.maxchild == 0); + + if (all_ports_freed) { + ext_hub_dev->dynamic.flags.waiting_children = 0; + ext_hub_dev->dynamic.flags.waiting_free = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE); + } else { + ext_hub_dev->dynamic.flags.waiting_children = 1; + } + } + EXT_HUB_EXIT_CRITICAL(); + + // Close the device if all children were freed + if (all_ports_freed) { + ESP_LOGD(EXT_HUB_TAG, "[%d] Release USBH device object", ext_hub_dev->constant.dev_addr); + ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl)); + } + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + ret = ESP_OK; +exit: + return ret; +} + +static void device_release(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device release", ext_hub_dev->constant.dev_addr); + + // Release IN EP + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.flags.is_gone = 1; + ext_hub_dev->dynamic.flags.waiting_release = 0; + EXT_HUB_EXIT_CRITICAL(); + + if (ext_hub_dev->single_thread.state >= EXT_HUB_STATE_CONFIGURED) { + // Hub device was configured and has a descriptor + assert(ext_hub_dev->constant.hub_desc != NULL); + assert(p_ext_hub_driver->constant.port_driver); + // Mark all ports as gone + for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { + if (ext_hub_dev->constant.ports[i]) { + ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); + if (ret == ESP_OK) { + // Port doesn't have a device and can be recycled right now + ret = device_port_free(ext_hub_dev, i); + if (ret != ESP_OK) { + // Hub runs into an error state + // TODO: IDF-10057 Hub handling error + } + } else if (ret == ESP_ERR_NOT_FINISHED) { + // Port has a device and will be recycled after USBH device will be released by all clients and freed + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone", ext_hub_dev->constant.dev_addr, i + 1); + } else { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s", + ext_hub_dev->constant.dev_addr, i + 1, esp_err_to_name(ret)); + } + } + } + } +} + +static esp_err_t device_alloc_desc(ext_hub_dev_t *ext_hub_hdl, const usb_hub_descriptor_t *hub_desc) +{ + // Allocate memory to store the configuration descriptor + usb_hub_descriptor_t *desc = heap_caps_malloc(hub_desc->bDescLength, MALLOC_CAP_DEFAULT); // Buffer to copy over full configuration descriptor (wTotalLength) + if (desc == NULL) { + return ESP_ERR_NO_MEM; + } + // Copy the hub descriptor + memcpy(desc, hub_desc, hub_desc->bDescLength); + // Assign the hub descriptor to the device object + assert(ext_hub_hdl->constant.hub_desc == NULL); + ext_hub_hdl->constant.hub_desc = desc; + return ESP_OK; +} + +static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_dev) +{ + esp_err_t ret; + urb_t *ctrl_urb = NULL; + urb_t *in_urb = NULL; + +#if !ENABLE_MULTIPLE_HUBS + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(config->dev_hdl, &dev_info)); + if (dev_info.parent.dev_hdl) { + ESP_LOGW(EXT_HUB_TAG, "Multiple Hubs not supported, use menuconfig to enable feature"); + ret = ESP_ERR_NOT_SUPPORTED; + goto fail; + } +#endif // ENABLE_MULTIPLE_HUBS + + ext_hub_dev_t *hub_dev = heap_caps_calloc(1, sizeof(ext_hub_dev_t), MALLOC_CAP_DEFAULT); + + if (hub_dev == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate device"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + // Allocate Control transfer URB + ctrl_urb = urb_alloc(sizeof(usb_setup_packet_t) + EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN, 0); + if (ctrl_urb == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Control URB"); + ret = ESP_ERR_NO_MEM; + goto ctrl_urb_fail; + } + + if (config->ep_in_desc->wMaxPacketSize > EXT_HUB_MAX_STATUS_BYTES_SIZE) { + ESP_LOGE(EXT_HUB_TAG, "wMaxPacketSize=%d is not supported", config->ep_in_desc->wMaxPacketSize); + ret = ESP_ERR_NOT_SUPPORTED; + goto in_urb_fail; + } + + in_urb = urb_alloc(config->ep_in_desc->wMaxPacketSize, 0); + // Allocate Interrupt transfer URB + if (in_urb == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Interrupt URB"); + ret = ESP_ERR_NO_MEM; + goto in_urb_fail; + } + + usbh_ep_handle_t ep_hdl; + usbh_ep_config_t ep_config = { + .bInterfaceNumber = config->iface_desc->bInterfaceNumber, + .bAlternateSetting = config->iface_desc->bAlternateSetting, + .bEndpointAddress = config->ep_in_desc->bEndpointAddress, + .ep_cb = interrupt_pipe_cb, + .ep_cb_arg = (void *)hub_dev, + .context = (void *)hub_dev, + }; + + ret = usbh_ep_alloc(config->dev_hdl, &ep_config, &ep_hdl); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Endpoint allocation failure: %s", esp_err_to_name(ret)); + goto ep_fail; + } + // Configure Control transfer URB + ctrl_urb->usb_host_client = (void *) p_ext_hub_driver; + ctrl_urb->transfer.callback = control_transfer_complete_cb; + ctrl_urb->transfer.context = (void *) hub_dev; + + // Client is a memory address of the p_ext_hub_driver driver object + in_urb->usb_host_client = (void *) p_ext_hub_driver; + in_urb->transfer.callback = interrupt_transfer_complete_cb; + in_urb->transfer.context = (void *) hub_dev; + in_urb->transfer.num_bytes = config->ep_in_desc->wMaxPacketSize; + + hub_dev->dynamic.flags.val = 0; + hub_dev->dynamic.action_flags = 0; + // Mutex protected + hub_dev->single_thread.state = EXT_HUB_STATE_ATTACHED; + // Constant members + hub_dev->constant.ep_in_hdl = ep_hdl; + hub_dev->constant.ctrl_urb = ctrl_urb; + hub_dev->constant.in_urb = in_urb; + hub_dev->constant.dev_hdl = config->dev_hdl; + hub_dev->constant.dev_addr = config->dev_addr; + hub_dev->constant.iface_num = config->iface_desc->bInterfaceNumber; + // Single thread members + hub_dev->single_thread.stage = EXT_HUB_STAGE_IDLE; + // We will update number of ports during Hub Descriptor handling stage + hub_dev->single_thread.maxchild = 0; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, hub_dev, dynamic.tailq_entry); + EXT_HUB_EXIT_CRITICAL(); + + ESP_LOGD(EXT_HUB_TAG, "[%d] New device (iface %d)", config->dev_addr, hub_dev->constant.iface_num); + + *ext_hub_dev = hub_dev; + return ret; + +ep_fail: + urb_free(in_urb); +in_urb_fail: + urb_free(ctrl_urb); +ctrl_urb_fail: + heap_caps_free(hub_dev); +fail: + return ret; +} + +static esp_err_t device_configure(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_dev->constant.hub_desc != NULL, ESP_ERR_INVALID_STATE); + usb_hub_descriptor_t *hub_desc = ext_hub_dev->constant.hub_desc; + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device configure (iface %d)", + ext_hub_dev->constant.dev_addr, + ext_hub_dev->constant.iface_num); + + if (hub_desc->wHubCharacteristics.compound) { + ESP_LOGD(EXT_HUB_TAG, "\tCompound device"); + } else { + ESP_LOGD(EXT_HUB_TAG, "\tStandalone HUB"); + } + + ESP_LOGD(EXT_HUB_TAG, "\t%d external port%s", + ext_hub_dev->constant.hub_desc->bNbrPorts, + (ext_hub_dev->constant.hub_desc->bNbrPorts == 1) ? "" : "s"); + + switch (hub_desc->wHubCharacteristics.power_switching) { + case USB_W_HUB_CHARS_PORT_PWR_CTRL_NO: + ESP_LOGD(EXT_HUB_TAG, "\tNo power switching (usb 1.0)"); + break; + case USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV: + ESP_LOGD(EXT_HUB_TAG, "\tIndividual port power switching"); + break; + default: + // USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL + ESP_LOGD(EXT_HUB_TAG, "\tAll ports power at once"); + break; + } + + switch (hub_desc->wHubCharacteristics.ovr_current_protect) { + case USB_W_HUB_CHARS_PORT_OVER_CURR_NO: + ESP_LOGD(EXT_HUB_TAG, "\tNo over-current protection"); + break; + case USB_W_HUB_CHARS_PORT_OVER_CURR_INDV: + ESP_LOGD(EXT_HUB_TAG, "\tIndividual port over-current protection"); + break; + default: + // USB_W_HUB_CHARS_PORT_OVER_CURR_ALL + ESP_LOGD(EXT_HUB_TAG, "\tGlobal over-current protection"); + break; + } + + if (hub_desc->wHubCharacteristics.indicator_support) { + ESP_LOGD(EXT_HUB_TAG, "\tPort indicators are supported"); + } + + ESP_LOGD(EXT_HUB_TAG, "\tPower on to power good time: %dms", hub_desc->bPwrOn2PwrGood * 2); + ESP_LOGD(EXT_HUB_TAG, "\tMaximum current: %d mA", hub_desc->bHubContrCurrent); + + // Create External Port flexible array + ext_hub_dev->constant.ports = heap_caps_calloc(ext_hub_dev->constant.hub_desc->bNbrPorts, sizeof(ext_port_hdl_t), MALLOC_CAP_DEFAULT); + if (ext_hub_dev->constant.ports == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Ports list allocation error"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + // Create port and add it to pending list + for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { + ext_hub_dev->constant.ports[i] = NULL; + ret = device_port_new(ext_hub_dev, i); + if (ret != ESP_OK) { + goto fail; + } + } + + ext_hub_dev->single_thread.state = EXT_HUB_STATE_CONFIGURED; + return ESP_OK; + +fail: + // Free allocated resources + for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { + if (ext_hub_dev->constant.ports[i]) { + device_port_free(ext_hub_dev, i); + } + } + + if (ext_hub_dev->constant.ports) { + assert(ext_hub_dev->single_thread.maxchild == 0); + heap_caps_free(ext_hub_dev->constant.ports); + } + + return ret; +} + +static void device_free(ext_hub_dev_t *ext_hub_dev) +{ + assert(ext_hub_dev->single_thread.maxchild == 0); // All ports should be freed by now + + ESP_LOGD(EXT_HUB_TAG, "[%d] Freeing device", ext_hub_dev->constant.dev_addr); + + EXT_HUB_ENTER_CRITICAL(); + assert(ext_hub_dev->dynamic.flags.waiting_free == 1); // Hub should await being freed + ext_hub_dev->dynamic.flags.waiting_free = 0; + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + EXT_HUB_EXIT_CRITICAL(); + + if (ext_hub_dev->constant.hub_desc) { + heap_caps_free(ext_hub_dev->constant.hub_desc); + } + if (ext_hub_dev->constant.ports) { + heap_caps_free(ext_hub_dev->constant.ports); + } + + ESP_ERROR_CHECK(usbh_ep_free(ext_hub_dev->constant.ep_in_hdl)); + urb_free(ext_hub_dev->constant.ctrl_urb); + urb_free(ext_hub_dev->constant.in_urb); + + heap_caps_free(ext_hub_dev); +} + +static esp_err_t get_dev_by_hdl(usb_device_handle_t dev_hdl, ext_hub_dev_t **ext_hub_hdl) +{ + esp_err_t ret = ESP_OK; + // Go through the Hubs lists to find the hub with the specified device address + ext_hub_dev_t *found_hub = NULL; + ext_hub_dev_t *hub = NULL; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_hdl == dev_hdl) { + found_hub = hub; + goto exit; + } + } + + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_hdl == dev_hdl) { + found_hub = hub; + goto exit; + } + } + +exit: + if (found_hub == NULL) { + ret = ESP_ERR_NOT_FOUND; + } + EXT_HUB_EXIT_CRITICAL(); + + *ext_hub_hdl = found_hub; + return ret; +} + +static esp_err_t get_dev_by_addr(uint8_t dev_addr, ext_hub_dev_t **ext_hub_hdl) +{ + esp_err_t ret = ESP_OK; + // Go through the Hubs lists to find the Hub with the specified device address + ext_hub_dev_t *found_hub = NULL; + ext_hub_dev_t *hub = NULL; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_addr == dev_addr) { + found_hub = hub; + goto exit; + } + } + + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_addr == dev_addr) { + found_hub = hub; + goto exit; + } + } + +exit: + if (found_hub == NULL) { + ret = ESP_ERR_NOT_FOUND; + } + EXT_HUB_EXIT_CRITICAL(); + + *ext_hub_hdl = found_hub; + return ret; +} + +// ----------------------------------------------------------------------------- +// -------------------------- Device handling --------------------------------- +// ----------------------------------------------------------------------------- + +static bool handle_hub_descriptor(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + bool pass; + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_hub_descriptor_t *hub_desc = (const usb_hub_descriptor_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(EXT_HUB_TAG, "Bad transfer status %d: stage=%d", ctrl_xfer->status, ext_hub_dev->single_thread.stage); + return false; + } + + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + + ret = device_alloc_desc(ext_hub_dev, hub_desc); + if (ret != ESP_OK) { + pass = false; + goto exit; + } + + ret = device_configure(ext_hub_dev); + if (ret != ESP_OK) { + pass = false; + goto exit; + } + + pass = true; +exit: + return pass; +} + +static bool handle_device_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_device_status_t *dev_status = (const usb_device_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device status: ", ext_hub_dev->constant.dev_addr); + ESP_LOGD(EXT_HUB_TAG, "\tPower: %s", dev_status->self_powered ? "self-powered" : "bus-powered"); + ESP_LOGD(EXT_HUB_TAG, "\tRemoteWakeup: %s", dev_status->remote_wakeup ? "yes" : "no"); + + if (dev_status->remote_wakeup) { + // Device in remote_wakeup, we need send command Clear Device Feature: USB_W_VALUE_FEATURE_DEVICE_REMOTE_WAKEUP + // HEX codes of command: 00 01 01 00 00 00 00 00 + // TODO: IDF-10055 Hub Support remote_wakeup feature + ESP_LOGW(EXT_HUB_TAG, "Remote Wakeup feature has not been implemented yet"); + } + return true; +} + +static bool handle_hub_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_hub_status_t *hub_status = (const usb_hub_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + ESP_LOGD(EXT_HUB_TAG, "[%d] Hub status: ", ext_hub_dev->constant.dev_addr); + ESP_LOGD(EXT_HUB_TAG, "\tExternal power supply: %s", hub_status->wHubStatus.HUB_LOCAL_POWER ? "yes" : "no"); + ESP_LOGD(EXT_HUB_TAG, "\tOvercurrent: %s", hub_status->wHubStatus.HUB_OVER_CURRENT ? "yes" : "no"); + + if (hub_status->wHubStatus.HUB_OVER_CURRENT) { + ESP_LOGE(EXT_HUB_TAG, "Device has overcurrent!"); + // Hub has an overcurrent, we need to disable all port and/or disable parent port + // TODO: IDF-10056 Hubs overcurrent handling + ESP_LOGW(EXT_HUB_TAG, "Feature has not been implemented yet"); + return false; + } + + return true; +} + +static void handle_port_feature(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer); + uint8_t port_idx = port_num - 1; + + assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts); + assert(p_ext_hub_driver->constant.port_driver); + p_ext_hub_driver->constant.port_driver->req_process(ext_hub_dev->constant.ports[port_idx]); +} + +static void handle_port_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer); + uint8_t port_idx = port_num - 1; + const usb_port_status_t *new_status = (const usb_port_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts); + assert(p_ext_hub_driver->constant.port_driver); + p_ext_hub_driver->constant.port_driver->set_status(ext_hub_dev->constant.ports[port_idx], new_status); +} + +static bool device_control_request(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + switch (ext_hub_dev->single_thread.stage) { + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + USB_SETUP_PACKET_INIT_GET_STATUS((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_status_t); + break; + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_descriptor_t); + break; + case EXT_HUB_STAGE_GET_HUB_STATUS: + USB_SETUP_PACKET_INIT_GET_HUB_STATUS((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_status_t); + break; + default: + // Should never occur + abort(); + break; + } + + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb: %s", esp_err_to_name(ret)); + return false; + } + + return true; +} + +static bool device_control_response_handling(ext_hub_dev_t *ext_hub_dev) +{ + bool stage_pass = false; + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + // Check transfer status + if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(EXT_HUB_TAG, "Control request bad transfer status %d", + ctrl_xfer->status); + return stage_pass; + } + + // Transfer completed, verbose data in ESP_LOG_VERBOSE is set + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + + switch (ext_hub_dev->single_thread.stage) { + case EXT_HUB_STAGE_CHECK_DEVICE_STATUS: + stage_pass = handle_device_status(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR: + stage_pass = handle_hub_descriptor(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_HUB_STATUS: + stage_pass = handle_hub_status(ext_hub_dev); + break; + case EXT_HUB_STAGE_PORT_FEATURE: + handle_port_feature(ext_hub_dev); + stage_pass = true; + break; + case EXT_HUB_STAGE_PORT_STATUS_REQUEST: + handle_port_status(ext_hub_dev); + stage_pass = true; + break; + default: + // Should never occur + abort(); + break; + } + + return stage_pass; +} + +static bool stage_need_process(ext_hub_stage_t stage) +{ + bool need_process_cb = false; + + switch (stage) { + // Stages, required control transfer + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + case EXT_HUB_STAGE_GET_HUB_STATUS: + // Error stage + case EXT_HUB_STAGE_FAILURE: + need_process_cb = true; + break; + default: + break; + } + + return need_process_cb; +} + +// return +// true - next stage requires the processing +// false - terminal stage +static bool device_set_next_stage(ext_hub_dev_t *ext_hub_dev, bool last_stage_pass) +{ + ext_hub_stage_t last_stage = ext_hub_dev->single_thread.stage; + ext_hub_stage_t next_stage; + + if (last_stage_pass) { + ESP_LOGD(EXT_HUB_TAG, "Stage %s OK", ext_hub_stage_strings[last_stage]); + if (last_stage == EXT_HUB_STAGE_GET_DEVICE_STATUS || + last_stage == EXT_HUB_STAGE_GET_HUB_DESCRIPTOR || + last_stage == EXT_HUB_STAGE_GET_HUB_STATUS) { + // Simply increment to get the next stage + next_stage = last_stage + 1; + } else { + // Terminal stages, move to IDLE + next_stage = EXT_HUB_STAGE_IDLE; + } + } else { + ESP_LOGE(EXT_HUB_TAG, "Stage %s FAILED", ext_hub_stage_strings[last_stage]); + // These stages cannot fail + assert(last_stage != EXT_HUB_STAGE_PORT_FEATURE || + last_stage != EXT_HUB_STAGE_PORT_STATUS_REQUEST); + + next_stage = EXT_HUB_STAGE_FAILURE; + } + + ext_hub_dev->single_thread.stage = next_stage; + return stage_need_process(next_stage); +} + +static void handle_error(ext_hub_dev_t *ext_hub_dev) +{ + ext_hub_dev->single_thread.state = EXT_HUB_STATE_FAILED; + // TODO: IDF-10057 Hub handling error + ESP_LOGW(EXT_HUB_TAG, "%s has not been implemented yet", __FUNCTION__); +} + +static void handle_device(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb; + bool stage_pass = false; + // FSM for external Hub + switch (ext_hub_dev->single_thread.stage) { + case EXT_HUB_STAGE_IDLE: + break; + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + case EXT_HUB_STAGE_GET_HUB_STATUS: + stage_pass = device_control_request(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_DEVICE_STATUS: + case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR: + case EXT_HUB_STAGE_CHECK_HUB_STATUS: + case EXT_HUB_STAGE_PORT_FEATURE: + case EXT_HUB_STAGE_PORT_STATUS_REQUEST: + stage_pass = device_control_response_handling(ext_hub_dev); + break; + case EXT_HUB_STAGE_FAILURE: + handle_error(ext_hub_dev); + stage_pass = true; + break; + default: + // Should never occur + abort(); + break; + } + + call_proc_req_cb = device_set_next_stage(ext_hub_dev, stage_pass); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void handle_ep1_flush(ext_hub_dev_t *ext_hub_dev) +{ + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_FLUSH)); +} + +static void handle_ep1_dequeue(ext_hub_dev_t *ext_hub_dev) +{ + // Dequeue all URBs and run their transfer callback + ESP_LOGD(EXT_HUB_TAG, "[%d] Interrupt dequeue", ext_hub_dev->constant.dev_addr); + + urb_t *urb; + usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_hdl, &urb); + while (urb != NULL) { + // Clear the transfer's in-flight flag to indicate the transfer is no longer in-flight + urb->usb_host_inflight = false; + urb->transfer.callback(&urb->transfer); + usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_hdl, &urb); + } +} + +static void handle_ep1_clear(ext_hub_dev_t *ext_hub_dev) +{ + // We allow the pipe command to fail just in case the pipe becomes invalid mid command + usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_CLEAR); +} + +static void handle_gone(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + // Set the flags + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.flags.waiting_free = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +// ----------------------------------------------------------------------------- +// ------------------------------ Driver --------------------------------------- +// ----------------------------------------------------------------------------- + +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) { + ret = ESP_ERR_NO_MEM; + goto err; + } + // Save callbacks + ext_hub_drv->constant.proc_req_cb = config->proc_req_cb; + ext_hub_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg; + // Copy Port driver pointer + ext_hub_drv->constant.port_driver = config->port_driver; + + if (ext_hub_drv->constant.port_driver == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Port Driver has not been implemented yet"); + ret = ESP_ERR_NOT_FINISHED; + goto err; + } + + TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_tailq); + TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_pending_tailq); + + EXT_HUB_ENTER_CRITICAL(); + if (p_ext_hub_driver != NULL) { + EXT_HUB_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto err; + } + p_ext_hub_driver = ext_hub_drv; + EXT_HUB_EXIT_CRITICAL(); + + ESP_LOGD(EXT_HUB_TAG, "Driver installed"); + return ESP_OK; + +err: + if (mux_lock != NULL) { + vSemaphoreDelete(mux_lock); + } + heap_caps_free(ext_hub_drv); + return ret; +} + +esp_err_t ext_hub_uninstall(void) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_tailq), ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq), ESP_ERR_INVALID_STATE); + ext_hub_driver_t *ext_hub_drv = p_ext_hub_driver; + p_ext_hub_driver = NULL; + EXT_HUB_EXIT_CRITICAL(); + + // Free resources + heap_caps_free(ext_hub_drv); + ESP_LOGD(EXT_HUB_TAG, "Driver uninstalled"); + return ESP_OK; +} + +void *ext_hub_get_client(void) +{ + bool driver_installed = false; + EXT_HUB_ENTER_CRITICAL(); + driver_installed = (p_ext_hub_driver != NULL); + EXT_HUB_EXIT_CRITICAL(); + return (driver_installed) ? (void*) p_ext_hub_driver : NULL; +} + +// ----------------------------------------------------------------------------- +// -------------------------- External Hub API --------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + return get_dev_by_hdl(dev_hdl, ext_hub_hdl); +} + +static esp_err_t find_first_intf_desc(const usb_config_desc_t *config_desc, device_config_t *hub_config) +{ + bool iface_found = false; + const usb_ep_desc_t *ep_in_desc = NULL; + + int offset = 0; + const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type( + (const usb_standard_desc_t *)config_desc, + config_desc->wTotalLength, + USB_B_DESCRIPTOR_TYPE_INTERFACE, + &offset); + + while (next_intf_desc != NULL) { + if (iface_found) { + // TODO: IDF-10058 Hubs support interface selection (HS) + ESP_LOGW(EXT_HUB_TAG, "Device has several Interfaces, selection has not been implemented yet. Using first."); + break; + } + // Parse all interfaces + if (USB_CLASS_HUB == next_intf_desc->bInterfaceClass) { + // We have found the first interface descriptor with matching bInterfaceNumber + + if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_FS) { + // TODO: IDF-10059 Hubs support multiple TT (HS) + if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_HS_NO_TT) { + ESP_LOGW(EXT_HUB_TAG, "Transaction Translator has not been implemented yet"); + } + + switch (next_intf_desc->bInterfaceProtocol) { + case USB_B_DEV_PROTOCOL_HUB_HS_NO_TT: + ESP_LOGD(EXT_HUB_TAG, "\tNo TT"); + break; + case USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT: + ESP_LOGD(EXT_HUB_TAG, "\tSingle TT"); + break; + case USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT: + ESP_LOGD(EXT_HUB_TAG, "\tMulti TT"); + break; + default: + ESP_LOGE(EXT_HUB_TAG, "\tInterface Protocol (%#x) not supported", next_intf_desc->bInterfaceProtocol); + goto next_iface; + } + } + + // Hub Interface always should have only one Interrupt endpoint + if (next_intf_desc->bNumEndpoints != 1) { + ESP_LOGE(EXT_HUB_TAG, "Unexpected number of endpoints (%d)", next_intf_desc->bNumEndpoints); + goto next_iface; + } + // Get related IN EP + ep_in_desc = usb_parse_endpoint_descriptor_by_index(next_intf_desc, 0, config_desc->wTotalLength, &offset); + if (ep_in_desc == NULL) { + ESP_LOGE(EXT_HUB_TAG, "EP descriptor not found (iface=%d)", next_intf_desc->bInterfaceNumber); + goto next_iface; + } + + if (!USB_EP_DESC_GET_EP_DIR(ep_in_desc) || + (USB_EP_DESC_GET_XFERTYPE(ep_in_desc) != USB_TRANSFER_TYPE_INTR)) { + ESP_LOGE(EXT_HUB_TAG, "Interrupt EP not found (iface=%d)", next_intf_desc->bInterfaceNumber); + goto next_iface; + } + + // Interface found, fill the config + hub_config->iface_desc = next_intf_desc; + hub_config->ep_in_desc = ep_in_desc; + iface_found = true; + } + +next_iface: + next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type( + (const usb_standard_desc_t *)next_intf_desc, + config_desc->wTotalLength, + USB_B_DESCRIPTOR_TYPE_INTERFACE, + &offset); + } + + return (iface_found) ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t ext_hub_new_dev(uint8_t dev_addr) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + esp_err_t ret; + ext_hub_dev_t *hub_dev = NULL; + usb_device_handle_t dev_hdl = NULL; + const usb_config_desc_t *config_desc = NULL; + bool call_proc_req_cb = false; + + // Open device + ret = usbh_devs_open(dev_addr, &dev_hdl); + if (ret != ESP_OK) { + return ret; + } + + // Get Configuration Descriptor + ret = usbh_dev_get_config_desc(dev_hdl, &config_desc); + if (ret != ESP_OK) { + goto exit; + } + + // Find related Hub Interface descriptor + device_config_t hub_config = { + .dev_hdl = dev_hdl, + .dev_addr = dev_addr, + .iface_desc = NULL, + .ep_in_desc = NULL, + }; + + ret = find_first_intf_desc(config_desc, &hub_config); + if (ret != ESP_OK) { + goto exit; + } + + // Create External Hub device + ret = device_alloc(&hub_config, &hub_dev); + if (ret != ESP_OK) { + goto exit; + } + + hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_HUB_DESCRIPTOR; + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ret; + +exit: + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + return ret; +} + +esp_err_t ext_hub_dev_gone(uint8_t dev_addr) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + esp_err_t ret; + + ext_hub_dev_t *ext_hub_dev = NULL; + bool in_pending = false; + + EXT_HUB_CHECK(dev_addr != 0, ESP_ERR_INVALID_ARG); + // Find device with dev_addr in the devices TAILQ + // TODO: IDF-10058 + // Release all devices by dev_addr + ret = get_dev_by_addr(dev_addr, &ext_hub_dev); + if (ret != ESP_OK) { + ESP_LOGD(EXT_HUB_TAG, "No device with address %d was found", dev_addr); + goto exit; + } + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device gone", ext_hub_dev->constant.dev_addr); + + EXT_HUB_ENTER_CRITICAL(); + if (ext_hub_dev->dynamic.flags.waiting_release || + ext_hub_dev->dynamic.flags.waiting_children) { + // External Hub was already released or waiting children to be freed + EXT_HUB_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + if (ext_hub_dev->dynamic.flags.in_pending_list) { + in_pending = true; + // TODO: IDF-10490 + // test case: + // - detach the external Hub device right before the Hub Descriptor request. + _device_set_actions(ext_hub_dev, DEV_ACTION_RELEASE); + } + EXT_HUB_EXIT_CRITICAL(); + + // Device not in pending, can be released immediately + if (!in_pending) { + device_release(ext_hub_dev); + } + + ret = ESP_OK; +exit: + return ret; +} + +esp_err_t ext_hub_all_free(void) +{ + bool call_proc_req_cb = false; + bool wait_for_free = false; + + EXT_HUB_ENTER_CRITICAL(); + for (int i = 0; i < 2; i++) { + ext_hub_dev_t *hub_curr; + ext_hub_dev_t *hub_next; + // Go through pending list first + if (i == 0) { + hub_curr = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq); + } else { + hub_curr = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_tailq); + } + while (hub_curr != NULL) { + hub_next = TAILQ_NEXT(hub_curr, dynamic.tailq_entry); + hub_curr->dynamic.flags.waiting_release = 1; + call_proc_req_cb = _device_set_actions(hub_curr, DEV_ACTION_RELEASE); + // At least one hub should be released + wait_for_free = true; + hub_curr = hub_next; + } + } + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK; +} + +esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + return device_enable_int_ep(ext_hub_dev); +} + +esp_err_t ext_hub_process(void) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + // Keep processing until all device's with pending events have been handled + while (!TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq)) { + // Move the device back into the idle device list, + ext_hub_dev_t *ext_hub_dev = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq); + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + // Clear the device's flags + uint32_t action_flags = ext_hub_dev->dynamic.action_flags; + ext_hub_dev->dynamic.action_flags = 0; + ext_hub_dev->dynamic.flags.in_pending_list = 0; + + /* --------------------------------------------------------------------- + Exit critical section to handle device action flags in their listed order + --------------------------------------------------------------------- */ + EXT_HUB_EXIT_CRITICAL(); + ESP_LOGD(EXT_HUB_TAG, "[%d] Processing actions 0x%"PRIx32"", ext_hub_dev->constant.dev_addr, action_flags); + + if (action_flags & DEV_ACTION_REQ || + action_flags & DEV_ACTION_EP0_COMPLETE) { + handle_device(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_FLUSH) { + handle_ep1_flush(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_DEQUEUE) { + handle_ep1_dequeue(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_CLEAR) { + handle_ep1_clear(ext_hub_dev); + } + if (action_flags & DEV_ACTION_ERROR) { + handle_error(ext_hub_dev); + } + if (action_flags & DEV_ACTION_GONE) { + handle_gone(ext_hub_dev); + } + if (action_flags & DEV_ACTION_RELEASE) { + device_release(ext_hub_dev); + } + if (action_flags & DEV_ACTION_FREE) { + device_free(ext_hub_dev); + } + EXT_HUB_ENTER_CRITICAL(); + /* --------------------------------------------------------------------- + Re-enter critical sections. All device action flags should have been handled. + --------------------------------------------------------------------- */ + + } + EXT_HUB_EXIT_CRITICAL(); + + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// --------------------- External Hub - Device related ------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_get_hub_status(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_HUB_STATUS; + + EXT_HUB_ENTER_CRITICAL(); + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +esp_err_t ext_hub_get_status(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_DEVICE_STATUS; + EXT_HUB_ENTER_CRITICAL(); + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// --------------------- External Hub - Port related --------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_port_recycle(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_EXIT_CRITICAL(); + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + esp_err_t ret; + uint8_t port_idx = port_num - 1; + bool free_port = false; + bool release_port = false; + + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + EXT_HUB_ENTER_CRITICAL(); + if (ext_hub_dev->dynamic.flags.waiting_release) { + release_port = true; + } else if (ext_hub_dev->dynamic.flags.waiting_children) { + assert(ext_hub_dev->dynamic.flags.waiting_release == 0); // Device should be already released + assert(ext_hub_dev->dynamic.flags.is_gone == 1); // Device should be gone by now + free_port = true; + } + EXT_HUB_EXIT_CRITICAL(); + + if (!release_port && !free_port) { + // Parent still present, recycle the port + ret = p_ext_hub_driver->constant.port_driver->recycle(ext_hub_dev->constant.ports[port_idx]); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to recycle the port: %s", ext_hub_dev->constant.dev_addr, port_num, esp_err_to_name(ret)); + goto exit; + } + } else { + if (release_port) { + // Notify the port that parent is not available anymore and port should be recycled then freed + ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[port_idx]); + if (ret == ESP_OK) { + ESP_LOGD(EXT_HUB_TAG, "[%d:%d] Port doesn't have a device and can be freed right now", + ext_hub_dev->constant.dev_addr, + port_num); + assert(free_port == false); + free_port = true; + } else if (ret == ESP_ERR_NOT_FINISHED) { + // Port has a device and will be recycled after USBH device will be released by all clients and freed + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone", + ext_hub_dev->constant.dev_addr, + port_num); + // Logically, recycling logic are finished for the Hub Driver, return ESP_OK to free the node + assert(free_port == false); + } else { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s", + ext_hub_dev->constant.dev_addr, port_num, esp_err_to_name(ret)); + } + } + + if (free_port) { + ret = device_port_free(ext_hub_dev, port_idx); + if (ret != ESP_OK) { + goto exit; + } + } + } + + ret = ESP_OK; +exit: + return ret; +} + +esp_err_t ext_hub_port_reset(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_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + + return p_ext_hub_driver->constant.port_driver->reset(ext_hub_dev->constant.ports[port_idx]); +} + +esp_err_t ext_hub_port_active(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_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + + return p_ext_hub_driver->constant.port_driver->active(ext_hub_dev->constant.ports[port_idx]); +} + +esp_err_t ext_hub_port_disable(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_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + + return p_ext_hub_driver->constant.port_driver->disable(ext_hub_dev->constant.ports[port_idx]); +} + +esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, usb_speed_t *speed) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + + return p_ext_hub_driver->constant.port_driver->get_speed(ext_hub_dev->constant.ports[port_idx], speed); +} + +// ----------------------------------------------------------------------------- +// --------------------------- USB Chapter 11 ---------------------------------- +// ----------------------------------------------------------------------------- + +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_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + USB_SETUP_PACKET_INIT_SET_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE; + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Set port feature %#x, failed to submit ctrl urb: %s", + ext_hub_dev->constant.dev_addr, + port_num, + feature, + esp_err_to_name(ret)); + } + return ret; +} + +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_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE; + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Clear port feature %#x, failed to submit ctrl urb: %s", + ext_hub_dev->constant.dev_addr, + port_num, + feature, + esp_err_to_name(ret)); + } + return ret; +} + +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_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, port_num); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_port_status_t); + + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_STATUS_REQUEST; + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Get port status, failed to submit ctrl urb: %s", + ext_hub_dev->constant.dev_addr, + port_num, + esp_err_to_name(ret)); + } + return ret; +} diff --git a/components/usb/hub.c b/components/usb/hub.c index 2735ef5a46..df679a3799 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "esp_err.h" @@ -18,12 +19,18 @@ #include "hub.h" #include "usb/usb_helpers.h" +#if ENABLE_USB_HUBS +#include "ext_hub.h" +#endif // ENABLE_USB_HUBS + /* Implementation of the HUB driver that only supports the Root Hub with a single port. Therefore, we currently don't implement the bare minimum to control the root HCD port. */ -#define HUB_ROOT_PORT_NUM 1 // HCD only supports one port +#define HUB_ROOT_PORT_NUM 1 // HCD only supports one port +#define HUB_ROOT_DEV_UID 1 // Unique device ID + #ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_RX #elif CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT @@ -32,34 +39,23 @@ implement the bare minimum to control the root HCD port. #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED #endif -#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK -#define ENABLE_ENUM_FILTER_CALLBACK -#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK - -#define SET_ADDR_RECOVERY_INTERVAL_MS CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS - -#define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE -#define ENUM_DEV_ADDR 1 // Device address used in enumeration -#define ENUM_DEV_UID 1 // Unique ID for device connected to root port -#define ENUM_CONFIG_INDEX_DEFAULT 0 // Index used to get the first configuration descriptor of the device -#define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength) -#define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device -#define ENUM_WORST_CASE_MPS_FS 64 // The worst case MPS of EP0 for a FS device -#define ENUM_LOW_SPEED_MPS 8 // Worst case MPS for the default endpoint of a low-speed device -#define ENUM_FULL_SPEED_MPS 64 // Worst case MPS for the default endpoint of a full-speed device -#define ENUM_LANGID 0x409 // Current enumeration only supports English (United States) string descriptors - -// Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive -#define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 -#define HUB_DRIVER_FLAG_ACTION_PORT_REQ 0x02 -#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04 - #define PORT_REQ_DISABLE 0x01 #define PORT_REQ_RECOVER 0x02 +/** + * @brief Hub driver action flags + */ +typedef enum { + HUB_DRIVER_ACTION_ROOT_EVENT = (1 << 0), + HUB_DRIVER_ACTION_ROOT_REQ = (1 << 1), +#if ENABLE_USB_HUBS + HUB_DRIVER_ACTION_EXT_HUB = (1 << 6), + HUB_DRIVER_ACTION_EXT_PORT = (1 << 7) +#endif // ENABLE_USB_HUBS +} hub_flag_action_t; + /** * @brief Root port states - * */ typedef enum { ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */ @@ -70,136 +66,44 @@ typedef enum { } root_port_state_t; /** - * @brief Stages of device enumeration listed in their order of execution + * @brief Hub device tree node * - * - These stages MUST BE LISTED IN THE ORDER OF THEIR EXECUTION as the enumeration will simply increment the current stage - * - If an error occurs at any stage, ENUM_STAGE_CLEANUP_FAILED acts as a common exit stage on failure - * - Must start with 0 as enum is also used as an index - * - The short descriptor stages are used to fetch the start particular descriptors that don't have a fixed length in order to determine the full descriptors length + * Object type of a node in the USB device tree that is maintained by the Hub driver */ -typedef enum { - ENUM_STAGE_NONE = 0, /**< There is no device awaiting enumeration. Start requires device connection and first reset. */ - ENUM_STAGE_START, /**< A device has connected and has already been reset once. Allocate a device object in USBH */ - // Basic device enumeration - ENUM_STAGE_GET_SHORT_DEV_DESC, /**< Getting short dev desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ - ENUM_STAGE_CHECK_SHORT_DEV_DESC, /**< Save bMaxPacketSize0 from the short dev desc. Update the MPS of the enum pipe */ - ENUM_STAGE_SECOND_RESET, /**< Reset the device again (Workaround for old USB devices that get confused by the previous short dev desc request). */ - ENUM_STAGE_SET_ADDR, /**< Send SET_ADDRESS request */ - ENUM_STAGE_CHECK_ADDR, /**< Update the enum pipe's target address */ - ENUM_STAGE_SET_ADDR_RECOVERY, /**< Wait SET ADDRESS recovery interval at least for 2ms due to usb_20, chapter 9.2.6.3 */ - ENUM_STAGE_GET_FULL_DEV_DESC, /**< Get the full dev desc */ - ENUM_STAGE_CHECK_FULL_DEV_DESC, /**< Check the full dev desc, fill it into the device object in USBH. Save the string descriptor indexes*/ - ENUM_STAGE_GET_SHORT_CONFIG_DESC, /**< Getting a short config desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */ - ENUM_STAGE_CHECK_SHORT_CONFIG_DESC, /**< Save wTotalLength of the short config desc */ - ENUM_STAGE_GET_FULL_CONFIG_DESC, /**< Get the full config desc (wLength is the saved wTotalLength) */ - ENUM_STAGE_CHECK_FULL_CONFIG_DESC, /**< Check the full config desc, fill it into the device object in USBH */ - ENUM_STAGE_SET_CONFIG, /**< Send SET_CONFIGURATION request */ - ENUM_STAGE_CHECK_CONFIG, /**< Check that SET_CONFIGURATION request was successful */ - // Get string descriptors - ENUM_STAGE_GET_SHORT_LANGID_TABLE, /**< Get the header of the LANGID table string descriptor */ - ENUM_STAGE_CHECK_SHORT_LANGID_TABLE, /**< Save the bLength of the LANGID table string descriptor */ - ENUM_STAGE_GET_FULL_LANGID_TABLE, /**< Get the full LANGID table string descriptor */ - ENUM_STAGE_CHECK_FULL_LANGID_TABLE, /**< Check whether ENUM_LANGID is in the LANGID table */ - ENUM_STAGE_GET_SHORT_MANU_STR_DESC, /**< Get the header of the iManufacturer string descriptor */ - ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC, /**< Save the bLength of the iManufacturer string descriptor */ - ENUM_STAGE_GET_FULL_MANU_STR_DESC, /**< Get the full iManufacturer string descriptor */ - ENUM_STAGE_CHECK_FULL_MANU_STR_DESC, /**< Check and fill the full iManufacturer string descriptor */ - ENUM_STAGE_GET_SHORT_PROD_STR_DESC, /**< Get the header of the string descriptor at index iProduct */ - ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC, /**< Save the bLength of the iProduct string descriptor */ - ENUM_STAGE_GET_FULL_PROD_STR_DESC, /**< Get the full iProduct string descriptor */ - ENUM_STAGE_CHECK_FULL_PROD_STR_DESC, /**< Check and fill the full iProduct string descriptor */ - ENUM_STAGE_GET_SHORT_SER_STR_DESC, /**< Get the header of the string descriptor at index iSerialNumber */ - ENUM_STAGE_CHECK_SHORT_SER_STR_DESC, /**< Save the bLength of the iSerialNumber string descriptor */ - ENUM_STAGE_GET_FULL_SER_STR_DESC, /**< Get the full iSerialNumber string descriptor */ - ENUM_STAGE_CHECK_FULL_SER_STR_DESC, /**< Check and fill the full iSerialNumber string descriptor */ - // Cleanup - ENUM_STAGE_CLEANUP, /**< Clean up after successful enumeration. Adds enumerated device to USBH */ - ENUM_STAGE_CLEANUP_FAILED, /**< Cleanup failed enumeration. Free device resources */ -} enum_stage_t; - -const char *const enum_stage_strings[] = { - "NONE", - "START", - "GET_SHORT_DEV_DESC", - "CHECK_SHORT_DEV_DESC", - "SECOND_RESET", - "SET_ADDR", - "CHECK_ADDR", - "SET_ADDR_RECOVERY", - "GET_FULL_DEV_DESC", - "CHECK_FULL_DEV_DESC", - "GET_SHORT_CONFIG_DESC", - "CHECK_SHORT_CONFIG_DESC", - "GET_FULL_CONFIG_DESC", - "CHECK_FULL_CONFIG_DESC", - "SET_CONFIG", - "CHECK_CONFIG", - "GET_SHORT_LANGID_TABLE", - "CHECK_SHORT_LANGID_TABLE", - "GET_FULL_LANGID_TABLE", - "CHECK_FULL_LANGID_TABLE", - "GET_SHORT_MANU_STR_DESC", - "CHECK_SHORT_MANU_STR_DESC", - "GET_FULL_MANU_STR_DESC", - "CHECK_FULL_MANU_STR_DESC", - "GET_SHORT_PROD_STR_DESC", - "CHECK_SHORT_PROD_STR_DESC", - "GET_FULL_PROD_STR_DESC", - "CHECK_FULL_PROD_STR_DESC", - "GET_SHORT_SER_STR_DESC", - "CHECK_SHORT_SER_STR_DESC", - "GET_FULL_SER_STR_DESC", - "CHECK_FULL_SER_STR_DESC", - "CLEANUP", - "CLEANUP_FAILED", +struct dev_tree_node_s { + TAILQ_ENTRY(dev_tree_node_s) tailq_entry; /**< Entry for the device tree node object tailq */ + unsigned int uid; /**< Device's unique ID */ + usb_device_handle_t parent_dev_hdl; /**< Device's parent handle */ + uint8_t parent_port_num; /**< Device's parent port number */ }; -typedef struct { - // Constant - urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */ - // Initialized at start of a particular enumeration - usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */ - // Updated during enumeration - enum_stage_t stage; /**< Current enumeration stage */ - int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */ - uint8_t bMaxPacketSize0; /**< Max packet size of the device's EP0. Read from bMaxPacketSize0 field of device descriptor */ - uint16_t wTotalLength; /**< Total length of device's configuration descriptor. Read from wTotalLength field of config descriptor */ - uint8_t iManufacturer; /**< Index of the Manufacturer string descriptor */ - uint8_t iProduct; /**< Index of the Product string descriptor */ - uint8_t iSerialNumber; /**< Index of the Serial Number string descriptor */ - uint8_t str_desc_bLength; /**< Saved bLength from getting a short string descriptor */ - uint8_t bConfigurationValue; /**< Device's current configuration number */ - uint8_t enum_config_index; /**< Configuration index used during enumeration */ -#ifdef ENABLE_ENUM_FILTER_CALLBACK - usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ - bool graceful_exit; /**< Exit enumeration by user's request from the callback function */ -#endif // ENABLE_ENUM_FILTER_CALLBACK -} enum_ctrl_t; +typedef struct dev_tree_node_s dev_tree_node_t; typedef struct { - // Dynamic members require a critical section struct { union { struct { - uint32_t actions: 8; - uint32_t reserved24: 24; + hub_flag_action_t actions: 8; /**< Hub actions */ + uint32_t reserved24: 24; /**< Reserved */ }; - uint32_t val; - } flags; - root_port_state_t root_port_state; - unsigned int port_reqs; - } dynamic; - // Single thread members don't require a critical section so long as they are never accessed from multiple threads + uint32_t val; /**< Hub flag action value */ + } flags; /**< Hub flags */ + root_port_state_t root_port_state; /**< Root port state */ + unsigned int port_reqs; /**< Root port request flag */ + } dynamic; /**< Dynamic members. Require a critical section */ + struct { - unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected - enum_ctrl_t enum_ctrl; - } single_thread; - // Constant members do no change after installation thus do not require a critical section + TAILQ_HEAD(tailhead_devs, dev_tree_node_s) dev_nodes_tailq; /**< Tailq of attached devices */ + unsigned int next_uid; /**< Unique ID for next upcoming device */ + } single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */ + struct { - hcd_port_handle_t root_port_hdl; - usb_proc_req_cb_t proc_req_cb; - void *proc_req_cb_arg; - } constant; + hcd_port_handle_t root_port_hdl; /**< Root port HCD handle */ + usb_proc_req_cb_t proc_req_cb; /**< Process request callback */ + void *proc_req_cb_arg; /**< Process request callback argument */ + hub_event_cb_t event_cb; /**< Hub Driver event callback */ + void *event_cb_arg; /**< Event callback argument */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ } hub_driver_t; static hub_driver_t *p_hub_driver_obj = NULL; @@ -243,537 +147,182 @@ const char *HUB_DRIVER_TAG = "HUB"; */ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); +// ---------------------- Internal Logic ------------------------ + /** - * @brief Control transfer callback used for enumeration + * @brief Creates new device tree node and propagate HUB_EVENT_CONNECTED event * - * @param transfer Transfer object + * @return esp_err_t */ -static void enum_transfer_callback(usb_transfer_t *transfer); - -// ------------------------------------------------- Enum Functions ---------------------------------------------------- - -static bool enum_stage_start(enum_ctrl_t *enum_ctrl) +static esp_err_t new_dev_tree_node(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed) { - // Open the newly added device (at address 0) - ESP_ERROR_CHECK(usbh_devs_open(0, &p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); + esp_err_t ret; + unsigned int node_uid = p_hub_driver_obj->single_thread.next_uid; - // Get the speed of the device to set the initial MPS of EP0 - usb_device_info_t dev_info; - ESP_ERROR_CHECK(usbh_dev_get_info(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl, &dev_info)); - enum_ctrl->bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; - - // Lock the device for enumeration. This allows us call usbh_dev_set_...() functions during enumeration - ESP_ERROR_CHECK(usbh_dev_enum_lock(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); - - // Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb -#ifdef ENABLE_ENUM_FILTER_CALLBACK - enum_ctrl->graceful_exit = false; -#endif // ENABLE_ENUM_FILTER_CALLBACK - return true; -} - -static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl) -{ - if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); - return false; - } - return true; -} - -static void get_string_desc_index_and_langid(enum_ctrl_t *enum_ctrl, uint8_t *index, uint16_t *langid) -{ - switch (enum_ctrl->stage) { - case ENUM_STAGE_GET_SHORT_LANGID_TABLE: - case ENUM_STAGE_GET_FULL_LANGID_TABLE: - *index = 0; // The LANGID table uses an index of 0 - *langid = 0; // Getting the LANGID table itself should use a LANGID of 0 - break; - case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: - case ENUM_STAGE_GET_FULL_MANU_STR_DESC: - *index = enum_ctrl->iManufacturer; - *langid = ENUM_LANGID; // Use the default LANGID - break; - case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: - case ENUM_STAGE_GET_FULL_PROD_STR_DESC: - *index = enum_ctrl->iProduct; - *langid = ENUM_LANGID; // Use the default LANGID - break; - case ENUM_STAGE_GET_SHORT_SER_STR_DESC: - case ENUM_STAGE_GET_FULL_SER_STR_DESC: - *index = enum_ctrl->iSerialNumber; - *langid = ENUM_LANGID; // Use the default LANGID - break; - default: - // Should not occur - abort(); - break; - } -} - -static bool set_config_index(enum_ctrl_t *enum_ctrl, const usb_device_desc_t *device_desc) -{ -#ifdef ENABLE_ENUM_FILTER_CALLBACK - // Callback enabled in the menuncofig, but the callback function was not defined - if (enum_ctrl->enum_filter_cb == NULL) { - enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT; - return true; + dev_tree_node_t *dev_tree_node = heap_caps_calloc(1, sizeof(dev_tree_node_t), MALLOC_CAP_DEFAULT); + if (dev_tree_node == NULL) { + return ESP_ERR_NO_MEM; } - uint8_t enum_config_index; - const bool enum_continue = enum_ctrl->enum_filter_cb(device_desc, &enum_config_index); + // Allocate a new USBH device + usbh_dev_params_t params = { + .uid = node_uid, + .speed = speed, + .root_port_hdl = p_hub_driver_obj->constant.root_port_hdl, // Always the same for all devices + // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH + .parent_dev_hdl = parent_dev_hdl, + .parent_port_num = parent_port_num, + }; - // User's request NOT to enumerate the USB device - if (!enum_continue) { - ESP_LOGW(HUB_DRIVER_TAG, "USB device (PID = 0x%x, VID = 0x%x) will not be enumerated", device_desc->idProduct, device_desc->idVendor); - enum_ctrl->graceful_exit = true; - return false; + ret = usbh_devs_add(¶ms); + if (ret != ESP_OK) { + // USBH devs add could failed due to lack of free hcd channels + // TODO: IDF-10044 Hub should recover after running out of hcd channels + goto fail; } - // Set configuration descriptor - if ((enum_config_index == 0) || (enum_config_index > device_desc->bNumConfigurations)) { - ESP_LOGW(HUB_DRIVER_TAG, "bConfigurationValue %d provided by user, device will be configured with configuration descriptor 1", enum_config_index); - enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT; - } else { - enum_ctrl->enum_config_index = enum_config_index - 1; - } -#else // ENABLE_ENUM_FILTER_CALLBACK - enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT; -#endif // ENABLE_ENUM_FILTER_CALLBACK + dev_tree_node->uid = node_uid; + dev_tree_node->parent_dev_hdl = parent_dev_hdl; + dev_tree_node->parent_port_num = parent_port_num; + TAILQ_INSERT_TAIL(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry); - return true; -} + p_hub_driver_obj->single_thread.next_uid++; + if (p_hub_driver_obj->single_thread.next_uid == 0) { + ESP_LOGW(HUB_DRIVER_TAG, "Counter overflowed, possibility of uid collisions"); + p_hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID; + } + // Verify presence of a device with newly prepared uid in USBH + // TODO: IDF-10022 Provide a mechanism to request presence status of a device with uid in USBH device object list + // Return if device uid is not in USBH device object list, repeat until uid will be founded -static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl) -{ - usb_transfer_t *transfer = &enum_ctrl->urb->transfer; - switch (enum_ctrl->stage) { - case ENUM_STAGE_GET_SHORT_DEV_DESC: { - // Initialize a short device descriptor request - USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)transfer->data_buffer); - ((usb_setup_packet_t *)transfer->data_buffer)->wLength = ENUM_SHORT_DESC_REQ_LEN; - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(ENUM_SHORT_DESC_REQ_LEN, enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + ENUM_SHORT_DESC_REQ_LEN; - break; - } - case ENUM_STAGE_SET_ADDR: { - USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)transfer->data_buffer, ENUM_DEV_ADDR); - transfer->num_bytes = sizeof(usb_setup_packet_t); // No data stage - enum_ctrl->expect_num_bytes = 0; // OUT transfer. No need to check number of bytes returned - break; - } - case ENUM_STAGE_GET_FULL_DEV_DESC: { - USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)transfer->data_buffer); - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_device_desc_t), enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly sizeof(usb_device_desc_t) bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t); - break; - } - case ENUM_STAGE_GET_SHORT_CONFIG_DESC: { - // Get a short config descriptor at index 0 - USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, enum_ctrl->enum_config_index, ENUM_SHORT_DESC_REQ_LEN); - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(ENUM_SHORT_DESC_REQ_LEN, enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + ENUM_SHORT_DESC_REQ_LEN; - break; - } - case ENUM_STAGE_GET_FULL_CONFIG_DESC: { - // Get the full configuration descriptor at index 0, requesting its exact length. - USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, enum_ctrl->enum_config_index, enum_ctrl->wTotalLength); - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(enum_ctrl->wTotalLength, enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly wTotalLength bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + enum_ctrl->wTotalLength; - break; - } - case ENUM_STAGE_SET_CONFIG: { - USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)transfer->data_buffer, enum_ctrl->bConfigurationValue); - transfer->num_bytes = sizeof(usb_setup_packet_t); // No data stage - enum_ctrl->expect_num_bytes = 0; // OUT transfer. No need to check number of bytes returned - break; - } - case ENUM_STAGE_GET_SHORT_LANGID_TABLE: - case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: - case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: - case ENUM_STAGE_GET_SHORT_SER_STR_DESC: { - uint8_t index; - uint16_t langid; - get_string_desc_index_and_langid(enum_ctrl, &index, &langid); - // Get only the header of the string descriptor - USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)transfer->data_buffer, - index, - langid, - sizeof(usb_str_desc_t)); - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_str_desc_t), enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly sizeof(usb_str_desc_t) bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_str_desc_t); - break; - } - case ENUM_STAGE_GET_FULL_LANGID_TABLE: - case ENUM_STAGE_GET_FULL_MANU_STR_DESC: - case ENUM_STAGE_GET_FULL_PROD_STR_DESC: - case ENUM_STAGE_GET_FULL_SER_STR_DESC: { - uint8_t index; - uint16_t langid; - get_string_desc_index_and_langid(enum_ctrl, &index, &langid); - // Get the full string descriptor at a particular index, requesting the descriptors exact length - USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)transfer->data_buffer, - index, - langid, - enum_ctrl->str_desc_bLength); - transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(enum_ctrl->str_desc_bLength, enum_ctrl->bMaxPacketSize0); - // IN data stage should return exactly str_desc_bLength bytes - enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + enum_ctrl->str_desc_bLength; - break; - } - default: // Should never occur - abort(); - break; - } - if (usbh_dev_submit_ctrl_urb(enum_ctrl->dev_hdl, enum_ctrl->urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to submit: %s", enum_stage_strings[enum_ctrl->stage]); - return false; - } - return true; -} + ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): new", node_uid); -static bool enum_stage_wait(enum_ctrl_t *enum_ctrl) -{ - switch (enum_ctrl->stage) { - case ENUM_STAGE_SET_ADDR_RECOVERY: { - vTaskDelay(pdMS_TO_TICKS(SET_ADDR_RECOVERY_INTERVAL_MS)); // Need a short delay before device is ready. Todo: IDF-7007 - return true; - } + hub_event_data_t event_data = { + .event = HUB_EVENT_CONNECTED, + .connected = { + .uid = node_uid, + }, + }; + p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg); - default: // Should never occur - abort(); - break; - } + return ret; - return false; -} - -static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) -{ - // Check transfer status - usb_transfer_t *transfer = &enum_ctrl->urb->transfer; - if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Bad transfer status %d: %s", transfer->status, enum_stage_strings[enum_ctrl->stage]); - return false; - } - // Check IN transfer returned the expected correct number of bytes - if (enum_ctrl->expect_num_bytes != 0 && transfer->actual_num_bytes != enum_ctrl->expect_num_bytes) { - if (transfer->actual_num_bytes > enum_ctrl->expect_num_bytes) { - // The device returned more bytes than requested. - // This violates the USB specs chapter 9.3.5, but we can continue - ESP_LOGW(HUB_DRIVER_TAG, "Incorrect number of bytes returned %d: %s", transfer->actual_num_bytes, enum_stage_strings[enum_ctrl->stage]); - } else { - // The device returned less bytes than requested. We cannot continue. - ESP_LOGE(HUB_DRIVER_TAG, "Incorrect number of bytes returned %d: %s", transfer->actual_num_bytes, enum_stage_strings[enum_ctrl->stage]); - return false; - } - } - - // Stage specific checks and updates - bool ret; - switch (enum_ctrl->stage) { - case ENUM_STAGE_CHECK_SHORT_DEV_DESC: { - const usb_device_desc_t *device_desc = (usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - // Check if the returned descriptor is corrupted - if (device_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_DEVICE) { - ESP_LOGE(HUB_DRIVER_TAG, "Short dev desc corrupt"); - ret = false; - break; - } - // Update and save the MPS of the EP0 - if (usbh_dev_set_ep0_mps(enum_ctrl->dev_hdl, device_desc->bMaxPacketSize0) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to update MPS"); - ret = false; - break; - } - // Save the actual MPS of EP0 - enum_ctrl->bMaxPacketSize0 = device_desc->bMaxPacketSize0; - ret = true; - break; - } - case ENUM_STAGE_CHECK_ADDR: { - // Update the device's address - ESP_ERROR_CHECK(usbh_dev_set_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); - ret = true; - break; - } - case ENUM_STAGE_CHECK_FULL_DEV_DESC: { - // Set the device's descriptor - const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - ESP_ERROR_CHECK(usbh_dev_set_desc(enum_ctrl->dev_hdl, device_desc)); - enum_ctrl->iManufacturer = device_desc->iManufacturer; - enum_ctrl->iProduct = device_desc->iProduct; - enum_ctrl->iSerialNumber = device_desc->iSerialNumber; - ret = set_config_index(enum_ctrl, device_desc); - break; - } - case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: { - const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - // Check if the returned descriptor is corrupted - if (config_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_CONFIGURATION) { - ESP_LOGE(HUB_DRIVER_TAG, "Short config desc corrupt"); - ret = false; - break; - } -#if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT16_MAX) // Suppress -Wtype-limits warning due to uint16_t wTotalLength - // Check if the descriptor is too long to be supported - if (config_desc->wTotalLength > ENUM_CTRL_TRANSFER_MAX_DATA_LEN) { - ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor larger than control transfer max length"); - ret = false; - break; - } -#endif - // Save the configuration descriptors full length - enum_ctrl->wTotalLength = config_desc->wTotalLength; - ret = true; - break; - } - case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: { - // Set the device's configuration descriptor - const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - enum_ctrl->bConfigurationValue = config_desc->bConfigurationValue; - ESP_ERROR_CHECK(usbh_dev_set_config_desc(enum_ctrl->dev_hdl, config_desc)); - ret = true; - break; - } - case ENUM_STAGE_CHECK_CONFIG: { - ret = true; - // Nothing to do - break; - } - case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: - case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: { - const usb_str_desc_t *str_desc = (usb_str_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - // Check if the returned descriptor is supported or corrupted - if (str_desc->bDescriptorType == 0) { - ESP_LOGW(HUB_DRIVER_TAG, "String desc not supported"); - ret = false; - break; - } else if (str_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_STRING) { - ESP_LOGE(HUB_DRIVER_TAG, "Full string desc corrupt"); - ret = false; - break; - } -#if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT8_MAX) // Suppress -Wtype-limits warning due to uint8_t bLength - // Check if the descriptor is too long to be supported - if (str_desc->bLength > (uint32_t)ENUM_CTRL_TRANSFER_MAX_DATA_LEN) { - ESP_LOGE(HUB_DRIVER_TAG, "String descriptor larger than control transfer max length"); - ret = false; - break; - } -#endif - // Save the descriptors full length - enum_ctrl->str_desc_bLength = str_desc->bLength; - ret = true; - break; - } - case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: - case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: - case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: - case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: { - const usb_str_desc_t *str_desc = (usb_str_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); - // Check if the returned descriptor is supported or corrupted - if (str_desc->bDescriptorType == 0) { - ESP_LOGW(HUB_DRIVER_TAG, "String desc not supported"); - ret = false; - break; - } else if (str_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_STRING) { - ESP_LOGE(HUB_DRIVER_TAG, "Full string desc corrupt"); - ret = false; - break; - } - if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_LANGID_TABLE) { - // Scan the LANGID table for our target LANGID - bool target_langid_found = false; - int langid_table_num_entries = (str_desc->bLength - sizeof(usb_str_desc_t)) / 2; // Each LANGID is 2 bytes - for (int i = 0; i < langid_table_num_entries; i++) { // Each LANGID is 2 bytes - if (str_desc->wData[i] == ENUM_LANGID) { - target_langid_found = true; - break; - } - } - if (!target_langid_found) { - ESP_LOGE(HUB_DRIVER_TAG, "LANGID 0x%x not found", ENUM_LANGID); - } - ret = target_langid_found; - break; - } else { - // Fill the string descriptor into the device object - int select; - if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_MANU_STR_DESC) { - select = 0; - } else if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_PROD_STR_DESC) { - select = 1; - } else { // ENUM_STAGE_CHECK_FULL_PROD_STR_DESC - select = 2; - } - ESP_ERROR_CHECK(usbh_dev_set_str_desc(enum_ctrl->dev_hdl, str_desc, select)); - ret = true; - break; - } - } - default: // Should never occur - ret = false; - abort(); - break; - } +fail: + heap_caps_free(dev_tree_node); return ret; } -static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) +static esp_err_t dev_tree_node_reset_completed(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) { - // Unlock the device as we are done with the enumeration - ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); - // Propagate a new device event - ESP_ERROR_CHECK(usbh_devs_new_dev_event(enum_ctrl->dev_hdl)); - // We are done with using the device. Close it. - ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); - // Clear values in enum_ctrl - enum_ctrl->dev_hdl = NULL; -} - -static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) -{ - if (enum_ctrl->dev_hdl) { - // Close the device and unlock it as we done with enumeration - ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl)); - ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl)); - // We allow this to fail in case the device object was already freed - usbh_devs_remove(ENUM_DEV_UID); - } - // Clear values in enum_ctrl - enum_ctrl->dev_hdl = NULL; -} - -static enum_stage_t get_next_stage(enum_stage_t old_stage, enum_ctrl_t *enum_ctrl) -{ - enum_stage_t new_stage = old_stage + 1; - // Skip the GET_DESCRIPTOR string type corresponding stages if a particular index is 0. - while (((new_stage == ENUM_STAGE_GET_SHORT_MANU_STR_DESC || - new_stage == ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC || - new_stage == ENUM_STAGE_GET_FULL_MANU_STR_DESC || - new_stage == ENUM_STAGE_CHECK_FULL_MANU_STR_DESC) && enum_ctrl->iManufacturer == 0) || - ((new_stage == ENUM_STAGE_GET_SHORT_PROD_STR_DESC || - new_stage == ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC || - new_stage == ENUM_STAGE_GET_FULL_PROD_STR_DESC || - new_stage == ENUM_STAGE_CHECK_FULL_PROD_STR_DESC) && enum_ctrl->iProduct == 0) || - ((new_stage == ENUM_STAGE_GET_SHORT_SER_STR_DESC || - new_stage == ENUM_STAGE_CHECK_SHORT_SER_STR_DESC || - new_stage == ENUM_STAGE_GET_FULL_SER_STR_DESC || - new_stage == ENUM_STAGE_CHECK_FULL_SER_STR_DESC) && enum_ctrl->iSerialNumber == 0)) { - new_stage++; - } - return new_stage; -} - -static void enum_set_next_stage(enum_ctrl_t *enum_ctrl, bool last_stage_pass) -{ - // Set next stage - if (last_stage_pass) { - if (enum_ctrl->stage != ENUM_STAGE_NONE && - enum_ctrl->stage != ENUM_STAGE_CLEANUP && - enum_ctrl->stage != ENUM_STAGE_CLEANUP_FAILED) { - enum_ctrl->stage = get_next_stage(enum_ctrl->stage, enum_ctrl); - } else { - enum_ctrl->stage = ENUM_STAGE_NONE; - } - } else { - switch (enum_ctrl->stage) { - case ENUM_STAGE_START: - // Stage failed but clean up not required - enum_ctrl->stage = ENUM_STAGE_NONE; - break; - case ENUM_STAGE_GET_SHORT_LANGID_TABLE: - case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: - case ENUM_STAGE_GET_FULL_LANGID_TABLE: - case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: - case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: - case ENUM_STAGE_GET_FULL_MANU_STR_DESC: - case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: - case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: - case ENUM_STAGE_GET_FULL_PROD_STR_DESC: - case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: - case ENUM_STAGE_GET_SHORT_SER_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: - case ENUM_STAGE_GET_FULL_SER_STR_DESC: - case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: - // String descriptor stages are allow to fail. We just don't fetch them and treat enumeration as successful - enum_ctrl->stage = ENUM_STAGE_CLEANUP; - break; - default: - // Enumeration failed. Go to failure clean up - enum_ctrl->stage = ENUM_STAGE_CLEANUP_FAILED; + dev_tree_node_t *dev_tree_node = NULL; + dev_tree_node_t *dev_tree_iter; + // Search the device tree nodes list for a device node with the specified parent + TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) { + if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl && + dev_tree_iter->parent_port_num == parent_port_num) { + dev_tree_node = dev_tree_iter; break; } } - // These stages are not waiting for a callback, so we need to re-trigger the enum event - bool re_trigger; - switch (enum_ctrl->stage) { - case ENUM_STAGE_GET_SHORT_DEV_DESC: - case ENUM_STAGE_SECOND_RESET: - case ENUM_STAGE_SET_ADDR: - case ENUM_STAGE_SET_ADDR_RECOVERY: - case ENUM_STAGE_GET_FULL_DEV_DESC: - case ENUM_STAGE_GET_SHORT_CONFIG_DESC: - case ENUM_STAGE_GET_FULL_CONFIG_DESC: - case ENUM_STAGE_SET_CONFIG: - case ENUM_STAGE_GET_SHORT_LANGID_TABLE: - case ENUM_STAGE_GET_FULL_LANGID_TABLE: - case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: - case ENUM_STAGE_GET_FULL_MANU_STR_DESC: - case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: - case ENUM_STAGE_GET_FULL_PROD_STR_DESC: - case ENUM_STAGE_GET_SHORT_SER_STR_DESC: - case ENUM_STAGE_GET_FULL_SER_STR_DESC: - case ENUM_STAGE_CLEANUP: - case ENUM_STAGE_CLEANUP_FAILED: - re_trigger = true; - break; - default: - re_trigger = false; - break; - } - if (re_trigger) { - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - HUB_DRIVER_EXIT_CRITICAL(); + if (dev_tree_node == NULL) { + ESP_LOGE(HUB_DRIVER_TAG, "Reset completed, but device tree node with port=%d not found", parent_port_num); + return ESP_ERR_NOT_FOUND; } + + hub_event_data_t event_data = { + .event = HUB_EVENT_RESET_COMPLETED, + .reset_completed = { + .uid = dev_tree_node->uid, + }, + }; + p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg); + return ESP_OK; } -// ------------------------------------------------- Event Handling ---------------------------------------------------- +static esp_err_t dev_tree_node_dev_gone(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + dev_tree_node_t *dev_tree_node = NULL; + dev_tree_node_t *dev_tree_iter; + // Search the device tree nodes list for a device node with the specified parent + TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) { + if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl && + dev_tree_iter->parent_port_num == parent_port_num) { + dev_tree_node = dev_tree_iter; + break; + } + } + + if (dev_tree_node == NULL) { + ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): device gone", dev_tree_node->uid); + + hub_event_data_t event_data = { + .event = HUB_EVENT_DISCONNECTED, + .disconnected = { + .uid = dev_tree_node->uid, + }, + }; + p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg); + + return ESP_OK; +} + +/** + * @brief Frees device tree node + * + * @return esp_err_t + */ +static esp_err_t dev_tree_node_remove_by_parent(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + dev_tree_node_t *dev_tree_node = NULL; + dev_tree_node_t *dev_tree_iter; + // Search the device tree nodes list for a device node with the specified parent + TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) { + if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl && + dev_tree_iter->parent_port_num == parent_port_num) { + dev_tree_node = dev_tree_iter; + break; + } + } + + if (dev_tree_node == NULL) { + ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num); + return ESP_ERR_NOT_FOUND; + } + + ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): freeing", dev_tree_node->uid); + + TAILQ_REMOVE(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry); + heap_caps_free(dev_tree_node); + return ESP_OK; +} // ---------------------- Callbacks ------------------------ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) { HUB_DRIVER_ENTER_CRITICAL_SAFE(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); assert(in_isr); // Currently, this callback should only ever be called from an ISR context return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } -static void enum_transfer_callback(usb_transfer_t *transfer) +#ifdef ENABLE_USB_HUBS +static bool ext_hub_callback(bool in_isr, void *user_arg) { - // We simply trigger a processing request to handle the completed enumeration control transfer HUB_DRIVER_ENTER_CRITICAL_SAFE(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_HUB; HUB_DRIVER_EXIT_CRITICAL_SAFE(); - p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); + return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } +#endif // ENABLE_USB_HUBS // ---------------------- Handlers ------------------------- - static void root_port_handle_events(hcd_port_handle_t root_port_hdl) { hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl); @@ -791,18 +340,16 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { goto new_dev_err; } - // Allocate a new device. We use a fixed ENUM_DEV_UID for now since we only support a single device - if (usbh_devs_add(ENUM_DEV_UID, speed, p_hub_driver_obj->constant.root_port_hdl) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device"); + + if (new_dev_tree_node(NULL, 0, speed) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new device"); goto new_dev_err; } - p_hub_driver_obj->single_thread.root_dev_uid = ENUM_DEV_UID; - // Start enumeration + + // Change Port state HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED; HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; break; new_dev_err: // We allow this to fail in case a disconnect/port error happens while disabling. @@ -813,31 +360,29 @@ reset_err: case HCD_PORT_EVENT_DISCONNECTION: case HCD_PORT_EVENT_ERROR: case HCD_PORT_EVENT_OVERCURRENT: { - bool pass_event_to_usbh = false; + bool port_has_device = false; HUB_DRIVER_ENTER_CRITICAL(); switch (p_hub_driver_obj->dynamic.root_port_state) { case ROOT_PORT_STATE_POWERED: // This occurred before enumeration case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled // Therefore, there's no device object to clean up, and we can go straight to port recovery p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ; break; case ROOT_PORT_STATE_ENABLED: // There is an enabled (active) device. We need to indicate to USBH that the device is gone - pass_event_to_usbh = true; + port_has_device = true; break; default: abort(); // Should never occur break; } - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); - if (pass_event_to_usbh) { - // The port must have a device object - assert(p_hub_driver_obj->single_thread.root_dev_uid != 0); - // We allow this to fail in case the device object was already freed - usbh_devs_remove(p_hub_driver_obj->single_thread.root_dev_uid); + + if (port_has_device) { + ESP_ERROR_CHECK(dev_tree_node_dev_gone(NULL, 0)); } + break; } default: @@ -870,81 +415,43 @@ static void root_port_req(hcd_port_handle_t root_port_hdl) } } -static void enum_handle_events(void) +static esp_err_t root_port_recycle(void) { - bool stage_pass; - enum_ctrl_t *enum_ctrl = &p_hub_driver_obj->single_thread.enum_ctrl; - switch (enum_ctrl->stage) { - case ENUM_STAGE_START: - stage_pass = enum_stage_start(enum_ctrl); + // Device is free, we can now request its port be recycled + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + HUB_DRIVER_ENTER_CRITICAL(); + // How the port is recycled will depend on the port's state + switch (port_state) { + case HCD_PORT_STATE_ENABLED: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; break; - case ENUM_STAGE_SECOND_RESET: - stage_pass = enum_stage_second_reset(enum_ctrl); - break; - // Transfer submission stages - case ENUM_STAGE_GET_SHORT_DEV_DESC: - case ENUM_STAGE_SET_ADDR: - case ENUM_STAGE_GET_FULL_DEV_DESC: - case ENUM_STAGE_GET_SHORT_CONFIG_DESC: - case ENUM_STAGE_GET_FULL_CONFIG_DESC: - case ENUM_STAGE_SET_CONFIG: - case ENUM_STAGE_GET_SHORT_LANGID_TABLE: - case ENUM_STAGE_GET_FULL_LANGID_TABLE: - case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: - case ENUM_STAGE_GET_FULL_MANU_STR_DESC: - case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: - case ENUM_STAGE_GET_FULL_PROD_STR_DESC: - case ENUM_STAGE_GET_SHORT_SER_STR_DESC: - case ENUM_STAGE_GET_FULL_SER_STR_DESC: - stage_pass = enum_stage_transfer(enum_ctrl); - break; - // Recovery interval - case ENUM_STAGE_SET_ADDR_RECOVERY: - stage_pass = enum_stage_wait(enum_ctrl); - break; - // Transfer check stages - case ENUM_STAGE_CHECK_SHORT_DEV_DESC: - case ENUM_STAGE_CHECK_ADDR: - case ENUM_STAGE_CHECK_FULL_DEV_DESC: - case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: - case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: - case ENUM_STAGE_CHECK_CONFIG: - case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: - case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: - case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC: - case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC: - case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: - case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: - case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: - stage_pass = enum_stage_transfer_check(enum_ctrl); - break; - case ENUM_STAGE_CLEANUP: - enum_stage_cleanup(enum_ctrl); - stage_pass = true; - break; - case ENUM_STAGE_CLEANUP_FAILED: - enum_stage_cleanup_failed(enum_ctrl); - stage_pass = true; + case HCD_PORT_STATE_RECOVERY: + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; break; default: - stage_pass = true; + abort(); // Should never occur break; } - if (stage_pass) { - ESP_LOGD(HUB_DRIVER_TAG, "Stage done: %s", enum_stage_strings[enum_ctrl->stage]); - } else { -#ifdef ENABLE_ENUM_FILTER_CALLBACK - if (!enum_ctrl->graceful_exit) { - ESP_LOGE(HUB_DRIVER_TAG, "Stage failed: %s", enum_stage_strings[enum_ctrl->stage]); - } else { - ESP_LOGD(HUB_DRIVER_TAG, "Stage done: %s", enum_stage_strings[enum_ctrl->stage]); - } -#else // ENABLE_ENUM_FILTER_CALLBACK - ESP_LOGE(HUB_DRIVER_TAG, "Stage failed: %s", enum_stage_strings[enum_ctrl->stage]); -#endif // ENABLE_ENUM_FILTER_CALLBACK - } - enum_set_next_stage(enum_ctrl, stage_pass); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ; + HUB_DRIVER_EXIT_CRITICAL(); + + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); + + return ESP_OK; +} + +static esp_err_t root_port_disable(void) +{ + hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); + HUB_DRIVER_CHECK(port_state == HCD_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE); + + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ; + HUB_DRIVER_EXIT_CRITICAL(); + + p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); + return ESP_OK; } // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- @@ -958,12 +465,24 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) // Allocate Hub driver object hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT); - urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0); - if (hub_driver_obj == NULL || enum_urb == NULL) { + if (hub_driver_obj == NULL) { return ESP_ERR_NO_MEM; } - enum_urb->usb_host_client = (void *)hub_driver_obj; - enum_urb->transfer.callback = enum_transfer_callback; + +#if ENABLE_USB_HUBS + // Install External HUB driver + ext_hub_config_t ext_hub_config = { + .proc_req_cb = ext_hub_callback, + .port_driver = NULL, + }; + ret = ext_hub_install(&ext_hub_config); + if (ret != ESP_OK) { + goto err_ext_hub; + } + *client_ret = ext_hub_get_client(); +#else + *client_ret = NULL; +#endif // ENABLE_USB_HUBS // Install HCD port hcd_port_config_t port_config = { @@ -972,24 +491,24 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) .callback_arg = NULL, .context = NULL, }; - hcd_port_handle_t port_hdl; - ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &port_hdl); + hcd_port_handle_t root_port_hdl; + ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &root_port_hdl); if (ret != ESP_OK) { goto err; } // Initialize Hub driver object - hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; - hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; -#ifdef ENABLE_ENUM_FILTER_CALLBACK - hub_driver_obj->single_thread.enum_ctrl.enum_filter_cb = hub_config->enum_filter_cb; -#endif // ENABLE_ENUM_FILTER_CALLBACK - hub_driver_obj->constant.root_port_hdl = port_hdl; + hub_driver_obj->constant.root_port_hdl = root_port_hdl; hub_driver_obj->constant.proc_req_cb = hub_config->proc_req_cb; hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; + hub_driver_obj->constant.event_cb = hub_config->event_cb; + hub_driver_obj->constant.event_cb_arg = hub_config->event_cb_arg; + hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID; + TAILQ_INIT(&hub_driver_obj->single_thread.dev_nodes_tailq); + // Driver is not installed, we can modify dynamic section outside of the critical section + hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; HUB_DRIVER_ENTER_CRITICAL(); - hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; if (p_hub_driver_obj != NULL) { HUB_DRIVER_EXIT_CRITICAL(); ret = ESP_ERR_INVALID_STATE; @@ -998,16 +517,15 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); - // Write-back client_ret pointer - *client_ret = (void *)hub_driver_obj; - ret = ESP_OK; - return ret; assign_err: - ESP_ERROR_CHECK(hcd_port_deinit(port_hdl)); + ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl)); err: - urb_free(enum_urb); +#if ENABLE_USB_HUBS + ext_hub_uninstall(); +err_ext_hub: +#endif // ENABLE_USB_HUBS heap_caps_free(hub_driver_obj); return ret; } @@ -1021,9 +539,12 @@ esp_err_t hub_uninstall(void) p_hub_driver_obj = NULL; HUB_DRIVER_EXIT_CRITICAL(); +#if ENABLE_USB_HUBS + ESP_ERROR_CHECK(ext_hub_uninstall()); +#endif // ENABLE_USB_HUBS + ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl)); // Free Hub driver resources - urb_free(hub_driver_obj->single_thread.enum_ctrl.urb); heap_caps_free(hub_driver_obj); return ESP_OK; } @@ -1061,34 +582,130 @@ esp_err_t hub_root_stop(void) return ret; } -esp_err_t hub_port_recycle(unsigned int dev_uid) +esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid) { - if (dev_uid == p_hub_driver_obj->single_thread.root_dev_uid) { - // Device is free, we can now request its port be recycled - hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl); - p_hub_driver_obj->single_thread.root_dev_uid = 0; - HUB_DRIVER_ENTER_CRITICAL(); - // How the port is recycled will depend on the port's state - switch (port_state) { - case HCD_PORT_STATE_ENABLED: - p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE; - break; - case HCD_PORT_STATE_RECOVERY: - p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; - break; - default: - abort(); // Should never occur - break; - } - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; - HUB_DRIVER_EXIT_CRITICAL(); + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + esp_err_t ret; - p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); + if (parent_port_num == 0) { + ret = root_port_recycle(); + } else { +#if ENABLE_USB_HUBS + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_recycle(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS } - return ESP_OK; + if (ret == ESP_OK) { + ESP_ERROR_CHECK(dev_tree_node_remove_by_parent(parent_dev_hdl, parent_port_num)); + } + + return ret; } +esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + esp_err_t ret; + + if (parent_port_num == 0) { + ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET); + if (ret != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset"); + } else { + ret = dev_tree_node_reset_completed(NULL, 0); + } + } else { +#if ENABLE_USB_HUBS + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_reset(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Resetting External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS + } + return ret; +} + +esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + esp_err_t ret; + + if (parent_port_num == 0) { + // Root port no need to be activated + ret = ESP_OK; + } else { +#if ENABLE_USB_HUBS + // External Hub port + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_active(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS + } + return ret; +} + +esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + esp_err_t ret; + + if (parent_port_num == 0) { + ret = root_port_disable(); + } else { +#if ENABLE_USB_HUBS + // External Hub port + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_disable(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS + } + return ret; +} + +#if ENABLE_USB_HUBS +esp_err_t hub_notify_new_dev(uint8_t dev_addr) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_new_dev(dev_addr); +} + +esp_err_t hub_notify_dev_gone(uint8_t dev_addr) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_dev_gone(dev_addr); +} + +esp_err_t hub_notify_all_free(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_all_free(); +} +#endif // ENABLE_USB_HUBS + esp_err_t hub_process(void) { HUB_DRIVER_ENTER_CRITICAL(); @@ -1097,20 +714,29 @@ esp_err_t hub_process(void) HUB_DRIVER_EXIT_CRITICAL(); while (action_flags) { - if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { +#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()); + } +#endif // ENABLE_USB_HUBS + if (action_flags & HUB_DRIVER_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); } - if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) { + if (action_flags & HUB_DRIVER_ACTION_ROOT_REQ) { root_port_req(p_hub_driver_obj->constant.root_port_hdl); } - if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { - enum_handle_events(); - } HUB_DRIVER_ENTER_CRITICAL(); action_flags = p_hub_driver_obj->dynamic.flags.actions; p_hub_driver_obj->dynamic.flags.actions = 0; HUB_DRIVER_EXIT_CRITICAL(); } + return ESP_OK; } diff --git a/components/usb/include/usb/usb_types_ch11.h b/components/usb/include/usb/usb_types_ch11.h index 6d13998cc5..a30e9d3919 100644 --- a/components/usb/include/usb/usb_types_ch11.h +++ b/components/usb/include/usb/usb_types_ch11.h @@ -66,7 +66,34 @@ typedef enum { } usb_hub_port_feature_t; /** - * @brief Size of a USB Hub Port Status and Hub Change results + * @brief USB Hub characteristics + * + * See USB 2.0 spec Table 11-13, offset 3 + */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL (0) /**< All ports power control at once */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV (1) /**< Individual port power control */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_NO (2) /**< No power switching */ + +#define USB_W_HUB_CHARS_PORT_OVER_CURR_ALL (0) /**< All ports Over-Current reporting */ +#define USB_W_HUB_CHARS_PORT_OVER_CURR_INDV (1) /**< Individual port Over-current reporting */ +#define USB_W_HUB_CHARS_PORT_OVER_CURR_NO (2) /**< No Over-current Protection support */ + +#define USB_W_HUB_CHARS_TTTT_8_BITS (0) /**< TT requires at most 8 FS bit times of inter transaction gap on a full-/low-speed downstream bus */ +#define USB_W_HUB_CHARS_TTTT_16_BITS (1) /**< TT requires at most 16 FS bit times */ +#define USB_W_HUB_CHARS_TTTT_24_BITS (2) /**< TT requires at most 24 FS bit times */ +#define USB_W_HUB_CHARS_TTTT_32_BITS (3) /**< TT requires at most 32 FS bit times */ + +/** + * @brief USB Hub bDeviceProtocol + */ +#define USB_B_DEV_PROTOCOL_HUB_FS (0) /**< Full speed hub */ +#define USB_B_DEV_PROTOCOL_HUB_HS_NO_TT (0) /**< Hi-speed hub without TT */ +#define USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT (1) /**< Hi-speed hub with single TT */ +#define USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT (2) /**< Hi-speed hub with multiple TT */ +#define USB_B_DEV_PROTOCOL_HUB_SS (3) /**< Super speed hub */ + +/** + * @brief USB Hub Port Status and Hub Change results size */ #define USB_PORT_STATUS_SIZE 4 @@ -148,7 +175,17 @@ typedef struct { uint8_t bDescLength; /**< Number of bytes in this descriptor, including this byte */ uint8_t bDescriptorType; /**< Descriptor Type, value: 29H for Hub descriptor */ uint8_t bNbrPorts; /**< Number of downstream facing ports that this Hub supports */ - uint16_t wHubCharacteristics; /**< Logical Power Switching Mode, Compound Device, Over-current Protection Mode, TT Think Time, Port Indicators Supported */ + union { + struct { + uint16_t power_switching: 2; + uint16_t compound: 1; + uint16_t ovr_current_protect: 2; + uint16_t tt_think_time: 2; + uint16_t indicator_support: 1; + uint16_t reserved: 8; + }; + uint16_t val; /**< Hub Characteristics value */ + } wHubCharacteristics; /**< Hub Characteristics */ uint8_t bPwrOn2PwrGood; /**< Time (in 2 ms intervals) from the time the power-on sequence begins on a port until power is good on that port */ uint8_t bHubContrCurrent; /**< Maximum current requirements of the Hub Controller electronics in mA. */ } __attribute__((packed)) usb_hub_descriptor_t; diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index c09440eb00..500a5dc51c 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/components/usb/include/usb/usb_types_ch9.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -101,6 +101,21 @@ typedef union { } usb_setup_packet_t; ESP_STATIC_ASSERT(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb_setup_packet_t incorrect"); +/** + * @brief Structure representing a USB device status + * + * See Figures 9-4 Information Returned by a GetStatus() Request to a Device of USB2.0 specification for more details + */ +typedef union { + struct { + uint16_t self_powered: 1; /**< 1 - Device is currently self-powered, 0 - bus powered */ + uint16_t remote_wakeup: 1; /**< 1 - the ability of the device to signal remote wakeup is enabled, 0 - the ability of the device to signal remote wakeup is disabled. */ + uint16_t reserved: 14; /**< reserved */ + } USB_DESC_ATTR; /**< Packed */ + uint16_t val; /**< Device status value */ +} usb_device_status_t; +ESP_STATIC_ASSERT(sizeof(usb_device_status_t) == sizeof(uint16_t), "Size of usb_device_status_t incorrect"); + /** * @brief Bit masks belonging to the bmRequestType field of a setup packet */ @@ -144,6 +159,19 @@ ESP_STATIC_ASSERT(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of #define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 #define USB_W_VALUE_DT_INTERFACE_POWER 0x08 +/** + * @brief Initializer for a GET_STATUS request + * + * Sets the address of a connected device + */ +#define USB_SETUP_PACKET_INIT_GET_STATUS(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_STATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 2; \ +}) + /** * @brief Initializer for a SET_ADDRESS request * diff --git a/components/usb/include/usb/usb_types_stack.h b/components/usb/include/usb/usb_types_stack.h index 8b351063f4..bb2c290666 100644 --- a/components/usb/include/usb/usb_types_stack.h +++ b/components/usb/include/usb/usb_types_stack.h @@ -70,10 +70,19 @@ typedef struct usb_device_handle_s *usb_device_handle_t; */ typedef bool (*usb_host_enum_filter_cb_t)(const usb_device_desc_t *dev_desc, uint8_t *bConfigurationValue); +/** + * @brief Parent device information +*/ +typedef struct { + usb_device_handle_t dev_hdl; /**< Device's parent handle */ + uint8_t port_num; /**< Device's parent port number */ +} usb_parent_dev_info_t; + /** * @brief Basic information of an enumerated device */ typedef struct { + usb_parent_dev_info_t parent; /**< Device's parent information */ usb_speed_t speed; /**< Device's speed */ uint8_t dev_addr; /**< Device's address */ uint8_t bMaxPacketSize0; /**< The maximum packet size of the device's default endpoint */ diff --git a/components/usb/private_include/enum.h b/components/usb/private_include/enum.h new file mode 100644 index 0000000000..0dc44cff69 --- /dev/null +++ b/components/usb/private_include/enum.h @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "hcd.h" +#include "usbh.h" +#include "usb/usb_types_stack.h" +#include "usb/usb_types_ch9.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------------- Settings & Configuration ----------------------------- +#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK +#define ENABLE_ENUM_FILTER_CALLBACK 1 +#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK + +// -------------------------- Public Types ------------------------------------- + +// ---------------------------- Handles ---------------------------------------- + +/** + * @brief Handle of enumeration control object + */ +typedef struct enum_ctx_handle_s * enum_ctx_handle_t; + +// ------------------------------ Events --------------------------------------- + +/** + * @brief Event data object for Enumerator driver events + */ +typedef enum { + ENUM_EVENT_STARTED, /**< Enumeration of a device has started */ + ENUM_EVENT_RESET_REQUIRED, /**< Enumerating device requires a reset */ + ENUM_EVENT_COMPLETED, /**< Enumeration of a device has completed */ + ENUM_EVENT_CANCELED, /**< Enumeration of a device was canceled */ +} enum_event_t; + +typedef struct { + enum_event_t event; /**< Enumerator driver event */ + union { + struct { + unsigned int uid; /**< Device unique ID */ + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } started; /**< ENUM_EVENT_STARTED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } reset_req; /**< ENUM_EVENT_RESET_REQUIRED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + usb_device_handle_t dev_hdl; /**< Handle of the enumerating device */ + uint8_t dev_addr; /**< Address of the enumerating device */ + } complete; /**< ENUM_EVENT_COMPLETED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } canceled; /**< ENUM_EVENT_CANCELED specific data */ + }; +} enum_event_data_t; + +// ---------------------------- Callbacks -------------------------------------- + +/** + * @brief Callback used to indicate that the Enumerator has an event + */ +typedef void (*enum_event_cb_t)(enum_event_data_t *event_data, void *arg); + +/** + * @brief Enum driver configuration + */ +typedef struct { + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ + enum_event_cb_t enum_event_cb; /**< Enum event callback */ + void *enum_event_cb_arg; /**< Enum event callback argument */ +#if ENABLE_ENUM_FILTER_CALLBACK + usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ + void *enum_filter_cb_arg; /**< Set device configuration callback argument */ +#endif // ENABLE_ENUM_FILTER_CALLBACK +} enum_config_t; + +/** + * @brief Install Enumerator driver + * + * Entry: + * - USBH must already be installed + * - HUB must already be installed + * + * @param[in] enum_config Enumeration driver configuration + * @param[out] client_ret Unique pointer to identify Enum Driver as a USB Host client + * @return esp_err_t + */ +esp_err_t enum_install(enum_config_t *enum_config, void **client_ret); + +/** + * @brief Uninstall Enumerator driver + * + * This must be called before uninstalling the HUB and USBH + * + * @return esp_err_t + */ +esp_err_t enum_uninstall(void); + +/** + * @brief Start the enumeration process + * + * This will start the enumeration process for the device currently at address 0 + * + * @param[in] uid Unique device ID + * @retval ESP_OK: Enumeration process started + * @retval ESP_ERR_NOT_FOUND: No device at address 0 + */ +esp_err_t enum_start(unsigned int uid); + +/** + * @brief Continue enumeration process + * + * This will continue the enumeration process. Typically called after the successful + * handling of a request from the Enumerator driver (such as ENUM_EVENT_RESET_REQUIRED) + * + * @param[in] uid Unique device ID + * @return esp_err_t + */ +esp_err_t enum_proceed(unsigned int uid); + +/** + * @brief Cancel the enumeration process + * + * This will cancel enumeration process for device object under enumeration + * + * @return esp_err_t + */ +esp_err_t enum_cancel(unsigned int uid); + +/** + * @brief Enumerator processing function + * + * Processing function that must be called repeatedly to process enumeration stages + * + * @return esp_err_t + */ +esp_err_t enum_process(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h new file mode 100644 index 0000000000..1e1db24be6 --- /dev/null +++ b/components/usb/private_include/ext_hub.h @@ -0,0 +1,249 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "hcd.h" +#include "usbh.h" +#include "usb/usb_types_stack.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" + +#if CONFIG_USB_HOST_HUB_MULTI_LEVEL +#define ENABLE_MULTIPLE_HUBS 1 +#endif // CONFIG_USB_HOST_HUB_MULTI_LEVEL + +#ifdef __cplusplus +extern "C" { +#endif + +// ----------------------------- Handles --------------------------------------- + +typedef struct ext_hub_s *ext_hub_handle_t; + +// ---------------------------- Callbacks -------------------------------------- + +/** + * @brief Callback used to indicate that the External Hub Driver requires process callback + * For Hub Driver only + */ +typedef bool (*ext_hub_cb_t)(bool in_isr, void *user_arg); + +// ------------------------ External Port API typedefs ------------------------- + +/** + * @brief External Hub Port driver + */ +typedef struct { + esp_err_t (*new)(void *port_cfg, void **port_hdl); + esp_err_t (*reset)(void *port_hdl); + esp_err_t (*recycle)(void *port_hdl); + 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 (*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); + esp_err_t (*req_process)(void *port_hdl); +} ext_hub_port_driver_t; + +/** + * @brief External Hub Driver configuration + */ +typedef struct { + ext_hub_cb_t proc_req_cb; /**< External Hub process callback */ + void *proc_req_cb_arg; /**< External Hub process callback argument */ + const ext_hub_port_driver_t* port_driver; /**< External Port Driver */ +} ext_hub_config_t; + +// ------------------------------ Driver --------------------------------------- + +/** + * @brief Install External Hub Driver + * + * Entry: + * - should be called within Hub Driver + * + * @param[in] config External Hub driver configuration + * @return esp_err_t + */ +esp_err_t ext_hub_install(const ext_hub_config_t* config); + +/** + * @brief Uninstall External Hub Driver + * + * Entry: + * - should be called within Hub Driver + * + * @return esp_err_t + */ +esp_err_t ext_hub_uninstall(void); + +/** + * @brief External Hub Driver get client pointer + * + * Entry: + * - should be called within Hub Driver + * + * @param[in] config External Hub driver configuration + * @return Unique pointer to identify the External Hub as a USB Host client + */ +void *ext_hub_get_client(void); + +// -------------------------- External Hub API --------------------------------- + +/** + * @brief Get External Hub device handle by USBH device handle + * + * @param[in] dev_hdl USBH device handle + * @param[out] ext_hub_hdl External Hub device handle + * @return esp_err_t + */ +esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_hub_hdl); + +/** + * @brief Add new device + * + * After attaching new device: + * - configure it's parameters (requesting hub descriptor) + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_new_dev(uint8_t dev_addr); + +/** + * @brief Device gone + * + * After device were detached: + * - prepare the device to be freed + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_dev_gone(uint8_t dev_addr); + +/** + * @brief Marks all devices to be freed + * + * Entry: + * - should be called within Hub Driver when USB Host library need to be uninstalled + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_all_free(void); + +/** + * @brief The External Hub or Ports statuses change completed + * + * Enables Interrupt IN endpoint to get information about Hub or Ports statuses change + * + * @param[in] ext_hub_hdl External Hub device handle + * @return esp_err_t + */ +esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl); + +/** + * @brief External Hub driver's process function + * + * External Hub 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. + */ +esp_err_t ext_hub_process(void); + +// -------------------- External Hub - Port related ---------------------------- + +/** + * @brief Indicate to the External Hub driver that a device's port can be recycled + * + * The device connected to the port has been freed. The Hub driver can now + * recycle the port. + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port need a reset + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port has a device and device has been enumerated + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port should be disabled + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Returns device speed of the device, attached to the port + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @param[out] speed Devices' speed + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, usb_speed_t *speed); + +// --------------------------- USB Chapter 11 ---------------------------------- + +/** + * @brief Set Port Feature request + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @param[in] feature Port Feature to set + * @return esp_err_t + */ +esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); + +/** + * @brief Clear Port Feature request + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @param[in] feature Port Feature to clear + * @return esp_err_t + */ +esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); + +/** + * @brief Get Port Status request + * + * Sends the request to retrieve port status data. + * Actual port status data could be requested by calling ext_hub_get_port_status() after request completion. + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @return esp_err_t + */ +esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index cf85ad861d..65bc98ff46 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -13,10 +13,47 @@ #include "usb_private.h" #include "usbh.h" +#if CONFIG_USB_HOST_HUBS_SUPPORTED +#define ENABLE_USB_HUBS 1 +#endif // CONFIG_USB_HOST_HUBS_SUPPORTED + #ifdef __cplusplus extern "C" { #endif +// ----------------------------- Events ---------------------------------------- +typedef enum { + HUB_EVENT_CONNECTED, /**< Device has been connected */ + HUB_EVENT_RESET_COMPLETED, /**< Device reset completed */ + HUB_EVENT_DISCONNECTED, /**< Device has been disconnected */ +} hub_event_t; + +typedef struct { + hub_event_t event; /**< HUB event ID */ + union { + struct { + unsigned int uid; /**< Unique device ID */ + } connected; /**< HUB_EVENT_DEV_CONNECTED specific data */ + + struct { + unsigned int uid; /**< Unique device ID */ + } reset_completed; /**< HUB_EVENT_RESET_COMPLETED specific data */ + + struct { + unsigned int uid; /**< Unique device ID */ + } disconnected; /**< HUB_EVENT_DEV_DISCONNECTED specific data */ + }; +} hub_event_data_t; + +// ---------------------- Callbacks ------------------------ + +/** + * @brief Callback used to indicate that the USBH has an event + * + * @note This callback is called from within usbh_process() + */ +typedef void (*hub_event_cb_t)(hub_event_data_t *event_data, void *arg); + // ------------------------------------------------------ Types -------------------------------------------------------- /** @@ -25,9 +62,8 @@ extern "C" { typedef struct { usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ void *proc_req_cb_arg; /**< Processing request callback argument */ -#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK - usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ -#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK + hub_event_cb_t event_cb; /**< Hub event callback */ + void *event_cb_arg; /**< Hub event callback argument */ } hub_config_t; // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- @@ -65,6 +101,8 @@ esp_err_t hub_uninstall(void); * * This will power the root port ON * + * @note This function should only be called from the Host Library task + * * @return esp_err_t */ esp_err_t hub_root_start(void); @@ -81,19 +119,92 @@ esp_err_t hub_root_stop(void); /** * @brief Indicate to the Hub driver that a device's port can be recycled * - * The device connected to the port has been freed. The Hub driver can now - * recycled the port. + * The device connected to the port has been freed. The Hub driver can now recycled the port * - * @param dev_uid Device's unique ID + * @note This function should only be called from the Host Library task + * + * @param[in] parent_dev_hdl + * @param[in] parent_port_num + * @param[in] dev_uid Device's unique ID * @return * - ESP_OK: Success */ -esp_err_t hub_port_recycle(unsigned int dev_uid); +esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid); + +/** + * @brief Reset the port + * + * @note This function should only be called from the Host Library task + * + * @param[in] parent_dev_hdl + * @param[in] parent_port_num + * @return + * - ESP_OK: Success + */ +esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); + +/** + * @brief Activate the port + * + * @note This function should only be called from the Host Library task + * + * @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: Success + */ +esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); + +/** + * @brief Disable the port + * + * @note This function should only be called from the Host Library task + * + * @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 + * @retval ESP_OK: Port has been disabled without error + * @retval ESP_ERR_INVALID_STATE: Port can't be disabled in current state + * @retval ESP_ERR_NOT_SUPPORTED: This function is not support by the selected port + */ +esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); + +#if ENABLE_USB_HUBS +/** + * @brief Notify Hub driver that new device has been attached + * + * If device is has a HUB class, then it will be added as External Hub to Hub Driver. + * + * @param[in] dev_addr Device bus address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_new_dev(uint8_t dev_addr); + +/** + * @brief Notify Hub driver that device has been removed + * + * If the device was an External Hub, then it will be removed from the Hub Driver. + * + * @param[in] dev_addr Device bus address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_dev_gone(uint8_t dev_addr); + +/** + * @brief Notify Hub driver that all devices should be freed + * + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_all_free(void); +#endif // ENABLE_USB_HUBS /** * @brief Hub driver's processing function * - * Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the + * Hub driver handling function that must be called repeatedly to process the Hub driver's 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_t diff --git a/components/usb/private_include/usb_private.h b/components/usb/private_include/usb_private.h index 4fb25cc4ee..0a081d4e5c 100644 --- a/components/usb/private_include/usb_private.h +++ b/components/usb/private_include/usb_private.h @@ -58,6 +58,7 @@ typedef struct urb_s urb_t; typedef enum { USB_PROC_REQ_SOURCE_USBH = 0x01, USB_PROC_REQ_SOURCE_HUB = 0x02, + USB_PROC_REQ_SOURCE_ENUM = 0x03 } usb_proc_req_source_t; /** diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index a1b5b2a845..c4097ec107 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -59,6 +59,8 @@ typedef struct { } dev_gone_data; struct { unsigned int dev_uid; + usb_device_handle_t parent_dev_hdl; + uint8_t port_num; } dev_free_data; }; } usbh_event_data_t; @@ -130,7 +132,18 @@ typedef struct { void *event_cb_arg; /**< USBH event callback argument */ } usbh_config_t; -// -------------------------------------------- USBH Processing Functions ---------------------------------------------- +/** + * @brief USBH device parameters used in usbh_devs_add() +*/ +typedef struct { + unsigned int uid; /**< Unique ID assigned to the device */ + usb_speed_t speed; /**< Device's speed */ + hcd_port_handle_t root_port_hdl; /**< Handle of the port that the device is connected to */ + usb_device_handle_t parent_dev_hdl; /**< Parent's device handle */ + uint8_t parent_port_num; /**< Parent's port number */ +} usbh_dev_params_t; + +// ---------------------- USBH Processing Functions ---------------------------- /** * @brief Installs the USBH driver @@ -169,7 +182,7 @@ esp_err_t usbh_uninstall(void); */ esp_err_t usbh_process(void); -// ---------------------------------------------- Device Pool Functions ------------------------------------------------ +// ---------------------- Device Pool Functions -------------------------------- /** * @brief Get the current number of devices @@ -206,12 +219,10 @@ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *nu * - Call usbh_dev_enum_lock() before enumerating the device via the various * usbh_dev_set_...() functions. * - * @param[in] uid Unique ID assigned to the device - * @param[in] dev_speed Device's speed - * @param[in] port_hdl Handle of the port that the device is connected to + * @param[in] params Device parameters, using for device creation * @return esp_err_t */ -esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl); +esp_err_t usbh_devs_add(usbh_dev_params_t *params); /** * @brief Indicates to the USBH that a device is gone @@ -221,6 +232,18 @@ esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle */ esp_err_t usbh_devs_remove(unsigned int uid); +/** + * @brief Get a device's connection information + * + * @note Can be called without opening the device + * + * @param[in] uid Unique ID assigned to the device + * @param[out] parent_info Parent device handle + * @param[out] port_num Parent port number + * @return esp_err_t + */ +esp_err_t usbh_devs_get_parent_info(unsigned int uid, usb_parent_dev_info_t *parent_info); + /** * @brief Mark that all devices should be freed at the next possible opportunity * @@ -243,16 +266,6 @@ esp_err_t usbh_devs_mark_all_free(void); */ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl); -/** - * @brief CLose a device - * - * Device can be opened by calling usbh_devs_open() - * - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl); - /** * @brief Trigger a USBH_EVENT_NEW_DEV event for the device * @@ -263,14 +276,23 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl); */ esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl); -// ------------------------------------------------ Device Functions --------------------------------------------------- +// ------------------------ Device Functions ----------------------------------- -// ----------------------- Getters ------------------------- +/** + * @brief Close a device + * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * + * @param[in] dev_hdl Device handle + * @return esp_err_t + */ +esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl); +// ------------------------------ Getters -------------------------------------- /** * @brief Get a device's address * - * @note Can be called without opening the device + * @note Callers of this function must have opened the device via usbh_devs_open() * * @param[in] dev_hdl Device handle * @param[out] dev_addr Device's address @@ -281,8 +303,10 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr); /** * @brief Get a device's information * + * @note Callers of this function must have opened the device via usbh_devs_open() * @note It is possible that the device has not been enumerated yet, thus some * fields may be NULL. + * * @param[in] dev_hdl Device handle * @param[out] dev_info Device information * @return esp_err_t @@ -292,7 +316,7 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ /** * @brief Get a device's device descriptor * - * - The device descriptor is cached when the device is created by the Hub driver + * The device descriptor is cached when the device is created by the Hub driver * * @note It is possible that the device has not been enumerated yet, thus the * device descriptor could be NULL. @@ -305,6 +329,7 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t /** * @brief Get a device's active configuration descriptor * + * @note Callers of this function must have opened the device via usbh_devs_open() * Simply returns a reference to the internally cached configuration descriptor * * @note It is possible that the device has not been enumerated yet, thus the @@ -315,11 +340,13 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t */ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret); -// ----------------------- Setters ------------------------- +// ------------------------------- Setters ------------------------------------- /** * @brief Lock a device for enumeration * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * - A device's enumeration lock must be set before any of its enumeration fields * (e.g., address, device/config descriptors) can be set/updated. * - The caller must be the sole opener of the device (see 'usbh_devs_open()') @@ -333,6 +360,8 @@ esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl); /** * @brief Release a device's enumeration lock * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @param[in] dev_hdl Device handle * @return esp_err_t */ @@ -344,8 +373,11 @@ esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl); * Typically called during enumeration after obtaining the first 8 bytes of the * device's descriptor. * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @note The device's enumeration lock must be set before calling this function * (see 'usbh_dev_enum_lock()') + * * @param[in] dev_hdl Device handle * @param[in] wMaxPacketSize Maximum packet size * @return esp_err_t @@ -355,13 +387,15 @@ esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketS /** * @brief Set a device's address * - * Typically called during enumeration after a SET_ADDRESSS request has be + * Typically called during enumeration after a SET_ADDRESS request has been * sent to the device. * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @note The device's enumeration lock must be set before calling this function * (see 'usbh_dev_enum_lock()') * @param[in] dev_hdl Device handle - * @param[in] dev_addr + * @param[in] dev_addr Device address to set * @return esp_err_t */ esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); @@ -372,8 +406,11 @@ esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); * Typically called during enumeration after obtaining the device's descriptor * via a GET_DESCRIPTOR request. * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @note The device's enumeration lock must be set before calling this function * (see 'usbh_dev_enum_lock()') + * * @param[in] dev_hdl Device handle * @param[in] device_desc Device descriptor to copy * @return esp_err_t @@ -386,8 +423,11 @@ esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t * Typically called during enumeration after obtaining the device's configuration * descriptor via a GET_DESCRIPTOR request. * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @note The device's enumeration lock must be set before calling this function * (see 'usbh_dev_enum_lock()') + * * @param[in] dev_hdl Device handle * @param[in] config_desc_full Configuration descriptor to copy * @return esp_err_t @@ -400,8 +440,11 @@ esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config * Typically called during enumeration after obtaining one of the device's string * descriptor via a GET_DESCRIPTOR request. * + * @note Callers of this function must have opened the device via usbh_devs_open() + * * @note The device's enumeration lock must be set before calling this function * (see 'usbh_dev_enum_lock()') + * * @param[in] dev_hdl Device handle * @param[in] str_desc String descriptor to copy * @param[in] select Select string descriptor. 0/1/2 for Manufacturer/Product/Serial @@ -410,7 +453,7 @@ esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config */ esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); -// ----------------------------------------------- Endpoint Functions ------------------------------------------------- +// ----------------------- Endpoint Functions ---------------------------------- /** * @brief Allocate an endpoint on a device @@ -482,7 +525,7 @@ esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command); */ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl); -// ----------------------------------------------- Transfer Functions -------------------------------------------------- +// ------------------------- Transfer Functions -------------------------------- /** * @brief Submit a control transfer (URB) to a device diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 7094839db6..9d41be06af 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -20,6 +20,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to #include "esp_log.h" #include "esp_heap_caps.h" #include "hub.h" +#include "enum.h" #include "usbh.h" #include "hcd.h" #include "esp_private/usb_phy.h" @@ -46,12 +47,9 @@ static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED; } \ }) -#define PROCESS_REQUEST_PENDING_FLAG_USBH 0x01 -#define PROCESS_REQUEST_PENDING_FLAG_HUB 0x02 - -#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK -#define ENABLE_ENUM_FILTER_CALLBACK -#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK +#define PROCESS_REQUEST_PENDING_FLAG_USBH (1 << 0) +#define PROCESS_REQUEST_PENDING_FLAG_HUB (1 << 1) +#define PROCESS_REQUEST_PENDING_FLAG_ENUM (1 << 2) #define SHORT_DESC_REQ_LEN 8 #define CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE @@ -112,7 +110,7 @@ struct client_s { uint32_t val; } flags; uint32_t num_done_ctrl_xfer; - uint32_t opened_dev_addr_map; + uint32_t opened_dev_addr_map[4]; } dynamic; // Mux protected members must be protected by host library the mux_lock when accessed struct { @@ -152,7 +150,8 @@ typedef struct { SemaphoreHandle_t event_sem; SemaphoreHandle_t mux_lock; usb_phy_handle_t phy_handle; // Will be NULL if host library is installed with skip_phy_setup - void *hub_client; // Pointer to Hub driver (acting as a client). Used to reroute completed USBH control transfers + void *enum_client; // Pointer to Enum driver (acting as a client). Used to reroute completed USBH control transfers + void *hub_client; // Pointer to External Hub driver (acting as a client). Used to reroute completed USBH control transfers. NULL, when External Hub Driver not available. } constant; } host_lib_t; @@ -164,26 +163,25 @@ const char *USB_HOST_TAG = "USB HOST"; static inline void _record_client_opened_device(client_t *client_obj, uint8_t dev_addr) { - assert(dev_addr != 0); - client_obj->dynamic.opened_dev_addr_map |= (1 << (dev_addr - 1)); + assert(dev_addr != 0 && dev_addr <= 127); + client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] |= (uint32_t)(1 << (dev_addr % 32)); } static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev_addr) { - assert(dev_addr != 0); - client_obj->dynamic.opened_dev_addr_map &= ~(1 << (dev_addr - 1)); + assert(dev_addr != 0 && dev_addr <= 127); + client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] &= ~(uint32_t)(1 << (dev_addr % 32)); } static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr) { bool ret; - + assert(dev_addr <= 127); if (dev_addr != 0) { - ret = client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1)); + ret = client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] & (uint32_t)(1 << (dev_addr % 32)); } else { ret = false; } - return ret; } @@ -223,6 +221,19 @@ static bool _unblock_lib(bool in_isr) return yield; } +static inline bool _is_internal_client(void *client) +{ + if (p_host_lib_obj->constant.enum_client && (client == p_host_lib_obj->constant.enum_client)) { + return true; + } +#if ENABLE_USB_HUBS + if (p_host_lib_obj->constant.hub_client && (client == p_host_lib_obj->constant.hub_client)) { + return true; + } +#endif // ENABLE_USB_HUBS + return false; +} + static void send_event_msg_to_clients(const usb_host_client_event_msg_t *event_msg, bool send_to_all, uint8_t opened_dev_addr) { // Lock client list @@ -267,6 +278,9 @@ static bool proc_req_callback(usb_proc_req_source_t source, bool in_isr, void *a case USB_PROC_REQ_SOURCE_HUB: p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_HUB; break; + case USB_PROC_REQ_SOURCE_ENUM: + p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_ENUM; + break; } bool yield = _unblock_lib(in_isr); HOST_EXIT_CRITICAL_SAFE(); @@ -281,8 +295,8 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) assert(event_data->ctrl_xfer_data.urb != NULL); assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL); // Redistribute completed control transfers to the clients that submitted them - if (event_data->ctrl_xfer_data.urb->usb_host_client == p_host_lib_obj->constant.hub_client) { - // Redistribute to Hub driver. Simply call the transfer callback + if (_is_internal_client(event_data->ctrl_xfer_data.urb->usb_host_client)) { + // Simply call the transfer callback event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer); } else { client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; @@ -301,9 +315,15 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) .new_dev.address = event_data->new_dev_data.dev_addr, }; send_event_msg_to_clients(&event_msg, true, 0); +#if ENABLE_USB_HUBS + hub_notify_new_dev(event_data->new_dev_data.dev_addr); +#endif // ENABLE_USB_HUBS break; } case USBH_EVENT_DEV_GONE: { +#if ENABLE_USB_HUBS + hub_notify_dev_gone(event_data->new_dev_data.dev_addr); +#endif // ENABLE_USB_HUBS // Prepare event msg, send only to clients that have opened the device usb_host_client_event_msg_t event_msg = { .event = USB_HOST_CLIENT_EVENT_DEV_GONE, @@ -314,7 +334,10 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) } case USBH_EVENT_DEV_FREE: { // Let the Hub driver know that the device is free and its port can be recycled - ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.dev_uid)); + // Port could be absent, no need to verify + hub_port_recycle(event_data->dev_free_data.parent_dev_hdl, + event_data->dev_free_data.port_num, + event_data->dev_free_data.dev_uid); break; } case USBH_EVENT_ALL_FREE: { @@ -331,6 +354,55 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) } } +static void hub_event_callback(hub_event_data_t *event_data, void *arg) +{ + switch (event_data->event) { + case HUB_EVENT_CONNECTED: + // Start enumeration process + enum_start(event_data->connected.uid); + break; + case HUB_EVENT_RESET_COMPLETED: + ESP_ERROR_CHECK(enum_proceed(event_data->reset_completed.uid)); + break; + case HUB_EVENT_DISCONNECTED: + // Cancel enumeration process + enum_cancel(event_data->disconnected.uid); + // We allow this to fail in case the device object was already freed + usbh_devs_remove(event_data->disconnected.uid); + break; + default: + abort(); // Should never occur + break; + } +} + +static void enum_event_callback(enum_event_data_t *event_data, void *arg) +{ + enum_event_t event = event_data->event; + + switch (event) { + case ENUM_EVENT_STARTED: + // Enumeration process started + break; + case ENUM_EVENT_RESET_REQUIRED: + // Device may be gone, don't need to verify result + hub_port_reset(event_data->reset_req.parent_dev_hdl, event_data->reset_req.parent_port_num); + break; + case ENUM_EVENT_COMPLETED: + // Notify port that device completed enumeration + hub_port_active(event_data->complete.parent_dev_hdl, event_data->complete.parent_port_num); + // Propagate a new device event + ESP_ERROR_CHECK(usbh_devs_new_dev_event(event_data->complete.dev_hdl)); + break; + case ENUM_EVENT_CANCELED: + hub_port_disable(event_data->canceled.parent_dev_hdl, event_data->canceled.parent_port_num); + break; + default: + abort(); // Should never occur + break; + } +} + // ------------------- Client Related ---------------------- static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) @@ -388,6 +460,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config) - USB PHY - HCD - USBH + - Enum - Hub */ @@ -429,18 +502,28 @@ esp_err_t usb_host_install(const usb_host_config_t *config) goto usbh_err; } -#ifdef ENABLE_ENUM_FILTER_CALLBACK - if (config->enum_filter_cb == NULL) { - ESP_LOGW(USB_HOST_TAG, "User callback to set USB device configuration is enabled, but not used"); - } + // Install Enumeration driver + enum_config_t enum_config = { + .proc_req_cb = proc_req_callback, + .proc_req_cb_arg = NULL, + .enum_event_cb = enum_event_callback, + .enum_event_cb_arg = NULL, +#if ENABLE_ENUM_FILTER_CALLBACK + .enum_filter_cb = config->enum_filter_cb, + .enum_filter_cb_arg = NULL, #endif // ENABLE_ENUM_FILTER_CALLBACK + }; + ret = enum_install(&enum_config, &host_lib_obj->constant.enum_client); + if (ret != ESP_OK) { + goto enum_err; + } + // Install Hub hub_config_t hub_config = { .proc_req_cb = proc_req_callback, .proc_req_cb_arg = NULL, -#ifdef ENABLE_ENUM_FILTER_CALLBACK - .enum_filter_cb = config->enum_filter_cb, -#endif // ENABLE_ENUM_FILTER_CALLBACK + .event_cb = hub_event_callback, + .event_cb_arg = NULL, }; ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client); if (ret != ESP_OK) { @@ -465,6 +548,8 @@ esp_err_t usb_host_install(const usb_host_config_t *config) assign_err: ESP_ERROR_CHECK(hub_uninstall()); hub_err: + ESP_ERROR_CHECK(enum_uninstall()); +enum_err: ESP_ERROR_CHECK(usbh_uninstall()); usbh_err: ESP_ERROR_CHECK(hcd_uninstall()); @@ -507,11 +592,13 @@ esp_err_t usb_host_uninstall(void) /* Uninstall each layer of the Host stack (listed below) from the highest layer to the lowest - Hub + - Enum - USBH - HCD - USB PHY */ ESP_ERROR_CHECK(hub_uninstall()); + ESP_ERROR_CHECK(enum_uninstall()); ESP_ERROR_CHECK(usbh_uninstall()); ESP_ERROR_CHECK(hcd_uninstall()); // If the USB PHY was setup, then delete it @@ -558,6 +645,9 @@ esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_f if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_HUB) { ESP_ERROR_CHECK(hub_process()); } + if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_ENUM) { + ESP_ERROR_CHECK(enum_process()); + } ret = ESP_OK; // Set timeout_ticks to 0 so that we can check for events again without blocking @@ -729,7 +819,10 @@ esp_err_t usb_host_client_deregister(usb_host_client_handle_t client_hdl) client_obj->dynamic.flags.taking_mux || client_obj->dynamic.flags.num_intf_claimed != 0 || client_obj->dynamic.num_done_ctrl_xfer != 0 || - client_obj->dynamic.opened_dev_addr_map != 0) { + client_obj->dynamic.opened_dev_addr_map[0] != 0 || + client_obj->dynamic.opened_dev_addr_map[1] != 0 || + client_obj->dynamic.opened_dev_addr_map[2] != 0 || + client_obj->dynamic.opened_dev_addr_map[3] != 0) { can_deregister = false; } else { can_deregister = true; @@ -863,7 +956,7 @@ esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_ return ret; already_opened: - ESP_ERROR_CHECK(usbh_devs_close(dev_hdl)); + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); exit: return ret; } @@ -905,7 +998,7 @@ esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_ _clear_client_opened_device(client_obj, dev_addr); HOST_EXIT_CRITICAL(); - ESP_ERROR_CHECK(usbh_devs_close(dev_hdl)); + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); ret = ESP_OK; exit: xSemaphoreGive(p_host_lib_obj->constant.mux_lock); @@ -918,6 +1011,9 @@ esp_err_t usb_host_device_free_all(void) HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.flags.num_clients == 0, ESP_ERR_INVALID_STATE); // All clients must have been deregistered HOST_EXIT_CRITICAL(); esp_err_t ret; +#if ENABLE_USB_HUBS + hub_notify_all_free(); +#endif // ENABLE_USB_HUBS ret = usbh_devs_mark_all_free(); // If ESP_ERR_NOT_FINISHED is returned, caller must wait for USB_HOST_LIB_EVENT_FLAGS_ALL_FREE to confirm all devices are free return ret; diff --git a/components/usb/usbh.c b/components/usb/usbh.c index b402e72a99..5d60bc543f 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -40,78 +40,79 @@ typedef struct device_s device_t; typedef struct { struct { - usbh_ep_cb_t ep_cb; - void *ep_cb_arg; - hcd_pipe_handle_t pipe_hdl; - device_t *dev; // Pointer to the device object that this endpoint is contained in - const usb_ep_desc_t *ep_desc; // This just stores a pointer endpoint descriptor inside the device's "config_desc" - } constant; + usbh_ep_cb_t ep_cb; /**< Endpoint callback is called when transfer in complete or error occurred */ + void *ep_cb_arg; /**< Endpoint callback argument */ + hcd_pipe_handle_t pipe_hdl; /**< Endpoint HCD pipe handle */ + device_t *dev; /**< Pointer to the device object that this endpoint is contained in */ + const usb_ep_desc_t *ep_desc; /**< This just stores a pointer endpoint descriptor inside the device's "config_desc" */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ } endpoint_t; struct device_s { - // Dynamic members require a critical section struct { - TAILQ_ENTRY(device_s) tailq_entry; + TAILQ_ENTRY(device_s) tailq_entry; /**< Entry for the device object tailq */ union { struct { - uint32_t in_pending_list: 1; - uint32_t is_gone: 1; // Device is gone (disconnected or port error) - uint32_t waiting_free: 1; // Device object is awaiting to be freed - uint32_t enum_lock: 1; // Device is locked for enumeration. Enum information (e.g., address, device/config desc etc) may change - uint32_t reserved28: 28; + uint32_t in_pending_list: 1; /**< Device is in pending list */ + uint32_t is_gone: 1; /**< Device is gone (disconnected or port error) */ + uint32_t waiting_free: 1; /**< Device object is awaiting to be freed */ + uint32_t enum_lock: 1; /**< Device is locked for enumeration. Enum information (e.g., address, device/config desc etc) may change */ + uint32_t reserved28: 28; /**< Reserved */ }; - uint32_t val; + uint32_t val; /**< Device flags value */ } flags; - uint32_t action_flags; - int num_ctrl_xfers_inflight; - usb_device_state_t state; - uint32_t open_count; - } dynamic; - // Mux protected members must be protected by the USBH mux_lock when accessed + uint32_t action_flags; /**< Device action flags */ + int num_ctrl_xfers_inflight; /**< Amount of ongoing Control transfers */ + usb_device_state_t state; /**< Device state */ + uint32_t open_count; /**< Amount of clients which opened this device */ + } dynamic; /**< Dynamic members. Require a critical section */ + struct { /* - Endpoint object pointers for each possible non-default endpoint - All OUT EPs are listed before IN EPs (i.e., EP_NUM_MIN OUT ... EP_NUM_MAX OUT ... EP_NUM_MIN IN ... EP_NUM_MAX) */ endpoint_t *endpoints[NUM_NON_DEFAULT_EP]; - } mux_protected; + } mux_protected; /**< Mutex protected members. Must be protected by the USBH mux_lock when accessed */ + // Constant members do not require a critical section struct { // Assigned on device allocation and remain constant for the device's lifetime - hcd_pipe_handle_t default_pipe; - hcd_port_handle_t port_hdl; - usb_speed_t speed; - unsigned int uid; + hcd_pipe_handle_t default_pipe; /**< Pipe handle for Control EP0 */ + hcd_port_handle_t port_hdl; /**< HCD port handle */ + usb_device_handle_t parent_dev_hdl; /**< Device's parent device handle. NULL if device is connected to the root port */ + uint8_t parent_port_num; /**< Device's parent port number. 0 if device connected to the root port */ + usb_speed_t speed; /**< Device's speed */ + unsigned int uid; /**< Device's Unique ID */ /* These fields are can only be changed when enum_lock is set, thus can be treated as constant */ - uint8_t address; - usb_device_desc_t *desc; - usb_config_desc_t *config_desc; - usb_str_desc_t *str_desc_manu; - usb_str_desc_t *str_desc_product; - usb_str_desc_t *str_desc_ser_num; - } constant; + uint8_t address; /**< Device's bus address */ + usb_device_desc_t *desc; /**< Device's descriptor pointer */ + usb_config_desc_t *config_desc; /**< Device's configuration descriptor pointer. NULL if not configured. */ + usb_str_desc_t *str_desc_manu; /**< Device's Manufacturer string descriptor pointer */ + usb_str_desc_t *str_desc_product; /**< Device's Product string descriptor pointer */ + usb_str_desc_t *str_desc_ser_num; /**< Device's Serial string descriptor pointer */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ }; typedef struct { - // Dynamic members require a critical section struct { - TAILQ_HEAD(tailhead_devs, device_s) devs_idle_tailq; // Tailq of all enum and configured devices - TAILQ_HEAD(tailhead_devs_cb, device_s) devs_pending_tailq; // Tailq of devices that need to have their cb called - } dynamic; - // Mux protected members must be protected by the USBH mux_lock when accessed + TAILQ_HEAD(tailhead_devs, device_s) devs_idle_tailq; /**< Tailq of all enum and configured devices */ + TAILQ_HEAD(tailhead_devs_cb, device_s) devs_pending_tailq; /**< Tailq of devices that need to have their cb called */ + } dynamic; /**< Dynamic members. Require a critical section */ + struct { - uint8_t num_device; // Number of enumerated devices - } mux_protected; - // Constant members do no change after installation thus do not require a critical section + uint8_t num_device; /**< Current number of device objects */ + } mux_protected; /**< Mutex protected members. Must be protected by the USBH mux_lock when accessed */ + struct { - usb_proc_req_cb_t proc_req_cb; - void *proc_req_cb_arg; - usbh_event_cb_t event_cb; - void *event_cb_arg; - SemaphoreHandle_t mux_lock; - } constant; + usb_proc_req_cb_t proc_req_cb; /**< USB Host process request callback. Refer to proc_req_callback() in usb_host.c */ + void *proc_req_cb_arg; /**< USB Host process request callback argument */ + usbh_event_cb_t event_cb; /**< USBH event callback */ + void *event_cb_arg; /**< USBH event callback argument */ + 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 */ } usbh_t; static usbh_t *p_usbh_obj = NULL; @@ -147,7 +148,9 @@ static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags); -// ----------------------------------------------------- Helpers ------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ---------------------------- Helpers ---------------------------------------- +// ----------------------------------------------------------------------------- static device_t *_find_dev_from_uid(unsigned int uid) { @@ -305,7 +308,9 @@ static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer return true; } -// --------------------------------------------------- Allocation ------------------------------------------------------ +// ----------------------------------------------------------------------------- +// ----------------------------- Allocation ------------------------------------ +// ----------------------------------------------------------------------------- static esp_err_t endpoint_alloc(device_t *dev_obj, const usb_ep_desc_t *ep_desc, usbh_ep_config_t *ep_config, endpoint_t **ep_obj_ret) { @@ -358,10 +363,7 @@ static void endpoint_free(endpoint_t *ep_obj) heap_caps_free(ep_obj); } -static esp_err_t device_alloc(unsigned int uid, - usb_speed_t speed, - hcd_port_handle_t port_hdl, - device_t **dev_obj_ret) +static esp_err_t device_alloc(usbh_dev_params_t *params, device_t **dev_obj_ret) { device_t *dev_obj = heap_caps_calloc(1, sizeof(device_t), MALLOC_CAP_DEFAULT); if (dev_obj == NULL) { @@ -375,22 +377,23 @@ static esp_err_t device_alloc(unsigned int uid, .callback_arg = (void *)dev_obj, .context = (void *)dev_obj, .ep_desc = NULL, // No endpoint descriptor means we're allocating a pipe for EP0 - .dev_speed = speed, + .dev_speed = params->speed, .dev_addr = 0, }; hcd_pipe_handle_t default_pipe_hdl; - ret = hcd_pipe_alloc(port_hdl, &pipe_config, &default_pipe_hdl); + ret = hcd_pipe_alloc(params->root_port_hdl, &pipe_config, &default_pipe_hdl); if (ret != ESP_OK) { goto err; } // Initialize device object dev_obj->dynamic.state = USB_DEVICE_STATE_DEFAULT; dev_obj->constant.default_pipe = default_pipe_hdl; - dev_obj->constant.port_hdl = port_hdl; - dev_obj->constant.speed = speed; - dev_obj->constant.uid = uid; + dev_obj->constant.port_hdl = params->root_port_hdl; + dev_obj->constant.parent_dev_hdl = params->parent_dev_hdl; + dev_obj->constant.parent_port_num = params->parent_port_num; + dev_obj->constant.speed = params->speed; + dev_obj->constant.uid = params->uid; // Note: Enumeration related dev_obj->constant fields are initialized later using usbh_dev_set_...() functions - // Write-back device object *dev_obj_ret = dev_obj; ret = ESP_OK; @@ -407,11 +410,11 @@ static void device_free(device_t *dev_obj) if (dev_obj == NULL) { return; } - // Device descriptor might not have been set yet + // Device descriptor might not have been allocated (in case of early enumeration failure) if (dev_obj->constant.desc) { heap_caps_free(dev_obj->constant.desc); } - // Configuration descriptor might not have been set yet + // Configuration might not have been allocated (in case of early enumeration failure) if (dev_obj->constant.config_desc) { heap_caps_free(dev_obj->constant.config_desc); } @@ -429,7 +432,9 @@ static void device_free(device_t *dev_obj) heap_caps_free(dev_obj); } -// ---------------------------------------------------- Callbacks ------------------------------------------------------ +// ----------------------------------------------------------------------------- +// -------------------------- Callbacks ---------------------------------------- +// ----------------------------------------------------------------------------- static bool ep0_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) { @@ -487,7 +492,9 @@ static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_ in_isr); } -// -------------------------------------------------- Event Related ---------------------------------------------------- +// ----------------------------------------------------------------------------- +// ------------------------- Event Related ------------------------------------- +// ----------------------------------------------------------------------------- static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) { @@ -581,6 +588,8 @@ static inline void handle_free(device_t *dev_obj) { // Cache a copy of the device's address as we are about to free the device object const unsigned int dev_uid = dev_obj->constant.uid; + usb_device_handle_t parent_dev_hdl = dev_obj->constant.parent_dev_hdl; + const uint8_t parent_port_num = dev_obj->constant.parent_port_num; bool all_free; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); @@ -605,6 +614,8 @@ static inline void handle_free(device_t *dev_obj) .event = USBH_EVENT_DEV_FREE, .dev_free_data = { .dev_uid = dev_uid, + .parent_dev_hdl = parent_dev_hdl, + .port_num = parent_port_num, } }; p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); @@ -629,7 +640,9 @@ static inline void handle_prop_new_dev(device_t *dev_obj) p_usbh_obj->constant.event_cb(&event_data, p_usbh_obj->constant.event_cb_arg); } -// -------------------------------------------- USBH Processing Functions ---------------------------------------------- +// ----------------------------------------------------------------------------- +// ------------------------- USBH Processing Functions ------------------------- +// ----------------------------------------------------------------------------- esp_err_t usbh_install(const usbh_config_t *usbh_config) { @@ -766,7 +779,9 @@ esp_err_t usbh_process(void) return ESP_OK; } -// ---------------------------------------------- Device Pool Functions ------------------------------------------------ +// ----------------------------------------------------------------------------- +// ------------------------- Device Pool Functions ----------------------------- +// ----------------------------------------------------------------------------- esp_err_t usbh_devs_num(int *num_devs_ret) { @@ -825,14 +840,15 @@ esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *nu return ESP_OK; } -esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl) +esp_err_t usbh_devs_add(usbh_dev_params_t *params) { - USBH_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); + USBH_CHECK(params != NULL, ESP_ERR_NOT_ALLOWED); + USBH_CHECK(params->root_port_hdl != NULL, ESP_ERR_INVALID_ARG); esp_err_t ret; device_t *dev_obj; // Allocate a device object (initialized to address 0) - ret = device_alloc(uid, dev_speed, port_hdl, &dev_obj); + ret = device_alloc(params, &dev_obj); if (ret != ESP_OK) { return ret; } @@ -842,7 +858,7 @@ esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle USBH_ENTER_CRITICAL(); // Check that there is not already a device with the same uid - if (_find_dev_from_uid(uid) != NULL) { + if (_find_dev_from_uid(params->uid) != NULL) { ret = ESP_ERR_INVALID_ARG; goto exit; } @@ -860,6 +876,11 @@ exit: USBH_EXIT_CRITICAL(); xSemaphoreGive(p_usbh_obj->constant.mux_lock); + if (ret != ESP_OK) { + // Free dev_obj for memory not to leak + device_free(dev_obj); + } + return ret; } @@ -901,6 +922,27 @@ exit: return ret; } +esp_err_t usbh_devs_get_parent_info(unsigned int uid, usb_parent_dev_info_t *parent_info) +{ + USBH_CHECK(parent_info, ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_FAIL; + device_t *dev_obj = NULL; + + USBH_ENTER_CRITICAL(); + dev_obj = _find_dev_from_uid(uid); + if (dev_obj == NULL) { + ret = ESP_ERR_NOT_FOUND; + goto exit; + } else { + parent_info->dev_hdl = dev_obj->constant.parent_dev_hdl; + parent_info->port_num = dev_obj->constant.parent_port_num; + ret = ESP_OK; + } +exit: + USBH_EXIT_CRITICAL(); + return ret; +} + esp_err_t usbh_devs_mark_all_free(void) { USBH_ENTER_CRITICAL(); @@ -971,7 +1013,30 @@ esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl) return ret; } -esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) +esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl) +{ + device_t *dev_obj = (device_t *)dev_hdl; + bool call_proc_req_cb = false; + + USBH_ENTER_CRITICAL(); + // Device must be in the configured state + USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); + USBH_EXIT_CRITICAL(); + + // Call the processing request callback + if (call_proc_req_cb) { + p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// ---------------------------- Device Functions ------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; @@ -1000,28 +1065,9 @@ esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl) return ESP_OK; } -esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl) -{ - device_t *dev_obj = (device_t *)dev_hdl; - bool call_proc_req_cb = false; - - USBH_ENTER_CRITICAL(); - // Device must be in the configured state - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW_DEV); - USBH_EXIT_CRITICAL(); - - // Call the processing request callback - if (call_proc_req_cb) { - p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg); - } - - return ESP_OK; -} - -// ------------------------------------------------ Device Functions --------------------------------------------------- - -// ----------------------- Getters ------------------------- +// ----------------------------------------------------------------------------- +// ---------------------------- Getters ---------------------------------------- +// ----------------------------------------------------------------------------- esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr) { @@ -1029,7 +1075,6 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr) device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->constant.address > 0, ESP_ERR_INVALID_STATE); *dev_addr = dev_obj->constant.address; USBH_EXIT_CRITICAL(); @@ -1041,6 +1086,8 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ USBH_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; + dev_info->parent.dev_hdl = dev_obj->constant.parent_dev_hdl; + dev_info->parent.port_num = dev_obj->constant.parent_port_num; dev_info->speed = dev_obj->constant.speed; dev_info->dev_addr = dev_obj->constant.address; // Device descriptor might not have been set yet @@ -1067,7 +1114,6 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t { USBH_CHECK(dev_hdl != NULL && dev_desc_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - *dev_desc_ret = dev_obj->constant.desc; return ESP_OK; } @@ -1082,7 +1128,9 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config return ESP_OK; } -// ----------------------- Setters ------------------------- +// ----------------------------------------------------------------------------- +// -------------------------------- Setters ------------------------------------ +// ----------------------------------------------------------------------------- esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl) { @@ -1330,7 +1378,9 @@ err: return ret; } -// ----------------------------------------------- Endpoint Functions ------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------- Endpoint Functions ---------------------------- +// ----------------------------------------------------------------------------- esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, usbh_ep_handle_t *ep_hdl_ret) { @@ -1460,7 +1510,9 @@ void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl) return hcd_pipe_get_context(ep_obj->constant.pipe_hdl); } -// ----------------------------------------------- Transfer Functions -------------------------------------------------- +// ----------------------------------------------------------------------------- +// ------------------------ Transfer Functions --------------------------------- +// ----------------------------------------------------------------------------- esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb) { diff --git a/docs/_static/usb_host/stack-overview.png b/docs/_static/usb_host/stack-overview.png index 0558d8388b..40996041de 100644 Binary files a/docs/_static/usb_host/stack-overview.png and b/docs/_static/usb_host/stack-overview.png differ diff --git a/docs/conf_common.py b/docs/conf_common.py index 599c5846fc..3ca34ecec8 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -121,7 +121,9 @@ USB_DOCS = ['api-reference/peripherals/usb_device.rst', 'api-reference/peripherals/usb_host/usb_host_notes_design.rst', 'api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst', 'api-reference/peripherals/usb_host/usb_host_notes_index.rst', - 'api-reference/peripherals/usb_host/usb_host_notes_usbh.rst'] + 'api-reference/peripherals/usb_host/usb_host_notes_usbh.rst', + 'api-reference/peripherals/usb_host/usb_host_notes_enum.rst', + 'api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst'] I80_LCD_DOCS = ['api-reference/peripherals/lcd/i80_lcd.rst'] RGB_LCD_DOCS = ['api-reference/peripherals/lcd/rgb_lcd.rst'] diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_enum.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_enum.rst new file mode 100644 index 0000000000..261cf16bbc --- /dev/null +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_enum.rst @@ -0,0 +1,119 @@ +USB Host Enumeration Driver (Enum) +================================== + +Introduction +------------ + +The USB Host Enumeration Driver (henceforth referred to as Enum Driver) provides a software interface which abstracts away the USB device enumeration process. The Enum Driver provides a simple API to start, proceed, complete and cancel the enumeration of a particular device. Internally, the Enum Driver will handle all stages of the enumeration process such as requesting various descriptors and setting the device's configuration. + +Requirements +------------ + +USB Specification Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Chapter 9.1.2 of the USB 2.0 specification outlines some actions, when a USB device is attached to a powered port. + +The design of the Enum Driver takes into consideration following actions: + +- **The hub performs the required reset processing for the port.** ``enum_config_t.enum_event_cb`` call with ``ENUM_EVENT_RESET_REQUIRED`` event used for Hub Driver notification. +- **The host assigns a unique address to the USB device.** The Enum Driver keeps the device address value and assigns a unique address to the USB device, moving the device to the Address state. +- **The host reads the device descriptor to determine what actual maximum data payload size this USB device's default pipe can use**. The Enum Driver reads the device descriptor to determine what actual maximum data payload size this USB device's default pipe can use. +- **The host reads the configuration information from the device by reading each configuration zero to n-1, where n is the number of configurations.** This requirement simplified to reading only one configuration information from the device by reading configuration with default number, which could be selected by ``enum_config_t.enum_filter_cb`` call. +- **The host assigns a configuration value to the device.** The Enum Driver assigns a configuration value to the device. + +.. note:: + + Typically, most USB devices only contain a single configuration. Thus, the Enum Driver defaults to selecting the configuration with `bConfigurationValue = 1`. + + If users would like to select a different configuration, the Enum Driver provides an enumeration filter callback feature (enabled via :ref:`CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK`). This callback is called during the enumeration process and allows users to decide what devices to enumerate, and which ``bConfigurationValue`` to use. + +.. note:: + + For more detailed information about Bus enumeration, please refer to `USB 2.0 Specification `_ > Chapter 9.1.2 **Bus Enumeration**. + +Host Stack Requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the USB 2.0 specification requirements, the Enum Driver also takes into consideration the requirements set for the overall Host Stack (see :doc:`./usb_host_notes_design`): + +- Enum Driver must not instantiate any tasks/threads +- Enum Driver must be event driven, providing event callbacks and an event processing function +- Enum Driver must use only API from underlying layer (USBH) + +Implementation & Usage +---------------------- + +Host Stack Interaction +^^^^^^^^^^^^^^^^^^^^^^ + +The Enum Driver takes place between USB Host layer and USBH layer with a possibility to select configuration with ``enum_config_t.enum_event_cb`` callback provided on Enum Driver installation. + +Events & Processing +^^^^^^^^^^^^^^^^^^^ + +The Enum Driver is completely event driven and all event handling is done via the ``enum_process()`` function. The ``enum_config_t.proc_req_cb`` callback provided on Enum Driver installation will be called when processing is required. Typically, ``enum_process()`` will be called from a shared USB Host stack thread/task. + +The Enum Driver exposes the following event callbacks: + +- ``enum_event_cb_t`` used to indicate various events regarding an enumeration process. This callback is called from the context of ``enum_process()`` + +The Enum Driver exposes the following events: + +- ``ENUM_EVENT_STARTED`` Enumeration process has been started +- ``ENUM_EVENT_RESET_REQUIRED`` Enumeration process requires device reset +- ``ENUM_EVENT_COMPLETED`` Enumeration process has been completed +- ``ENUM_EVENT_CANCELED`` Enumeration process has been canceled (due to internal error or via ``enum_cancel()`` function call) + +Device Enumeration +^^^^^^^^^^^^^^^^^^ + +The USB device enumeration process implemented in the Enum Driver is mostly based same process in the `Windows USB stack `__. The Enum Driver's enumeration process involves the following steps: + +#. First device descriptor request (to obtain the MPS of EP0) +#. Second device reset (to workaround some non-compliant devices) +#. Set device address +#. Second device descriptor request (to obtain and store the full device descriptor) +#. Configuration descriptor request (to obtain the full configuration descriptor of selected configuration) +#. Language ID Table request (checks to see if en-US is supported) +#. Manufacturer string descriptor request +#. Product string descriptor request +#. Serial number string descriptor request +#. Set configuration request (sets the device to target configuration number) + +.. note:: + + String descriptors are optional. If a device does not support string descriptors, these stages could be omitted. + +Enumeration Stages +^^^^^^^^^^^^^^^^^^ + +The Enum Driver splits the enumeration process into multiple stages which are executed linearly. Depending on the connected device, some stages (such as fetching the string descriptors) can be skipped. When a stage completes, a call to the ``enum_config_t.proc_req_cb`` callback must be made to trigger a subsequent call of ``enum_process()``.The subsequent call of ``enum_process()`` will then select and execute the next stage of enumeration. Stage completion can trigger the ``enum_config_t.proc_req_cb`` callback in one of the following ways: + +- Inside the control transfer completion callback (for stages that send a control transfer) +- Direct call to ``enum_config_t.proc_req_cb`` (for stages that don't need to wait for any event) +- Inside ``enum_proceed()`` (for stages that require some action to be carried out outside the Enum Driver) + +Any control transfer made during enumeration is split into two stages, where the first stage executes the transfer and the second stage (suffixed with ``_CHECK``) will check the results of the transfers. + +When requesting a variable length descriptors (e.g., configuration or string descriptors), the request is split into two control transfers. The first control transfer is fixed in length which only reads the header of the descriptor. The ``bLength`` field of the descriptor's header indicates the full length of the entire descriptor and is used to set the size of the second transfer which fetches the entire descriptor. As a result, any request for a variable length descriptor is split into four stages: + +- Get short **ANY** descriptor (prefixed with ``GET_SHORT_...``) +- Check short **ANY** descriptor (prefixed with ``CHECK_SHORT_...``) +- Get full **ANY** descriptor (prefixed with ``GET_FULL_...``) +- Check full **ANY** descriptor (prefixed with ``CHECK_FULL_...``) + +.. note:: + + Retrieving the Device Descriptor is an exception here because the second reset is taken place after retrieving short Device Descriptor. + +Cancel Enumeration +^^^^^^^^^^^^^^^^^^ + +In some cases (such as a device disconnection), an ongoing enumeration process may need to be cancelled. An ongoing enumeration can be cancelled (regardless of its current stage) by calling ``enum_cancel()`` which will change the enumeration process's current stage to ``ENUM_STAGE_CANCEL``. + +On the next call to ``enum_process``, the Enum Driver will execute the ``ENUM_STAGE_CANCEL`` which does the following: + +- releases the device's enumeration lock. +- frees all resources related to the current device. +- propagates the ``ENUM_EVENT_CANCELED`` event. diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst new file mode 100644 index 0000000000..3b2876a7a4 --- /dev/null +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst @@ -0,0 +1,65 @@ +USB Host External Hub Driver (Ext Hub) +====================================== + +Introduction +------------ + +The External Hub Driver (henceforth referred to as Ext Hub Driver) + +Requirements +------------ + +USB Specification Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Chapter 11 of the USB 2.0 specification outlines some aspects, when a USB Hub device is attached to a powered port. + +The design of the Ext Driver takes into consideration the following: + +- **Connectivity behavior** +- **Power management** +- **Device connect/disconnect detection** +- **Bus fault detection and recovery** +- **High-, full-, and low-speed device support** + +.. note:: + + For more detailed information, please refer to `USB 2.0 Specification `_ > Chapter 11.1 **Overview**. + +Host Stack Requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the USB 2.0 specification requirements, the Ext Hub Driver also takes into consideration the requirements set for the overall Host Stack (see :doc:`./usb_host_notes_design`): + +- Ext Hub Driver must not instantiate any tasks/threads +- Ext Hub Driver must be event driven, providing event callbacks and an event processing function +- Ext Hub Driver must use only API from underlying layer (USBH) + +Implementation & Usage +---------------------- + +Host Stack Interaction +^^^^^^^^^^^^^^^^^^^^^^ + +The Ext Hub Driver takes place between USB Host layer and USBH layer, next to the Hub Driver. The Hub Driver and the Ext Hub Driver were split into two Drivers to achieve the goal of logic distinguishing between root Hub and external Hub. + +Device handling +^^^^^^^^^^^^^^^ + +The Ext Hub Driver can be installed via ``ext_hub_install()`` call and uninstalled via ``ext_hub_uninstall()`` call. After installation the Ext Hub provides the following APIs for external Hub addition and removal: + +- ``ext_hub_new_dev()`` which will verify the device class (`HUB_CLASSCODE (09H)`) and, if the device has the Hub class, the Ext Hub Driver: + + - allocates a new device object + - adds it to the external device pool + - starts the process of Hub configuration (retrieving Hub Descriptor, Device status and Hub status) + +- ``ext_hub_dev_gone()`` which will verify the device in the Ext Hub Driver list and start the process of external Hub device removing. + +Events & Processing +^^^^^^^^^^^^^^^^^^^ + +The Ext Hub Driver is completely event driven and all event handling is done via the ``ext_hub_process()`` function. The ``ext_hub_config_t.proc_req_cb`` callback provided on the Ext Hub Driver installation will be called when processing is required. Typically, ``ext_hub_process()`` will be called from the Hub Driver ``hub_process()`` processing function. + +The Ext Hub Driver does not expose any event callback. + diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 2d23befb42..738ac7a270 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -22,6 +22,8 @@ This document is split into the following sections: usb_host_notes_arch usb_host_notes_dwc_otg usb_host_notes_usbh + usb_host_notes_enum + usb_host_notes_ext_hub Todo: @@ -44,6 +46,10 @@ Features & Limitations **The Host Stack currently supports the following notable features:** +.. only:: esp32p4 + + - Supports HS (High Speed) + - Supports FS (Full Speed) and LS (Low Speed) devices - Supports all transfer types (Control, Bulk, Isochronous, and Interrupt) - Automatically enumerates connected devices @@ -51,5 +57,4 @@ Features & Limitations **The Host Stack currently has the following notable limitations:** -- No HS (High Speed) support - No Hub support (currently only supports a single device) diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_enum.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_enum.rst new file mode 100644 index 0000000000..3e4a5dd75b --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_enum.rst @@ -0,0 +1 @@ +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_enum.rst diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst new file mode 100644 index 0000000000..e8bab47973 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst @@ -0,0 +1 @@ +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 1ab83954de..09d410debf 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -1,55 +1 @@ -USB 主机维护者注意事项(简介) -============================== - -:link_to_translation:`en:[English]` - -本文档包含有关 USB 主机协议栈实现细节的信息,面向 USB 主机协议栈的维护者和第三方贡献者。USB 主机协议栈的用户请参考 :doc:`../usb_host`。 - -.. warning:: - - USB 主机协议栈的实现细节属于私有 API,因此,除 USB 主机库外的所有层均不遵循 :ref:`ESP-IDF 版本简介 `,即允许进行重大更改。 - -.. figure:: ../../../../_static/usb_host/stack-overview.png - :align: center - :alt: 主机协议栈层次结构图 - -本文档分为以下几个部分: - -.. toctree:: - :maxdepth: 1 - - usb_host_notes_design - usb_host_notes_arch - usb_host_notes_dwc_otg - usb_host_notes_usbh - -待写章节: - -- USB 主机维护者注意事项(HAL 和 LL) -- USB 主机维护者注意事项(HCD) -- USB 主机维护者注意事项(Hub) -- USB 主机维护者注意事项(USB Host Library) - -.. -------------------------------------------------- Introduction ----------------------------------------------------- - -简介 ----- - -ESP-IDF USB 主机协议栈允许 {IDF_TARGET_NAME} 作为 USB 主机运行,此时,{IDF_TARGET_NAME} 能够与各种 USB 设备通信。然而,大多数 USB 主机协议栈实现都不运行在嵌入式硬件上(即在电脑和手机端运行),因此,相对来说具有更多的资源(即,具有更高内存和 CPU 速度)。 - -ESP-IDF USB 主机协议栈(以下简称为主机协议栈)的实现考虑到了 {IDF_TARGET_NAME} 的嵌入式特性,这体现在主机协议栈设计的各个方面。 - -特性和局限性 -^^^^^^^^^^^^ - -**主机协议栈目前支持以下显著特性:** - -- 支持 FS(全速)和 LS(低速)设备 -- 支持所有传输类型(控制传输、批量传输、同步传输和中断传输) -- 自动枚举已连接设备 -- 允许多个类驱动程序(即 USB 主机库的客户端)同时运行并共享同一设备(即组合设备) - -**主机协议栈目前存在以下显著局限:** - -- 不支持 HS(高速)设备 -- 不支持集线器(当前仅支持单个设备) +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_index.rst 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 0776c741c7..594488df51 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 @@ -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 */