refactor(enum): Curved out Enumeration process from Hub Driver

This commit is contained in:
Roman Leonov
2024-03-04 21:11:43 +01:00
parent 231247b0f5
commit e729453089
8 changed files with 1603 additions and 768 deletions

View File

@ -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) if(CONFIG_SOC_USB_OTG_SUPPORTED)
list(APPEND srcs "hcd_dwc.c" list(APPEND srcs "hcd_dwc.c"
"enum.c"
"hub.c" "hub.c"
"usb_helpers.c" "usb_helpers.c"
"usb_host.c" "usb_host.c"

1364
components/usb/enum.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED
#endif #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 // 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_ROOT_EVENT 0x01
#define HUB_DRIVER_FLAG_ACTION_PORT_REQ 0x02 #define HUB_DRIVER_FLAG_ACTION_PORT_REQ 0x02
#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04
#define PORT_REQ_DISABLE 0x01 #define PORT_REQ_DISABLE 0x01
#define PORT_REQ_RECOVER 0x02 #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_RECOVERY, /**< Root port encountered an error and needs to be recovered */
} root_port_state_t; } 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 { typedef struct {
// Dynamic members require a critical section // Dynamic members require a critical section
struct { 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 // Single thread members don't require a critical section so long as they are never accessed from multiple threads
struct { struct {
unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected 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; } single_thread;
// Constant members do no change after installation thus do not require a critical section // Constant members do no change after installation thus do not require a critical section
struct { 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); 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 ------------------------ // ---------------------- Callbacks ------------------------
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) 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); 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 ------------------------- // ---------------------- Handlers -------------------------
static void root_port_handle_events(hcd_port_handle_t root_port_hdl) 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"); ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device");
goto new_dev_err; goto new_dev_err;
} }
// Save uid to Port
p_hub_driver_obj->single_thread.root_dev_uid = HUB_ROOT_DEV_UID; p_hub_driver_obj->single_thread.root_dev_uid = HUB_ROOT_DEV_UID;
// Change Port state
// Start enumeration
HUB_DRIVER_ENTER_CRITICAL(); 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; p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED;
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START;
event_data.event = HUB_EVENT_CONNECTED; event_data.event = HUB_EVENT_CONNECTED;
event_data.connected.uid = p_hub_driver_obj->single_thread.root_dev_uid; event_data.connected.uid = p_hub_driver_obj->single_thread.root_dev_uid;
@ -848,7 +202,6 @@ reset_err:
abort(); // Should never occur abort(); // Should never occur
break; break;
} }
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY;
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
if (port_has_device) { if (port_has_device) {
// The port must have a device object // 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) static esp_err_t root_port_recycle(void)
{ {
// Device is free, we can now request its port be recycled // 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); 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(); HUB_DRIVER_ENTER_CRITICAL();
// How the port is recycled will depend on the port's state // How the port is recycled will depend on the port's state
switch (port_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 // Allocate Hub driver object
hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT); 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) {
if (hub_driver_obj == NULL || enum_urb == NULL) {
return ESP_ERR_NO_MEM; 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 // Install HCD port
hcd_port_config_t port_config = { 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 // 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.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 = hub_config->proc_req_cb;
hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; 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; p_hub_driver_obj = hub_driver_obj;
HUB_DRIVER_EXIT_CRITICAL(); HUB_DRIVER_EXIT_CRITICAL();
// Write-back client_ret pointer
*client_ret = (void *)hub_driver_obj;
ret = ESP_OK;
return ret; return ret;
assign_err: assign_err:
ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl)); ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl));
err: err:
urb_free(enum_urb);
heap_caps_free(hub_driver_obj); heap_caps_free(hub_driver_obj);
return ret; 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)); ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl));
// Free Hub driver resources // Free Hub driver resources
urb_free(hub_driver_obj->single_thread.enum_ctrl.urb);
heap_caps_free(hub_driver_obj); heap_caps_free(hub_driver_obj);
return ESP_OK; return ESP_OK;
} }
@ -1178,9 +441,6 @@ esp_err_t hub_process(void)
if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) { if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) {
root_port_req(p_hub_driver_obj->constant.root_port_hdl); 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(); HUB_DRIVER_ENTER_CRITICAL();
action_flags = p_hub_driver_obj->dynamic.flags.actions; action_flags = p_hub_driver_obj->dynamic.flags.actions;

View File

@ -0,0 +1,159 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#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

View File

@ -8,7 +8,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h" #include "esp_err.h"
#include "usb_private.h" #include "usb_private.h"
#include "usbh.h" #include "usbh.h"
@ -60,9 +59,6 @@ typedef struct {
void *proc_req_cb_arg; /**< Processing request callback argument */ void *proc_req_cb_arg; /**< Processing request callback argument */
hub_event_cb_t event_cb; /**< Hub event callback */ hub_event_cb_t event_cb; /**< Hub event callback */
void *event_cb_arg; /**< Hub event callback argument */ 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_config_t;
// ---------------------------------------------- Hub Driver Functions ------------------------------------------------- // ---------------------------------------------- Hub Driver Functions -------------------------------------------------

View File

@ -58,6 +58,7 @@ typedef struct urb_s urb_t;
typedef enum { typedef enum {
USB_PROC_REQ_SOURCE_USBH = 0x01, USB_PROC_REQ_SOURCE_USBH = 0x01,
USB_PROC_REQ_SOURCE_HUB = 0x02, USB_PROC_REQ_SOURCE_HUB = 0x02,
USB_PROC_REQ_SOURCE_ENUM = 0x03
} usb_proc_req_source_t; } usb_proc_req_source_t;
/** /**

View File

@ -20,6 +20,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to
#include "esp_log.h" #include "esp_log.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "hub.h" #include "hub.h"
#include "enum.h"
#include "usbh.h" #include "usbh.h"
#include "hcd.h" #include "hcd.h"
#include "esp_private/usb_phy.h" #include "esp_private/usb_phy.h"
@ -46,12 +47,9 @@ static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED;
} \ } \
}) })
#define PROCESS_REQUEST_PENDING_FLAG_USBH 0x01 #define PROCESS_REQUEST_PENDING_FLAG_USBH (1 << 0)
#define PROCESS_REQUEST_PENDING_FLAG_HUB 0x02 #define PROCESS_REQUEST_PENDING_FLAG_HUB (1 << 1)
#define PROCESS_REQUEST_PENDING_FLAG_ENUM (1 << 2)
#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define ENABLE_ENUM_FILTER_CALLBACK
#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define SHORT_DESC_REQ_LEN 8 #define SHORT_DESC_REQ_LEN 8
#define CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE #define CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
@ -152,7 +150,8 @@ typedef struct {
SemaphoreHandle_t event_sem; SemaphoreHandle_t event_sem;
SemaphoreHandle_t mux_lock; SemaphoreHandle_t mux_lock;
usb_phy_handle_t phy_handle; // Will be NULL if host library is installed with skip_phy_setup 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; } constant;
} host_lib_t; } host_lib_t;
@ -223,6 +222,14 @@ static bool _unblock_lib(bool in_isr)
return yield; 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) 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 // Lock client list
@ -267,6 +274,9 @@ static bool proc_req_callback(usb_proc_req_source_t source, bool in_isr, void *a
case USB_PROC_REQ_SOURCE_HUB: case USB_PROC_REQ_SOURCE_HUB:
p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_HUB; p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_HUB;
break; 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); bool yield = _unblock_lib(in_isr);
HOST_EXIT_CRITICAL_SAFE(); HOST_EXIT_CRITICAL_SAFE();
@ -281,8 +291,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 != NULL);
assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL); assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL);
// Redistribute completed control transfers to the clients that submitted them // 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) { if (_is_internal_client(event_data->ctrl_xfer_data.urb->usb_host_client)) {
// Redistribute to Hub driver. Simply call the transfer callback // Simply call the transfer callback
event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer); event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer);
} else { } else {
client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client; client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client;
@ -337,12 +347,16 @@ static void hub_event_callback(hub_event_data_t *event_data, void *arg)
{ {
switch (event_data->event) { switch (event_data->event) {
case HUB_EVENT_CONNECTED: case HUB_EVENT_CONNECTED:
// Nothing to do, because enumeration still holding in Hub Driver // Start enumeration process
enum_start(event_data->connected.uid);
break; break;
case HUB_EVENT_RESET_COMPLETED: 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; break;
case HUB_EVENT_DISCONNECTED: 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 // We allow this to fail in case the device object was already freed
usbh_devs_remove(event_data->disconnected.uid); usbh_devs_remove(event_data->disconnected.uid);
break; break;
@ -352,6 +366,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 ---------------------- // ------------------- Client Related ----------------------
static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr)
@ -409,6 +447,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
- USB PHY - USB PHY
- HCD - HCD
- USBH - USBH
- Enum
- Hub - Hub
*/ */
@ -450,20 +489,28 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
goto usbh_err; goto usbh_err;
} }
#ifdef ENABLE_ENUM_FILTER_CALLBACK // Install Enumeration driver
if (config->enum_filter_cb == NULL) { enum_config_t enum_config = {
ESP_LOGW(USB_HOST_TAG, "User callback to set USB device configuration is enabled, but not used"); .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 #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 // Install Hub
hub_config_t hub_config = { hub_config_t hub_config = {
.proc_req_cb = proc_req_callback, .proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL, .proc_req_cb_arg = NULL,
.event_cb = hub_event_callback, .event_cb = hub_event_callback,
.event_cb_arg = NULL, .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); ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client);
if (ret != ESP_OK) { if (ret != ESP_OK) {
@ -488,6 +535,8 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
assign_err: assign_err:
ESP_ERROR_CHECK(hub_uninstall()); ESP_ERROR_CHECK(hub_uninstall());
hub_err: hub_err:
ESP_ERROR_CHECK(enum_uninstall());
enum_err:
ESP_ERROR_CHECK(usbh_uninstall()); ESP_ERROR_CHECK(usbh_uninstall());
usbh_err: usbh_err:
ESP_ERROR_CHECK(hcd_uninstall()); ESP_ERROR_CHECK(hcd_uninstall());
@ -530,11 +579,13 @@ esp_err_t usb_host_uninstall(void)
/* /*
Uninstall each layer of the Host stack (listed below) from the highest layer to the lowest Uninstall each layer of the Host stack (listed below) from the highest layer to the lowest
- Hub - Hub
- Enum
- USBH - USBH
- HCD - HCD
- USB PHY - USB PHY
*/ */
ESP_ERROR_CHECK(hub_uninstall()); ESP_ERROR_CHECK(hub_uninstall());
ESP_ERROR_CHECK(enum_uninstall());
ESP_ERROR_CHECK(usbh_uninstall()); ESP_ERROR_CHECK(usbh_uninstall());
ESP_ERROR_CHECK(hcd_uninstall()); ESP_ERROR_CHECK(hcd_uninstall());
// If the USB PHY was setup, then delete it // If the USB PHY was setup, then delete it
@ -581,6 +632,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) { if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_HUB) {
ESP_ERROR_CHECK(hub_process()); ESP_ERROR_CHECK(hub_process());
} }
if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_ENUM) {
ESP_ERROR_CHECK(enum_process());
}
ret = ESP_OK; ret = ESP_OK;
// Set timeout_ticks to 0 so that we can check for events again without blocking // Set timeout_ticks to 0 so that we can check for events again without blocking

View File

@ -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 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */