diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 774177d0339a..ce87157e50e5 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" diff --git a/components/usb/enum.c b/components/usb/enum.c new file mode 100644 index 000000000000..eb4b448a93ab --- /dev/null +++ b/components/usb/enum.c @@ -0,0 +1,1364 @@ +/* + * 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 + * + * - 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 + */ +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_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[] = { + "NONE", + "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", + "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 { + union { + struct { + uint32_t processing: 1; /**< Enumeration process is active */ + uint32_t reserved31: 31; /**< Reserved */ + }; + uint32_t val; + } flags; + enum_stage_t stage; /**< Current enumeration stage */ + uint8_t next_dev_addr; /**< Device address for device under enumeration */ + } dynamic; /**< Dynamic members. Require a critical section */ + + 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_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 */ + } 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; +static portMUX_TYPE enum_driver_lock = portMUX_INITIALIZER_UNLOCKED; + +const char *ENUM_TAG = "ENUM"; + +// ----------------------------------------------------------------------------- +// ---------------------------- Helpers ---------------------------------------- +// ----------------------------------------------------------------------------- +#define ENUM_ENTER_CRITICAL() portENTER_CRITICAL(&enum_driver_lock) +#define ENUM_EXIT_CRITICAL() portEXIT_CRITICAL(&enum_driver_lock) + +#define ENUM_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +#define ENUM_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + ENUM_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// ----------------------------------------------------------------------------- +// ------------------------ Private functions ---------------------------------- +// ----------------------------------------------------------------------------- +static inline uint8_t get_next_dev_addr(void) +{ + uint8_t ret = 0; + + ENUM_ENTER_CRITICAL(); + p_enum_driver->dynamic.next_dev_addr++; + if (p_enum_driver->dynamic.next_dev_addr > ENUM_MAX_ADDRESS) { + p_enum_driver->dynamic.next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR; + } + ret = p_enum_driver->dynamic.next_dev_addr; + ENUM_EXIT_CRITICAL(); + + 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->dynamic.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(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 + */ +static void control_request_general(void) +{ + 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 (p_enum_driver->dynamic.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 + */ +static void control_request_string(void) +{ + usb_transfer_t *transfer = &p_enum_driver->constant.urb->transfer; + enum_stage_t stage = p_enum_driver->dynamic.stage; + 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) + sizeof(usb_str_desc_t) /* usb_round_up_to_mps(sizeof(usb_str_desc_t), ctx->bMaxPacketSize0) */; + // 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) + bLength /* usb_round_up_to_mps(ctx->str_desc_bLength, ctx->bMaxPacketSize0) */; + // 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)); + + ENUM_ENTER_CRITICAL(); + enum_stage_t stage = p_enum_driver->dynamic.stage; + ENUM_EXIT_CRITICAL(); + + return usbh_dev_set_str_desc(dev_hdl, str_desc, get_str_index(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 + */ +static esp_err_t control_request(void) +{ + esp_err_t ret; + + switch (p_enum_driver->dynamic.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(); + 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(); + 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[p_enum_driver->dynamic.stage]); + } + + return ret; +} + +/** + * @brief Control request response handling stage + * + * Based on the stage, does parse the response data + */ +static esp_err_t control_response_handling(void) +{ + 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; + + // Sanity check + // We already checked the transfer status for control transfer + assert(ctrl_xfer->status == USB_TRANSFER_STATUS_COMPLETED); + + // 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 (p_enum_driver->dynamic.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; + + // Close the 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; + + p_enum_driver->constant.urb->transfer.context = NULL; + + ENUM_ENTER_CRITICAL(); + p_enum_driver->dynamic.flags.processing = 0; + ENUM_EXIT_CRITICAL(); + + 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(); + + ENUM_ENTER_CRITICAL(); + p_enum_driver->dynamic.flags.processing = 0; + ENUM_EXIT_CRITICAL(); + + 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 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 + */ +static void set_next_stage(bool last_stage_pass) +{ + enum_stage_t last_stage = p_enum_driver->dynamic.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) { + // Complete stage are terminal, move state machine to IDLE + next_stage = ENUM_STAGE_IDLE; + } else if (last_stage == ENUM_STAGE_CANCEL) { + // CANCEL can be called anytime, keep the state and handle in the next process callback + next_stage = last_stage; + } 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_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 and jump straight to cleanup. + case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: + // iSerialNumber string failed. Jump to complete. + next_stage = ENUM_STAGE_COMPLETE; + 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; + } + } + ENUM_ENTER_CRITICAL(); + p_enum_driver->dynamic.stage = next_stage; + ENUM_EXIT_CRITICAL(); +} + +/** + * @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); + + if (ctrl_xfer->status == USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOG_BUFFER_HEXDUMP(ENUM_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + goto process; + } else { + ESP_LOGE(ENUM_TAG, "[%d:%d] Control transfer failed, status=%d", + p_enum_driver->single_thread.parent_dev_addr, + p_enum_driver->single_thread.parent_port_num, + ctrl_xfer->status); + } + // Cancel enumeration process + enum_cancel(p_enum_driver->single_thread.dev_uid); +process: + // 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_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver == NULL, ESP_ERR_INVALID_STATE); + ENUM_EXIT_CRITICAL(); + 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->dynamic.flags.val = 0; + enum_drv->dynamic.stage = ENUM_STAGE_IDLE; + enum_drv->dynamic.next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR; + + ENUM_ENTER_CRITICAL(); + if (p_enum_driver != NULL) { + ENUM_EXIT_CRITICAL(); + ret = ESP_ERR_NOT_FINISHED; + goto err; + } + p_enum_driver = enum_drv; + ENUM_EXIT_CRITICAL(); + + // 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_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + ENUM_EXIT_CRITICAL(); + + ENUM_ENTER_CRITICAL(); + enum_driver_t *enum_drv = p_enum_driver; + p_enum_driver = NULL; + ENUM_EXIT_CRITICAL(); + + // 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_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + ENUM_CHECK_FROM_CRIT(p_enum_driver->dynamic.flags.processing == 0, ESP_ERR_INVALID_STATE); + ENUM_EXIT_CRITICAL(); + + 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); + + ENUM_ENTER_CRITICAL(); + p_enum_driver->dynamic.flags.processing = 1; + p_enum_driver->dynamic.stage = ENUM_STAGE_GET_SHORT_DEV_DESC; + ENUM_EXIT_CRITICAL(); + + 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_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + ENUM_CHECK_FROM_CRIT(p_enum_driver->dynamic.flags.processing != 0, ESP_ERR_INVALID_STATE); + ENUM_EXIT_CRITICAL(); + + // 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_stage_t stage = ENUM_STAGE_CANCEL; + + ENUM_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + ENUM_CHECK_FROM_CRIT(p_enum_driver->dynamic.flags.processing != 0, ESP_ERR_INVALID_STATE); + stage = p_enum_driver->dynamic.stage; + p_enum_driver->dynamic.stage = ENUM_STAGE_CANCEL; + ENUM_EXIT_CRITICAL(); + + 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[stage]); + + // Nothing to do more here, will deal with that the very next enum_process() + return ESP_OK; +} + +esp_err_t enum_process(void) +{ + ENUM_ENTER_CRITICAL(); + ENUM_CHECK_FROM_CRIT(p_enum_driver != NULL, ESP_ERR_INVALID_STATE); + ENUM_CHECK_FROM_CRIT(p_enum_driver->dynamic.flags.processing != 0, ESP_ERR_INVALID_STATE); + ENUM_EXIT_CRITICAL(); + + esp_err_t ret = ESP_FAIL; + bool need_process_cb = true; + enum_stage_t stage = p_enum_driver->dynamic.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: + need_process_cb = false; // Do not need to request process callback, as we need to wait transfer completion + ret = control_request(); + 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)); + ret = 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: + ret = control_response_handling(); + break; + case ENUM_STAGE_SELECT_CONFIG: + ret = select_active_configuration(); + break; + case ENUM_STAGE_SECOND_RESET: + need_process_cb = false; // We need to wait Hub driver to finish port reset + ret = second_reset(); + break; + case ENUM_STAGE_CANCEL: + need_process_cb = false; // Terminal state + ret = stage_cancel(); + break; + case ENUM_STAGE_COMPLETE: + need_process_cb = false; // Terminal state + ret = stage_complete(); + break; + default: + // Should never occur + ret = ESP_ERR_INVALID_STATE; + abort(); + break; + } + + // Set nest stage of enumeration process + set_next_stage(ret == ESP_OK); + + // Request process callback is necessary + if (need_process_cb) { + p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg); + } + + return ret; +} diff --git a/components/usb/hub.c b/components/usb/hub.c index 4e74ebce8c29..a5dc27bd20b8 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -34,26 +34,9 @@ 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_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 @@ -70,113 +53,6 @@ typedef enum { ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */ } root_port_state_t; -/** - * @brief Stages of device enumeration listed in their order of execution - * - * - 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 - */ -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", -}; - -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 { // Dynamic members require a critical section struct { @@ -193,7 +69,6 @@ typedef struct { // Single thread members don't require a critical section so long as they are never accessed from multiple threads 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 struct { @@ -246,516 +121,6 @@ const char *HUB_DRIVER_TAG = "HUB"; */ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); -/** - * @brief Control transfer callback used for enumeration - * - * @param transfer Transfer object - */ -static void enum_transfer_callback(usb_transfer_t *transfer); - -// ------------------------------------------------- Enum Functions ---------------------------------------------------- - -static bool enum_stage_start(enum_ctrl_t *enum_ctrl) -{ - // Open the newly added device (at address 0) - ESP_ERROR_CHECK(usbh_devs_open(0, &p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); - - // Get the speed of the device to set the initial MPS of EP0 - usb_device_info_t dev_info; - ESP_ERROR_CHECK(usbh_dev_get_info(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl, &dev_info)); - enum_ctrl->bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; - - // Lock the device for enumeration. This allows us call usbh_dev_set_...() functions during enumeration - ESP_ERROR_CHECK(usbh_dev_enum_lock(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl)); - - // Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb -#ifdef ENABLE_ENUM_FILTER_CALLBACK - enum_ctrl->graceful_exit = false; -#endif // ENABLE_ENUM_FILTER_CALLBACK - return true; -} - -static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl) -{ - // Hub Driver currently support only one root port, so the second reset always in root port - if (hub_port_reset(NULL, 0) != 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; - } - - uint8_t enum_config_index; - const bool enum_continue = enum_ctrl->enum_filter_cb(device_desc, &enum_config_index); - - // 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; - } - - // 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 - - return true; -} - -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; -} - -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; - } - - default: // Should never occur - abort(); - break; - } - - 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 str_index; - if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_MANU_STR_DESC) { - str_index = 0; - } else if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_PROD_STR_DESC) { - str_index = 1; - } else { // ENUM_STAGE_CHECK_FULL_PROD_STR_DESC - str_index = 2; - } - ESP_ERROR_CHECK(usbh_dev_set_str_desc(enum_ctrl->dev_hdl, str_desc, str_index)); - ret = true; - break; - } - } - default: // Should never occur - ret = false; - abort(); - break; - } - return ret; -} - -static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) -{ - // 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_dev_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_dev_close(enum_ctrl->dev_hdl)); - // We allow this to fail in case the device object was already freed - usbh_devs_remove(HUB_ROOT_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; - 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(); - } -} - -// ------------------------------------------------- Event Handling ---------------------------------------------------- - // ---------------------- Callbacks ------------------------ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) @@ -767,15 +132,6 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } -static void enum_transfer_callback(usb_transfer_t *transfer) -{ - // We simply trigger a processing request to handle the completed enumeration control transfer - HUB_DRIVER_ENTER_CRITICAL_SAFE(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - HUB_DRIVER_EXIT_CRITICAL_SAFE(); - p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); -} - // ---------------------- Handlers ------------------------- static void root_port_handle_events(hcd_port_handle_t root_port_hdl) @@ -809,14 +165,12 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device"); goto new_dev_err; } + // Save uid to Port p_hub_driver_obj->single_thread.root_dev_uid = HUB_ROOT_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; event_data.event = HUB_EVENT_CONNECTED; event_data.connected.uid = p_hub_driver_obj->single_thread.root_dev_uid; @@ -848,7 +202,6 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) abort(); // Should never occur break; } - p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); if (port_has_device) { // The port must have a device object @@ -890,88 +243,10 @@ static void root_port_req(hcd_port_handle_t root_port_hdl) } } -static void enum_handle_events(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); - 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; - break; - default: - stage_pass = true; - 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); -} - static esp_err_t root_port_recycle(void) { // 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) { @@ -1004,12 +279,11 @@ 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; + + *client_ret = NULL; // Install HCD port hcd_port_config_t port_config = { @@ -1025,11 +299,6 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) } // 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 = 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; @@ -1046,16 +315,11 @@ 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(root_port_hdl)); err: - urb_free(enum_urb); heap_caps_free(hub_driver_obj); return ret; } @@ -1071,7 +335,6 @@ esp_err_t hub_uninstall(void) 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; } @@ -1178,9 +441,6 @@ esp_err_t hub_process(void) if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) { root_port_req(p_hub_driver_obj->constant.root_port_hdl); } - if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { - enum_handle_events(); - } HUB_DRIVER_ENTER_CRITICAL(); action_flags = p_hub_driver_obj->dynamic.flags.actions; diff --git a/components/usb/private_include/enum.h b/components/usb/private_include/enum.h new file mode 100644 index 000000000000..0dc44cff693c --- /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/hub.h b/components/usb/private_include/hub.h index 62649d0d2eb6..8ad180828e46 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -8,7 +8,6 @@ #include #include -#include "sdkconfig.h" #include "esp_err.h" #include "usb_private.h" #include "usbh.h" @@ -60,9 +59,6 @@ typedef struct { void *proc_req_cb_arg; /**< Processing request callback argument */ hub_event_cb_t event_cb; /**< Hub event callback */ void *event_cb_arg; /**< Hub event 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_config_t; // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- diff --git a/components/usb/private_include/usb_private.h b/components/usb/private_include/usb_private.h index 4fb25cc4ee6b..0a081d4e5cb2 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/usb_host.c b/components/usb/usb_host.c index e7da863348e2..743a48639b86 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -19,6 +19,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" @@ -45,12 +46,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) typedef struct ep_wrapper_s ep_wrapper_t; typedef struct interface_s interface_t; @@ -148,7 +146,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; @@ -219,6 +218,14 @@ 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; + } + 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 @@ -263,6 +270,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(); @@ -277,8 +287,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; @@ -333,12 +343,16 @@ static void hub_event_callback(hub_event_data_t *event_data, void *arg) { switch (event_data->event) { case HUB_EVENT_CONNECTED: - // Nothing to do, because enumeration still holding in Hub Driver + // Start enumeration process + enum_start(event_data->connected.uid); break; case HUB_EVENT_RESET_COMPLETED: - // Nothing to do, because enumeration still holding in Hub Driver + // Proceed enumeration process + 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; @@ -348,6 +362,30 @@ static void hub_event_callback(hub_event_data_t *event_data, void *arg) } } +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: + hub_port_reset(event_data->reset_req.parent_dev_hdl, event_data->reset_req.parent_port_num); + break; + case ENUM_EVENT_COMPLETED: + // Propagate a new device event + ESP_ERROR_CHECK(usbh_devs_new_dev_event(event_data->complete.dev_hdl)); + break; + case ENUM_EVENT_CANCELED: + // Enumeration canceled + 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) @@ -399,6 +437,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config) - USB PHY - HCD - USBH + - Enum - Hub */ @@ -440,20 +479,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, .event_cb = hub_event_callback, .event_cb_arg = NULL, -#ifdef ENABLE_ENUM_FILTER_CALLBACK - .enum_filter_cb = config->enum_filter_cb, -#endif // ENABLE_ENUM_FILTER_CALLBACK }; ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client); if (ret != ESP_OK) { @@ -478,6 +525,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()); @@ -520,11 +569,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 @@ -571,6 +622,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 diff --git a/docs/_static/usb_host/stack-overview.png b/docs/_static/usb_host/stack-overview.png index 0558d8388b16..40996041de17 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 2bdebec6ca02..4cd11f4eb633 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -118,7 +118,8 @@ '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'] I80_LCD_DOCS = ['api-reference/peripherals/lcd/i80_lcd.rst'] RGB_LCD_DOCS = ['api-reference/peripherals/lcd/rgb_lcd.rst'] diff --git a/docs/docs_not_updated/esp32c5.txt b/docs/docs_not_updated/esp32c5.txt index acde0ee43aa6..e9eaf3921040 100644 --- a/docs/docs_not_updated/esp32c5.txt +++ b/docs/docs_not_updated/esp32c5.txt @@ -106,6 +106,7 @@ api-reference/peripherals/usb_host/usb_host_notes_index.rst api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst api-reference/peripherals/usb_host/usb_host_notes_design.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_device.rst api-reference/peripherals/sdspi_host.rst api-reference/peripherals/spi_slave.rst diff --git a/docs/docs_not_updated/esp32p4.txt b/docs/docs_not_updated/esp32p4.txt index b8329bf5905d..dacc365bc102 100644 --- a/docs/docs_not_updated/esp32p4.txt +++ b/docs/docs_not_updated/esp32p4.txt @@ -29,6 +29,7 @@ api-reference/peripherals/usb_host/usb_host_notes_index.rst api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst api-reference/peripherals/usb_host/usb_host_notes_design.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_device.rst api-reference/peripherals/touch_element.rst api-reference/peripherals/spi_flash/xip_from_psram.inc 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 000000000000..261cf16bbc84 --- /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_index.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 2d23befb42fc..6b1c6fa9ac09 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,7 @@ 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 Todo: 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 000000000000..3e4a5dd75bed --- /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_index.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 1ab83954de25..c6318a690a1a 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 @@ -22,6 +22,7 @@ USB 主机维护者注意事项(简介) usb_host_notes_arch usb_host_notes_dwc_otg usb_host_notes_usbh + usb_host_notes_enum 待写章节: 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 0776c741c7e6..594488df5193 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 */