diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 774177d033..ce87157e50 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 0000000000..eb4b448a93 --- /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 4e74ebce8c..a5dc27bd20 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 @@ reset_err: 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 0000000000..0dc44cff69 --- /dev/null +++ b/components/usb/private_include/enum.h @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "hcd.h" +#include "usbh.h" +#include "usb/usb_types_stack.h" +#include "usb/usb_types_ch9.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------------- Settings & Configuration ----------------------------- +#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK +#define ENABLE_ENUM_FILTER_CALLBACK 1 +#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK + +// -------------------------- Public Types ------------------------------------- + +// ---------------------------- Handles ---------------------------------------- + +/** + * @brief Handle of enumeration control object + */ +typedef struct enum_ctx_handle_s * enum_ctx_handle_t; + +// ------------------------------ Events --------------------------------------- + +/** + * @brief Event data object for Enumerator driver events + */ +typedef enum { + ENUM_EVENT_STARTED, /**< Enumeration of a device has started */ + ENUM_EVENT_RESET_REQUIRED, /**< Enumerating device requires a reset */ + ENUM_EVENT_COMPLETED, /**< Enumeration of a device has completed */ + ENUM_EVENT_CANCELED, /**< Enumeration of a device was canceled */ +} enum_event_t; + +typedef struct { + enum_event_t event; /**< Enumerator driver event */ + union { + struct { + unsigned int uid; /**< Device unique ID */ + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } started; /**< ENUM_EVENT_STARTED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } reset_req; /**< ENUM_EVENT_RESET_REQUIRED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + usb_device_handle_t dev_hdl; /**< Handle of the enumerating device */ + uint8_t dev_addr; /**< Address of the enumerating device */ + } complete; /**< ENUM_EVENT_COMPLETED specific data */ + + struct { + usb_device_handle_t parent_dev_hdl; /**< Parent of the enumerating device */ + uint8_t parent_port_num; /**< Parent port number of the enumerating device */ + } canceled; /**< ENUM_EVENT_CANCELED specific data */ + }; +} enum_event_data_t; + +// ---------------------------- Callbacks -------------------------------------- + +/** + * @brief Callback used to indicate that the Enumerator has an event + */ +typedef void (*enum_event_cb_t)(enum_event_data_t *event_data, void *arg); + +/** + * @brief Enum driver configuration + */ +typedef struct { + usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */ + void *proc_req_cb_arg; /**< Processing request callback argument */ + enum_event_cb_t enum_event_cb; /**< Enum event callback */ + void *enum_event_cb_arg; /**< Enum event callback argument */ +#if ENABLE_ENUM_FILTER_CALLBACK + usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */ + void *enum_filter_cb_arg; /**< Set device configuration callback argument */ +#endif // ENABLE_ENUM_FILTER_CALLBACK +} enum_config_t; + +/** + * @brief Install Enumerator driver + * + * Entry: + * - USBH must already be installed + * - HUB must already be installed + * + * @param[in] enum_config Enumeration driver configuration + * @param[out] client_ret Unique pointer to identify Enum Driver as a USB Host client + * @return esp_err_t + */ +esp_err_t enum_install(enum_config_t *enum_config, void **client_ret); + +/** + * @brief Uninstall Enumerator driver + * + * This must be called before uninstalling the HUB and USBH + * + * @return esp_err_t + */ +esp_err_t enum_uninstall(void); + +/** + * @brief Start the enumeration process + * + * This will start the enumeration process for the device currently at address 0 + * + * @param[in] uid Unique device ID + * @retval ESP_OK: Enumeration process started + * @retval ESP_ERR_NOT_FOUND: No device at address 0 + */ +esp_err_t enum_start(unsigned int uid); + +/** + * @brief Continue enumeration process + * + * This will continue the enumeration process. Typically called after the successful + * handling of a request from the Enumerator driver (such as ENUM_EVENT_RESET_REQUIRED) + * + * @param[in] uid Unique device ID + * @return esp_err_t + */ +esp_err_t enum_proceed(unsigned int uid); + +/** + * @brief Cancel the enumeration process + * + * This will cancel enumeration process for device object under enumeration + * + * @return esp_err_t + */ +esp_err_t enum_cancel(unsigned int uid); + +/** + * @brief Enumerator processing function + * + * Processing function that must be called repeatedly to process enumeration stages + * + * @return esp_err_t + */ +esp_err_t enum_process(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index 62649d0d2e..8ad180828e 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 4fb25cc4ee..0a081d4e5c 100644 --- a/components/usb/private_include/usb_private.h +++ b/components/usb/private_include/usb_private.h @@ -58,6 +58,7 @@ typedef struct urb_s urb_t; typedef enum { USB_PROC_REQ_SOURCE_USBH = 0x01, USB_PROC_REQ_SOURCE_HUB = 0x02, + USB_PROC_REQ_SOURCE_ENUM = 0x03 } usb_proc_req_source_t; /** diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index e7da863348..743a48639b 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/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c index 0776c741c7..594488df51 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */