From 548b03c69f30cd76f150fa5ff53e012480025a5d Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 2 Apr 2024 14:25:11 +0200 Subject: [PATCH 1/2] feat(ext_hub): Added External Hub driver --- components/usb/CMakeLists.txt | 4 + components/usb/Kconfig | 118 +- components/usb/ext_hub.c | 1598 +++++++++++++++++++ components/usb/hub.c | 163 +- components/usb/include/usb/usb_types_ch11.h | 41 +- components/usb/include/usb/usb_types_ch9.h | 30 +- components/usb/private_include/ext_hub.h | 248 +++ components/usb/private_include/hub.h | 49 + components/usb/usb_host.c | 23 +- 9 files changed, 2191 insertions(+), 83 deletions(-) create mode 100644 components/usb/ext_hub.c create mode 100644 components/usb/private_include/ext_hub.h diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index 8eca16596f..8f2ab8dc12 100644 --- a/components/usb/CMakeLists.txt +++ b/components/usb/CMakeLists.txt @@ -29,6 +29,10 @@ if(CONFIG_SOC_USB_OTG_SUPPORTED) list(APPEND priv_includes "private_include") endif() +if(CONFIG_USB_HOST_HUBS_SUPPORTED) + list(APPEND srcs "ext_hub.c") +endif() + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include} PRIV_INCLUDE_DIRS ${priv_includes} diff --git a/components/usb/Kconfig b/components/usb/Kconfig index ac71a7125e..1c333f870b 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -46,73 +46,85 @@ menu "USB-OTG" bool "Periodic OUT" endchoice - menu "Root Hub configuration" + menu "Hub Driver Configuration" - config USB_HOST_DEBOUNCE_DELAY_MS - int "Debounce delay in ms" - default 250 + menu "Root Port configuration" + + config USB_HOST_DEBOUNCE_DELAY_MS + int "Debounce delay in ms" + default 250 + help + On connection of a USB device, the USB 2.0 specification requires + a "debounce interval with a minimum duration of 100ms" to allow the connection to stabilize + (see USB 2.0 chapter 7.1.7.3 for more details). + During the debounce interval, no new connection/disconnection events are registered. + + The default value is set to 250 ms to be safe. + + config USB_HOST_RESET_HOLD_MS + int "Reset hold in ms" + default 30 + help + The reset signaling can be generated on any Hub or Host Controller port by request from + the USB System Software. The USB 2.0 specification requires that "the reset signaling must + be driven for a minimum of 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). + After the reset, the hub port will transition to the Enabled state (refer to Section 11.5). + + The default value is set to 30 ms to be safe. + + config USB_HOST_RESET_RECOVERY_MS + int "Reset recovery delay in ms" + default 30 + help + After a port stops driving the reset signal, the USB 2.0 specification requires that + the "USB System Software guarantees a minimum of 10 ms for reset recovery" before the + attached device is expected to respond to data transfers (see USB 2.0 chapter 7.1.7.3 for + more details). + The device may ignore any data transfers during the recovery interval. + + The default value is set to 30 ms to be safe. + + + config USB_HOST_SET_ADDR_RECOVERY_MS + int "SetAddress() recovery time in ms" + default 10 + help + "After successful completion of the Status stage, the device is allowed a SetAddress() + recovery interval of 2 ms. At the end of this interval, the device must be able to accept + Setup packets addressed to the new address. Also, at the end of the recovery interval, the + device must not respond to tokens sent to the old address (unless, of course, the old and new + address is the same)." See USB 2.0 chapter 9.2.6.3 for more details. + + The default value is set to 10 ms to be safe. + + endmenu #Root Hub configuration + + config USB_HOST_HUBS_SUPPORTED + bool "Support Hubs" + default n help - On connection of a USB device, the USB 2.0 specification requires a "debounce interval with a minimum - duration of 100ms" to allow the connection to stabilize (see USB 2.0 chapter 7.1.7.3 for more details). - During the debounce interval, no new connection/disconnection events are registered. + Enables support of external Hubs. - The default value is set to 250 ms to be safe. - - config USB_HOST_RESET_HOLD_MS - int "Reset hold in ms" - default 30 + config USB_HOST_HUB_MULTI_LEVEL + depends on USB_HOST_HUBS_SUPPORTED + bool "Support multiple Hubs" + default y help - The reset signaling can be generated on any Hub or Host Controller port by request from the USB System - Software. The USB 2.0 specification requires that "the reset signaling must be driven for a minimum of - 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). After the reset, the hub port will transition to - the Enabled state (refer to Section 11.5). + Enables support for connecting multiple Hubs simultaneously. - The default value is set to 30 ms to be safe. - - config USB_HOST_RESET_RECOVERY_MS - int "Reset recovery delay in ms" - default 30 - help - After a port stops driving the reset signal, the USB 2.0 specification requires that the "USB System - Software guarantees a minimum of 10 ms for reset recovery" before the attached device is expected to - respond to data transfers (see USB 2.0 chapter 7.1.7.3 for more details). The device may ignore any - data transfers during the recovery interval. - - The default value is set to 30 ms to be safe. - - - config USB_HOST_SET_ADDR_RECOVERY_MS - int "SetAddress() recovery time in ms" - default 10 - help - "After successful completion of the Status stage, the device is allowed a SetAddress() recovery - interval of 2 ms. At the end of this interval, the device must be able to accept Setup packets - addressed to the new address. Also, at the end of the recovery interval, the device must not respond to - tokens sent to the old address (unless, of course, the old and new address is the same)." See USB 2.0 - chapter 9.2.6.3 for more details. - - The default value is set to 10 ms to be safe. - - endmenu #Root Hub configuration + endmenu #Hub Driver Configuration config USB_HOST_ENABLE_ENUM_FILTER_CALLBACK bool "Enable enumeration filter callback" default n help - The enumeration filter callback is called before enumeration of each newly attached device. This callback - allows users to control whether a device should be enumerated, and what configuration number to use when - enumerating a device. + The enumeration filter callback is called before enumeration of each newly attached device. + This callback allows users to control whether a device should be enumerated, and what configuration + number to use when enumerating a device. If enabled, the enumeration filter callback can be set via 'usb_host_config_t' when calling 'usb_host_install()'. - config USB_HOST_EXT_HUB_SUPPORT - depends on IDF_EXPERIMENTAL_FEATURES - bool "Support USB HUB (Experimental)" - default n - help - Feature is under development. - # Hidden or compatibility options config USB_OTG_SUPPORTED # Invisible config kept for compatibility diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c new file mode 100644 index 0000000000..fe5692ba79 --- /dev/null +++ b/components/usb/ext_hub.c @@ -0,0 +1,1598 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/usb_dwc_hal.h" // for OTG_HSPHY_INTERFACE +#include "usb_private.h" +#include "ext_hub.h" +#include "usb/usb_helpers.h" + +typedef struct ext_port_s *ext_port_hdl_t; /* This will be implemented during ext_port driver implementation */ + +#define EXT_HUB_STATUS_CHANGE_FLAG (1 << 0) +#define EXT_HUB_STATUS_PORT1_CHANGE_FLAG (1 << 1) +#define EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE + +/** + * @brief Device state + * + * Global state of the Device + */ +typedef enum { + EXT_HUB_STATE_ATTACHED, /**< Device attached, but not state is unknown (no: Hub Descriptor, Device status and Hub status) */ + EXT_HUB_STATE_CONFIGURED, /**< Device attached and configured (has Hub Descriptor, Device status and Hub status were requested )*/ + EXT_HUB_STATE_SUSPENDED, /**< Device suspended */ + EXT_HUB_STATE_RELEASED, /**< Device released by USB Host driver (device could still be present on bus) */ + EXT_HUB_STATE_FAILED /**< Device has internal error */ +} ext_hub_state_t; + +/** + * @brief Device stages + * + * During the lifecycle, Hub requires different actions. To implement interaction with external Hub the FSM, based on these stages is using. + * + * Entry: + * - Every new attached external Hub should start from EXT_HUB_STAGE_GET_HUB_DESCRIPTOR. Without Fetching Hub Descriptor, device is not configured and doesn't have any ports. + * - After handling the response during EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, the External Hub Driver configures the Device according to the data from Hub Descriptor. + * - After completion of any stage, the Device does back to the IDLE stage and waits for another request. Source of the request could be: EP1 INT callback (the Hub or Ports changes) or external call. + * - Stages, that don't required response handling could not end up with fail. + */ +typedef enum { + // Device in IDLE state + EXT_HUB_STAGE_IDLE = 0, /**< Device in idle state and do not fulfill any actions */ + // Stages, required response handling + EXT_HUB_STAGE_GET_DEVICE_STATUS, /**< Device requests Device Status. For more details, refer to 9.4.5 Get Status of usb_20 */ + EXT_HUB_STAGE_CHECK_DEVICE_STATUS, /**< Device received the Device Status and required its' handling */ + EXT_HUB_STAGE_GET_HUB_DESCRIPTOR, /**< Device requests Hub Descriptor. For more details, refer to 11.24.2.5 Get Hub Descriptor of usb_20 */ + EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, /**< Device received the Hub Descriptor and requires its' handling */ + EXT_HUB_STAGE_GET_HUB_STATUS, /**< Device requests Hub Status. For more details, refer to 11.24.2.6 Get Hub Status of usb_20 */ + EXT_HUB_STAGE_CHECK_HUB_STATUS, /**< Device received the Hub Status and requires its' handling */ + // Stages, don't required response handling + EXT_HUB_STAGE_PORT_FEATURE, /**< Device completed the Port Feature class-specific request (Set Feature or Clear Feature). For more details, refer to 11.24.2 Class-specific Requests of usb_20 */ + EXT_HUB_STAGE_PORT_STATUS_REQUEST, /**< Device completed the Port Get Status class-specific request. For more details, refer to 11.24.2 Class-specific Requests of usb_20 */ + EXT_HUB_STAGE_FAILURE /**< Device has internal error and requires handling */ +} ext_hub_stage_t; + +const char *const ext_hub_stage_strings[] = { + "IDLE", + "GET_DEVICE_STATUS", + "CHECK_DEVICE_STATUS", + "GET_HUB_DESCRIPTOR", + "CHECK_HUB_DESCRIPTOR", + "GET_HUB_STATUS", + "CHECK_HUB_STATUS", + "PORT_FEATURE", + "PORT_STATUS_REQUEST", + "FAILURE" +}; + +/** + * @brief Device action flags + */ +typedef enum { + DEV_ACTION_EP0_COMPLETE = (1 << 1), /**< Device complete one of stages, requires handling */ + DEV_ACTION_EP1_FLUSH = (1 << 2), /**< Device's Interrupt EP needs to be flushed */ + DEV_ACTION_EP1_DEQUEUE = (1 << 3), /**< Device's Interrupt EP needs to be dequeued */ + DEV_ACTION_EP1_CLEAR = (1 << 4), /**< Device's Interrupt EP needs to be cleared */ + DEV_ACTION_REQ = (1 << 5), /**< Device has new actions and required handling */ + DEV_ACTION_ERROR = (1 << 6), /**< Device encounters an error */ + DEV_ACTION_GONE = (1 << 7), /**< Device was gone */ + DEV_ACTION_RELEASE = (1 << 8), /**< Device was released */ + DEV_ACTION_FREE = (1 << 9), /**< Device should be freed */ +} dev_action_t; + +typedef struct ext_hub_s ext_hub_dev_t; + +/** + * @brief External Hub device configuration parameters for device allocation + */ +typedef struct { + usb_device_handle_t dev_hdl; /**< Device's handle */ + uint8_t dev_addr; /**< Device's bus address */ + const usb_intf_desc_t *iface_desc; /**< Device's Interface Descriptor pointer */ + const usb_ep_desc_t *ep_in_desc; /**< Device's IN Endpoint Descriptor pointer */ +} device_config_t; + +struct ext_hub_s { + struct { + TAILQ_ENTRY(ext_hub_s) tailq_entry; + union { + struct { + uint32_t in_pending_list: 1; /**< Device is in pending list */ + uint32_t waiting_free: 1; /**< Device waiting to be freed */ + uint32_t is_gone: 1; /**< Device is gone */ + uint32_t reserved29: 29; /**< Reserved */ + }; + uint32_t val; /**< Device's flags value */ + } flags; + uint32_t action_flags; /**< Device's action flags */ + ext_hub_state_t state; /**< Device's state */ + ext_hub_stage_t stage; /**< Device's stage */ + } dynamic; /**< Dynamic members require a critical section */ + + struct { + // For optimisation & debug only + uint8_t iface_num; /**< Device's bInterfaceNum */ + // Driver purpose + uint8_t dev_addr; /**< Device's bus address */ + usb_device_handle_t dev_hdl; /**< Device's handle */ + urb_t *ctrl_urb; /**< Device's Control pipe transfer URB */ + urb_t *in_urb; /**< Device's Interrupt pipe URB */ + usbh_ep_handle_t ep_in_hdl; /**< Device's Interrupt EP handle */ + + usb_hub_descriptor_t *hub_desc; /**< Device's Hub descriptor pointer. Could be NULL when not requested */ + uint8_t maxchild; /**< Number of ports. Could be 0 for some Hubs. */ + ext_port_hdl_t *ports; /**< Flexible array of Ports. Could be NULL, when maxchild is 0 */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +}; + +typedef struct { + struct { + TAILQ_HEAD(ext_hubs, ext_hub_s) ext_hubs_tailq; /**< Idle tailq */ + TAILQ_HEAD(ext_hubs_cb, ext_hub_s) ext_hubs_pending_tailq; /**< Pending tailq */ + } dynamic; /**< Dynamic members require a critical section */ + + struct { + ext_hub_cb_t proc_req_cb; /**< Process callback */ + void *proc_req_cb_arg; /**< Process callback argument */ + const ext_hub_port_driver_t* port_driver; /**< External Port Driver */ + } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ +} ext_hub_driver_t; + +static ext_hub_driver_t *p_ext_hub_driver = NULL; +static portMUX_TYPE ext_hub_driver_lock = portMUX_INITIALIZER_UNLOCKED; + +const char *EXT_HUB_TAG = "EXT_HUB"; + +// ----------------------------------------------------------------------------- +// ------------------------------- Helpers ------------------------------------- +// ----------------------------------------------------------------------------- + +#define EXT_HUB_ENTER_CRITICAL() portENTER_CRITICAL(&ext_hub_driver_lock) +#define EXT_HUB_EXIT_CRITICAL() portEXIT_CRITICAL(&ext_hub_driver_lock) +#define EXT_HUB_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&ext_hub_driver_lock) +#define EXT_HUB_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&ext_hub_driver_lock) + +#define EXT_HUB_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define EXT_HUB_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + EXT_HUB_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// ----------------------------------------------------------------------------- +// ----------------------- Forward declaration --------------------------------- +// ----------------------------------------------------------------------------- +static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags); +static void device_disable(ext_hub_dev_t *ext_hub_dev); +static void device_error(ext_hub_dev_t *ext_hub_dev); +static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length); + +// ----------------------------------------------------------------------------- +// ---------------------- Callbacks (implementation) --------------------------- +// ----------------------------------------------------------------------------- + +static bool interrupt_pipe_cb(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr) +{ + uint32_t action_flags; + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)user_arg; + + switch (ep_event) { + case USBH_EP_EVENT_URB_DONE: { + // A interrupt transfer completed on EP1's pipe . We need to dequeue it + action_flags = DEV_ACTION_EP1_DEQUEUE; + break; + case USBH_EP_EVENT_ERROR_XFER: + case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL: + case USBH_EP_EVENT_ERROR_OVERFLOW: + // EP1's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again + action_flags = DEV_ACTION_EP1_FLUSH | + DEV_ACTION_EP1_DEQUEUE | + DEV_ACTION_EP1_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr); + } else { + ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr); + } + break; + case USBH_EP_EVENT_ERROR_STALL: + // EP1's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again + action_flags = DEV_ACTION_EP1_DEQUEUE | DEV_ACTION_EP1_CLEAR; + if (in_isr) { + ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr); + } else { + ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr); + } + break; + } + default: + action_flags = 0; + break; + } + + EXT_HUB_ENTER_CRITICAL_SAFE(); + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, action_flags); + EXT_HUB_EXIT_CRITICAL_SAFE(); + + bool yield = false; + if (call_proc_req_cb) { + yield = p_ext_hub_driver->constant.proc_req_cb(in_isr, p_ext_hub_driver->constant.proc_req_cb_arg); + } + return yield; +} + +/** + * @brief Control transfer completion callback + * + * Is called by lower logic when transfer is completed with or without error + * + * @param[in] ctrl_xfer Pointer to a transfer buffer + */ +static void control_transfer_complete_cb(usb_transfer_t *ctrl_xfer) +{ + bool call_proc_req_cb = false; + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *) ctrl_xfer->context; + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_EP0_COMPLETE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void interrupt_transfer_complete_cb(usb_transfer_t *intr_xfer) +{ + assert(intr_xfer); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)intr_xfer->context; + assert(ext_hub_dev); + + switch (intr_xfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, intr_xfer->data_buffer, intr_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + device_status_change_handle(ext_hub_dev, intr_xfer->data_buffer, intr_xfer->actual_num_bytes); + break; + case USB_TRANSFER_STATUS_NO_DEVICE: + // Device was removed, nothing to do + break; + case USB_TRANSFER_STATUS_CANCELED: + // Cancellation due to USB Host uninstall routine + device_disable(ext_hub_dev); + break; + default: + // Any other error + ESP_LOGE(EXT_HUB_TAG, "[%d] Interrupt transfer failed, status %d", ext_hub_dev->constant.dev_addr, intr_xfer->status); + device_error(ext_hub_dev); + break; + } + +} + +// ----------------------------------------------------------------------------- +// --------------------------- Internal Logic --------------------------------- +// ----------------------------------------------------------------------------- + +static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags) +{ + /* + THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION + */ + if (action_flags == 0) { + return false; + } + bool call_proc_req_cb; + // Check if device is already on the callback list + if (!ext_hub_dev->dynamic.flags.in_pending_list) { + // Move device form idle device list to callback device list + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry); + ext_hub_dev->dynamic.action_flags |= action_flags; + ext_hub_dev->dynamic.flags.in_pending_list = 1; + call_proc_req_cb = true; + } else { + // The device is already on the callback list, thus a processing request is already pending. + ext_hub_dev->dynamic.action_flags |= action_flags; + call_proc_req_cb = false; + } + return call_proc_req_cb; +} + +static esp_err_t device_enable_int_ep(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret = ESP_OK; + ret = usbh_ep_enqueue_urb(ext_hub_dev->constant.ep_in_hdl, ext_hub_dev->constant.in_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit in urb (%#x)", ret); + return ret; + } + return ret; +} + +static void device_has_changed(ext_hub_dev_t *ext_hub_dev) +{ + // TODO: IDF-10053 Hub status change handling + // After getting the IRQ about Hub status change we need to request status + // device_get_status(ext_hub_dev); + ESP_LOGW(EXT_HUB_TAG, "Hub status change has not been implemented yet"); + device_enable_int_ep(ext_hub_dev); +} + +// Figure 11-22. Hub and Port Status Change Bitmap +static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length) +{ + uint8_t port_idx = 0; + uint8_t max_port_num = (sizeof(uint8_t) * 8) - 1; // Maximal Port number in one uint8_t byte + // Hub status change + if (data[0] & EXT_HUB_STATUS_CHANGE_FLAG) { + device_has_changed(ext_hub_dev); + } + // Ports status change + for (uint8_t i = 0; i < length; i++) { + for (uint8_t j = 0; j < max_port_num; j++) { + if (data[i] & (EXT_HUB_STATUS_PORT1_CHANGE_FLAG << j)) { + // Notify Hub driver + port_idx = (j + (i * max_port_num)); + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[port_idx]); + } + } + } + } +} + +static void device_disable(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device disable", ext_hub_dev->constant.dev_addr); + + if (ext_hub_dev->dynamic.state == EXT_HUB_STATE_RELEASED || ext_hub_dev->dynamic.flags.is_gone) { + ESP_LOGD(EXT_HUB_TAG, "Device in release state or already gone"); + return; + } + + // Mark all Ports are disable and then gone + for (uint8_t i = 0; i < ext_hub_dev->constant.maxchild; i++) { + if (p_ext_hub_driver->constant.port_driver) { + // TODO: IDF-10054 Hubs should disable their ports power + // Meanwhile, mark the port as gone + p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); + } + } + + // Close the device + ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl)); + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void device_error(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_ERROR); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void device_release(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device release", ext_hub_dev->constant.dev_addr); + + // Mark all Ports are disable and then gone + for (uint8_t i = 0; i < ext_hub_dev->constant.maxchild; i++) { + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); + } + } + + // Release IN EP + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); + + // Close the device + ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl)); + + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static esp_err_t device_alloc_desc(ext_hub_dev_t *ext_hub_hdl, const usb_hub_descriptor_t *hub_desc) +{ + // Allocate memory to store the configuration descriptor + usb_hub_descriptor_t *desc = heap_caps_malloc(hub_desc->bDescLength, MALLOC_CAP_DEFAULT); // Buffer to copy over full configuration descriptor (wTotalLength) + if (desc == NULL) { + return ESP_ERR_NO_MEM; + } + // Copy the hub descriptor + memcpy(desc, hub_desc, hub_desc->bDescLength); + // Assign the hub descriptor to the device object + assert(ext_hub_hdl->constant.hub_desc == NULL); + ext_hub_hdl->constant.hub_desc = desc; + return ESP_OK; +} + +static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_dev) +{ + esp_err_t ret; + urb_t *ctrl_urb = NULL; + urb_t *in_urb = NULL; + +#if !ENABLE_MULTIPLE_HUBS + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usbh_dev_get_info(config->dev_hdl, &dev_info)); + if (dev_info.parent.dev_hdl) { + ESP_LOGW(EXT_HUB_TAG, "Multiple Hubs not supported, use menuconfig to enable feature"); + ret = ESP_ERR_NOT_SUPPORTED; + goto fail; + } +#endif // ENABLE_MULTIPLE_HUBS + + ext_hub_dev_t *hub_dev = heap_caps_calloc(1, sizeof(ext_hub_dev_t), MALLOC_CAP_DEFAULT); + + if (hub_dev == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate device"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + // Allocate Control transfer URB + ctrl_urb = urb_alloc(sizeof(usb_setup_packet_t) + EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN, 0); + if (ctrl_urb == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Control URB"); + ret = ESP_ERR_NO_MEM; + goto ctrl_urb_fail; + } + + in_urb = urb_alloc(config->ep_in_desc->wMaxPacketSize, 0); + // Allocate Interrupt transfer URB + if (in_urb == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Interrupt URB"); + ret = ESP_ERR_NO_MEM; + goto in_urb_fail; + } + + usbh_ep_handle_t ep_hdl; + usbh_ep_config_t ep_config = { + .bInterfaceNumber = config->iface_desc->bInterfaceNumber, + .bAlternateSetting = config->iface_desc->bAlternateSetting, + .bEndpointAddress = config->ep_in_desc->bEndpointAddress, + .ep_cb = interrupt_pipe_cb, + .ep_cb_arg = (void *)hub_dev, + .context = (void *)hub_dev, + }; + + ret = usbh_ep_alloc(config->dev_hdl, &ep_config, &ep_hdl); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Endpoint allocation failure (%#x)", ret); + goto ep_fail; + } + // Configure Control transfer URB + ctrl_urb->usb_host_client = (void *) p_ext_hub_driver; + ctrl_urb->transfer.callback = control_transfer_complete_cb; + ctrl_urb->transfer.context = (void *) hub_dev; + + // Client is a memory address of the p_ext_hub_driver driver object + in_urb->usb_host_client = (void *) p_ext_hub_driver; + in_urb->transfer.callback = interrupt_transfer_complete_cb; + in_urb->transfer.context = (void *) hub_dev; + in_urb->transfer.num_bytes = config->ep_in_desc->wMaxPacketSize; + + // Save constant parameters + hub_dev->constant.ep_in_hdl = ep_hdl; + hub_dev->constant.ctrl_urb = ctrl_urb; + hub_dev->constant.in_urb = in_urb; + hub_dev->constant.dev_hdl = config->dev_hdl; + hub_dev->constant.dev_addr = config->dev_addr; + hub_dev->constant.iface_num = config->iface_desc->bInterfaceNumber; + // We will update number of ports during Hub Descriptor handling stage + hub_dev->constant.maxchild = 0; + + hub_dev->dynamic.flags.val = 0; + hub_dev->dynamic.state = EXT_HUB_STATE_ATTACHED; + hub_dev->dynamic.stage = EXT_HUB_STAGE_IDLE; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, hub_dev, dynamic.tailq_entry); + EXT_HUB_EXIT_CRITICAL(); + + ESP_LOGD(EXT_HUB_TAG, "[%d] New device (iface %d)", config->dev_addr, hub_dev->constant.iface_num); + + *ext_hub_dev = hub_dev; + return ret; + +ep_fail: + urb_free(in_urb); +in_urb_fail: + urb_free(ctrl_urb); +ctrl_urb_fail: + heap_caps_free(hub_dev); +fail: + return ret; +} + +static esp_err_t device_configure(ext_hub_dev_t *ext_hub_dev) +{ + EXT_HUB_CHECK(ext_hub_dev->constant.hub_desc != NULL, ESP_ERR_INVALID_STATE); + usb_hub_descriptor_t *hub_desc = ext_hub_dev->constant.hub_desc; + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device configure (iface %d)", + ext_hub_dev->constant.dev_addr, + ext_hub_dev->constant.iface_num); + + if (hub_desc->wHubCharacteristics.compound) { + ESP_LOGD(EXT_HUB_TAG, "\tCompound device"); + } else { + ESP_LOGD(EXT_HUB_TAG, "\tStandalone HUB"); + } + + ESP_LOGD(EXT_HUB_TAG, "\t%d external port%s", + ext_hub_dev->constant.hub_desc->bNbrPorts, + (ext_hub_dev->constant.hub_desc->bNbrPorts == 1) ? "" : "s"); + + switch (hub_desc->wHubCharacteristics.power_switching) { + case USB_W_HUB_CHARS_PORT_PWR_CTRL_NO: + ESP_LOGD(EXT_HUB_TAG, "\tNo power switching (usb 1.0)"); + break; + case USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV: + ESP_LOGD(EXT_HUB_TAG, "\tIndividual port power switching"); + break; + default: + // USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL + ESP_LOGD(EXT_HUB_TAG, "\tAll ports power at once"); + break; + } + + switch (hub_desc->wHubCharacteristics.ovr_current_protect) { + case USB_W_HUB_CHARS_PORT_OVER_CURR_NO: + ESP_LOGD(EXT_HUB_TAG, "\tNo over-current protection"); + break; + case USB_W_HUB_CHARS_PORT_OVER_CURR_INDV: + ESP_LOGD(EXT_HUB_TAG, "\tIndividual port over-current protection"); + break; + default: + // USB_W_HUB_CHARS_PORT_OVER_CURR_ALL + ESP_LOGD(EXT_HUB_TAG, "\tGlobal over-current protection"); + break; + } + + if (hub_desc->wHubCharacteristics.indicator_support) { + ESP_LOGD(EXT_HUB_TAG, "\tPort indicators are supported"); + } + + ESP_LOGD(EXT_HUB_TAG, "\tPower on to power good time: %dms", hub_desc->bPwrOn2PwrGood * 2); + ESP_LOGD(EXT_HUB_TAG, "\tMaximum current: %d mA", hub_desc->bHubContrCurrent); + + // Create External Port flexible array + ext_hub_dev->constant.ports = heap_caps_calloc(ext_hub_dev->constant.hub_desc->bNbrPorts, sizeof(ext_port_hdl_t), MALLOC_CAP_DEFAULT); + if (ext_hub_dev->constant.ports == NULL) { + ESP_LOGE(EXT_HUB_TAG, "Ports allocation err"); + return ESP_ERR_NO_MEM; + } + + // Update device port amount + ext_hub_dev->constant.maxchild = ext_hub_dev->constant.hub_desc->bNbrPorts; + + // Create port and add it to pending list + for (uint8_t i = 0; i < ext_hub_dev->constant.maxchild; i++) { + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->new (NULL, (void**) &ext_hub_dev->constant.ports[i]); + } + } + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.state = EXT_HUB_STATE_CONFIGURED; + EXT_HUB_EXIT_CRITICAL(); + + return ESP_OK; +} + +static void device_free(ext_hub_dev_t *ext_hub_dev) +{ + ESP_LOGD(EXT_HUB_TAG, "[%d] Freeing device", ext_hub_dev->constant.dev_addr); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.flags.waiting_free = 0; + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + EXT_HUB_EXIT_CRITICAL(); + + // Free ports + for (uint8_t i = 0; i < ext_hub_dev->constant.maxchild; i++) { + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->free(ext_hub_dev->constant.ports[i]); + } + } + + if (ext_hub_dev->constant.hub_desc) { + heap_caps_free(ext_hub_dev->constant.hub_desc); + } + if (ext_hub_dev->constant.ports) { + heap_caps_free(ext_hub_dev->constant.ports); + } + + ESP_ERROR_CHECK(usbh_ep_free(ext_hub_dev->constant.ep_in_hdl)); + urb_free(ext_hub_dev->constant.ctrl_urb); + urb_free(ext_hub_dev->constant.in_urb); + + heap_caps_free(ext_hub_dev); +} + +static esp_err_t get_dev_by_hdl(usb_device_handle_t dev_hdl, ext_hub_dev_t **ext_hub_hdl) +{ + esp_err_t ret = ESP_OK; + // Go through the Hubs lists to find the hub with the specified device address + ext_hub_dev_t *found_hub = NULL; + ext_hub_dev_t *hub = NULL; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_hdl == dev_hdl) { + found_hub = hub; + goto exit; + } + } + + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_hdl == dev_hdl) { + found_hub = hub; + goto exit; + } + } + +exit: + if (found_hub == NULL) { + ret = ESP_ERR_NOT_FOUND; + } + EXT_HUB_EXIT_CRITICAL(); + + *ext_hub_hdl = found_hub; + return ret; +} + +static esp_err_t get_dev_by_addr(uint8_t dev_addr, ext_hub_dev_t **ext_hub_hdl) +{ + esp_err_t ret = ESP_OK; + // Go through the Hubs lists to find the Hub with the specified device address + ext_hub_dev_t *found_hub = NULL; + ext_hub_dev_t *hub = NULL; + + EXT_HUB_ENTER_CRITICAL(); + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_addr == dev_addr) { + found_hub = hub; + goto exit; + } + } + + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) { + if (hub->constant.dev_addr == dev_addr) { + found_hub = hub; + goto exit; + } + } + +exit: + if (found_hub == NULL) { + ret = ESP_ERR_NOT_FOUND; + } + EXT_HUB_EXIT_CRITICAL(); + + *ext_hub_hdl = found_hub; + return ret; +} + +// ----------------------------------------------------------------------------- +// -------------------------- Device handling --------------------------------- +// ----------------------------------------------------------------------------- + +static bool handle_hub_descriptor(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + bool pass; + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_hub_descriptor_t *hub_desc = (const usb_hub_descriptor_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(EXT_HUB_TAG, "Bad transfer status %d: stage=%d", ctrl_xfer->status, ext_hub_dev->dynamic.stage); + return false; + } + + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + + ret = device_alloc_desc(ext_hub_dev, hub_desc); + if (ret != ESP_OK) { + pass = false; + goto exit; + } + + ret = device_configure(ext_hub_dev); + if (ret != ESP_OK) { + pass = false; + goto exit; + } + + pass = true; +exit: + return pass; +} + +static bool handle_device_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_device_status_t *dev_status = (const usb_device_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + ESP_LOGD(EXT_HUB_TAG, "[%d] Device status: ", ext_hub_dev->constant.dev_addr); + ESP_LOGD(EXT_HUB_TAG, "\tPower: %s", dev_status->self_powered ? "self-powered" : "bus-powered"); + ESP_LOGD(EXT_HUB_TAG, "\tRemoteWakeup: %s", dev_status->remote_wakeup ? "yes" : "no"); + + if (dev_status->remote_wakeup) { + // Device in remote_wakeup, we need send command Clear Device Feature: USB_W_VALUE_FEATURE_DEVICE_REMOTE_WAKEUP + // HEX codes of command: 00 01 01 00 00 00 00 00 + // TODO: IDF-10055 Hub Support remote_wakeup feature + ESP_LOGW(EXT_HUB_TAG, "Remote Wakeup feature has not been implemented yet"); + } + return true; +} + +static bool handle_hub_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + const usb_hub_status_t *hub_status = (const usb_hub_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + ESP_LOGD(EXT_HUB_TAG, "[%d] Hub status: ", ext_hub_dev->constant.dev_addr); + ESP_LOGD(EXT_HUB_TAG, "\tExternal power supply: %s", hub_status->wHubStatus.HUB_LOCAL_POWER ? "yes" : "no"); + ESP_LOGD(EXT_HUB_TAG, "\tOvercurrent: %s", hub_status->wHubStatus.HUB_OVER_CURRENT ? "yes" : "no"); + + if (hub_status->wHubStatus.HUB_OVER_CURRENT) { + ESP_LOGE(EXT_HUB_TAG, "Device has overcurrent!"); + // Hub has an overcurrent, we need to disable all port and/or disable parent port + // TODO: IDF-10056 Hubs overcurrent handling + ESP_LOGW(EXT_HUB_TAG, "Feature has not been implemented yet"); + return false; + } + + return true; +} + +static bool device_control_request(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + switch (ext_hub_dev->dynamic.stage) { + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + USB_SETUP_PACKET_INIT_GET_STATUS((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_status_t); + break; + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_descriptor_t); + break; + case EXT_HUB_STAGE_GET_HUB_STATUS: + USB_SETUP_PACKET_INIT_GET_HUB_STATUS((usb_setup_packet_t *)transfer->data_buffer); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_status_t); + break; + default: + // Should never occur + abort(); + break; + } + + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb, error %#x", ret); + return false; + } + + return true; +} + +static bool device_control_response_handling(ext_hub_dev_t *ext_hub_dev) +{ + bool stage_pass = false; + + switch (ext_hub_dev->dynamic.stage) { + case EXT_HUB_STAGE_CHECK_DEVICE_STATUS: + stage_pass = handle_device_status(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR: + stage_pass = handle_hub_descriptor(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_HUB_STATUS: + stage_pass = handle_hub_status(ext_hub_dev); + break; + default: + // Should never occur + abort(); + break; + } + + return stage_pass; +} + +static bool stage_need_process(ext_hub_stage_t stage) +{ + bool need_process_cb = false; + + switch (stage) { + // Stages, required control transfer + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + case EXT_HUB_STAGE_GET_HUB_STATUS: + // Error stage + case EXT_HUB_STAGE_FAILURE: + need_process_cb = true; + break; + default: + break; + } + + return need_process_cb; +} + +// return +// true - next stage requires the processing +// false - terminal stage +static bool device_set_next_stage(ext_hub_dev_t *ext_hub_dev, bool last_stage_pass) +{ + bool need_process_cb; + ext_hub_stage_t last_stage = ext_hub_dev->dynamic.stage; + ext_hub_stage_t next_stage; + + if (last_stage_pass) { + ESP_LOGD(EXT_HUB_TAG, "Stage %s OK", ext_hub_stage_strings[last_stage]); + if (last_stage == EXT_HUB_STAGE_GET_DEVICE_STATUS || + last_stage == EXT_HUB_STAGE_GET_HUB_DESCRIPTOR || + last_stage == EXT_HUB_STAGE_GET_HUB_STATUS) { + // Simply increment to get the next stage + next_stage = last_stage + 1; + } else { + // Terminal stages, move to IDLE + next_stage = EXT_HUB_STAGE_IDLE; + } + } else { + ESP_LOGE(EXT_HUB_TAG, "Stage %s FAILED", ext_hub_stage_strings[last_stage]); + // These stages cannot fail + assert(last_stage != EXT_HUB_STAGE_PORT_FEATURE || + last_stage != EXT_HUB_STAGE_PORT_STATUS_REQUEST); + + next_stage = EXT_HUB_STAGE_FAILURE; + } + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = next_stage; + need_process_cb = stage_need_process(next_stage); + EXT_HUB_EXIT_CRITICAL(); + + return need_process_cb; +} + +static void handle_port_feature(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer); + uint8_t port_idx = port_num - 1; + + assert(port_idx < ext_hub_dev->constant.maxchild); + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[port_idx]); + } +} + +static void handle_port_status(ext_hub_dev_t *ext_hub_dev) +{ + usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer; + uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer); + uint8_t port_idx = port_num - 1; + const usb_port_status_t *new_status = (const usb_port_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t)); + + assert(port_idx < ext_hub_dev->constant.maxchild); + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->set_status(ext_hub_dev->constant.ports[port_idx], new_status); + } +} + +static void handle_device(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb; + bool stage_pass = false; + // FSM for external Hub + switch (ext_hub_dev->dynamic.stage) { + case EXT_HUB_STAGE_IDLE: + break; + case EXT_HUB_STAGE_GET_DEVICE_STATUS: + case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: + case EXT_HUB_STAGE_GET_HUB_STATUS: + stage_pass = device_control_request(ext_hub_dev); + break; + case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR: + stage_pass = device_control_response_handling(ext_hub_dev); + break; + case EXT_HUB_STAGE_PORT_FEATURE: + handle_port_feature(ext_hub_dev); + stage_pass = true; + break; + case EXT_HUB_STAGE_PORT_STATUS_REQUEST: + handle_port_status(ext_hub_dev); + stage_pass = true; + break; + case EXT_HUB_STAGE_FAILURE: + ESP_LOGW(EXT_HUB_TAG, "External Hub device failure handling has not been implemented yet"); + // device_error(ext_hub_dev); + break; + default: + // Should never occur + abort(); + break; + } + + call_proc_req_cb = device_set_next_stage(ext_hub_dev, stage_pass); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +static void handle_ep1_flush(ext_hub_dev_t *ext_hub_dev) +{ + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_FLUSH)); +} + +static void handle_ep1_dequeue(ext_hub_dev_t *ext_hub_dev) +{ + // Dequeue all URBs and run their transfer callback + ESP_LOGD(EXT_HUB_TAG, "[%d] Interrupt dequeue", ext_hub_dev->constant.dev_addr); + urb_t *urb; + usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_hdl, &urb); + while (urb != NULL) { + // Clear the transfer's in-flight flag to indicate the transfer is no longer in-flight + urb->usb_host_inflight = false; + urb->transfer.callback(&urb->transfer); + usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_hdl, &urb); + } +} + +static void handle_ep1_clear(ext_hub_dev_t *ext_hub_dev) +{ + // We allow the pipe command to fail just in case the pipe becomes invalid mid command + usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_CLEAR); +} + +static void handle_error(ext_hub_dev_t *ext_hub_dev) +{ + // TODO: IDF-10057 Hub handling error + ESP_LOGW(EXT_HUB_TAG, "%s has not been implemented yet", __FUNCTION__); + device_disable(ext_hub_dev); +} + +static void handle_gone(ext_hub_dev_t *ext_hub_dev) +{ + bool call_proc_req_cb = false; + + // Set the flags + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.flags.waiting_free = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } +} + +// ----------------------------------------------------------------------------- +// ------------------------------ Driver --------------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_install(const ext_hub_config_t *config) +{ + esp_err_t ret; + ext_hub_driver_t *ext_hub_drv = heap_caps_calloc(1, sizeof(ext_hub_driver_t), MALLOC_CAP_DEFAULT); + EXT_HUB_CHECK(ext_hub_drv != NULL, ESP_ERR_NO_MEM); + + // Save callbacks + ext_hub_drv->constant.proc_req_cb = config->proc_req_cb; + ext_hub_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg; + // Copy Port driver pointer + ext_hub_drv->constant.port_driver = config->port_driver; + + if (ext_hub_drv->constant.port_driver == NULL) { + ESP_LOGW(EXT_HUB_TAG, "Port Driver has not been installed"); + } + + TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_tailq); + TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_pending_tailq); + + EXT_HUB_ENTER_CRITICAL(); + if (p_ext_hub_driver != NULL) { + EXT_HUB_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto fail; + } + p_ext_hub_driver = ext_hub_drv; + EXT_HUB_EXIT_CRITICAL(); + + ESP_LOGD(EXT_HUB_TAG, "Driver installed"); + return ESP_OK; + +fail: + heap_caps_free(ext_hub_drv); + return ret; +} + +esp_err_t ext_hub_uninstall(void) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_tailq), ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq), ESP_ERR_INVALID_STATE); + ext_hub_driver_t *ext_hub_drv = p_ext_hub_driver; + p_ext_hub_driver = NULL; + EXT_HUB_EXIT_CRITICAL(); + + heap_caps_free(ext_hub_drv); + ESP_LOGD(EXT_HUB_TAG, "Driver uninstalled"); + return ESP_OK; +} + +void *ext_hub_get_client(void) +{ + bool driver_installed = false; + EXT_HUB_ENTER_CRITICAL(); + driver_installed = (p_ext_hub_driver != NULL); + EXT_HUB_EXIT_CRITICAL(); + return (driver_installed) ? (void*) p_ext_hub_driver : NULL; +} + +// ----------------------------------------------------------------------------- +// -------------------------- External Hub API --------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + return get_dev_by_hdl(dev_hdl, ext_hub_hdl); +} + +static esp_err_t find_first_intf_desc(const usb_config_desc_t *config_desc, device_config_t *hub_config) +{ + bool iface_found = false; + const usb_ep_desc_t *ep_in_desc = NULL; + + int offset = 0; + const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type( + (const usb_standard_desc_t *)config_desc, + config_desc->wTotalLength, + USB_B_DESCRIPTOR_TYPE_INTERFACE, + &offset); + + while (next_intf_desc != NULL) { + if (iface_found) { + // TODO: IDF-10058 Hubs support interface selection (HS) + ESP_LOGW(EXT_HUB_TAG, "Device has several Interfaces, selection has not been implemented yet. Using first."); + break; + } + // Parse all interfaces + if (USB_CLASS_HUB == next_intf_desc->bInterfaceClass) { + // We have found the first interface descriptor with matching bInterfaceNumber +#if (OTG_HSPHY_INTERFACE != 0) + // TODO: IDF-10059 Hubs support multiple TT (HS) + if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_HS_NO_TT) { + ESP_LOGD(EXT_HUB_TAG, "Transaction Translator:"); + ESP_LOGW(EXT_HUB_TAG, "Transaction Translator has not been implemented yet"); + } + + switch (next_intf_desc->bInterfaceProtocol) { + case USB_B_DEV_PROTOCOL_HUB_HS_NO_TT: + ESP_LOGD(EXT_HUB_TAG, "\tNo TT"); + break; + case USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT: + ESP_LOGD(EXT_HUB_TAG, "\tSingle TT"); + break; + case USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT: + ESP_LOGD(EXT_HUB_TAG, "\tMulti TT"); + break; + default: + ESP_LOGE(EXT_HUB_TAG, "\tInterface Protocol (%#x) not supported", next_intf_desc->bInterfaceProtocol); + goto next_iface; + } +#else + if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_FS) { + ESP_LOGE(EXT_HUB_TAG, "\tProtocol (%#x) not supported", next_intf_desc->bInterfaceProtocol); + goto next_iface; + } +#endif // OTG_HSPHY_INTERFACE != 0 + + // Hub Interface always should have only one Interrupt endpoint + if (next_intf_desc->bNumEndpoints != 1) { + ESP_LOGE(EXT_HUB_TAG, "Unexpected number of endpoints (%d)", next_intf_desc->bNumEndpoints); + goto next_iface; + } + // Get related IN EP + ep_in_desc = usb_parse_endpoint_descriptor_by_index(next_intf_desc, 0, config_desc->wTotalLength, &offset); + if (ep_in_desc == NULL) { + ESP_LOGE(EXT_HUB_TAG, "EP descriptor not found (iface=%d)", next_intf_desc->bInterfaceNumber); + goto next_iface; + } + + if (!USB_EP_DESC_GET_EP_DIR(ep_in_desc) || + (USB_EP_DESC_GET_XFERTYPE(ep_in_desc) != USB_TRANSFER_TYPE_INTR)) { + ESP_LOGE(EXT_HUB_TAG, "Interrupt EP not found (iface=%d)", next_intf_desc->bInterfaceNumber); + goto next_iface; + } + + // Interface found, fill the config + hub_config->iface_desc = next_intf_desc; + hub_config->ep_in_desc = ep_in_desc; + iface_found = true; + } + +next_iface: + next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type( + (const usb_standard_desc_t *)next_intf_desc, + config_desc->wTotalLength, + USB_B_DESCRIPTOR_TYPE_INTERFACE, + &offset); + } + + return (iface_found) ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t ext_hub_new_dev(uint8_t dev_addr) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + esp_err_t ret; + ext_hub_dev_t *hub_dev = NULL; + usb_device_handle_t dev_hdl = NULL; + const usb_config_desc_t *config_desc = NULL; + bool call_proc_req_cb = false; + + // Open device + ret = usbh_devs_open(dev_addr, &dev_hdl); + if (ret != ESP_OK) { + return ret; + } + + // Get Configuration Descriptor + ret = usbh_dev_get_config_desc(dev_hdl, &config_desc); + if (ret != ESP_OK) { + goto exit; + } + + // Find related Hub Interface descriptor + device_config_t hub_config = { + .dev_hdl = dev_hdl, + .dev_addr = dev_addr, + .iface_desc = NULL, + .ep_in_desc = NULL, + }; + + ret = find_first_intf_desc(config_desc, &hub_config); + if (ret != ESP_OK) { + goto exit; + } + + // Create External Hub device + ret = device_alloc(&hub_config, &hub_dev); + if (ret != ESP_OK) { + goto exit; + } + + EXT_HUB_ENTER_CRITICAL(); + hub_dev->dynamic.stage = EXT_HUB_STAGE_GET_HUB_DESCRIPTOR; + call_proc_req_cb = _device_set_actions(hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ret; + +exit: + ESP_ERROR_CHECK(usbh_dev_close(dev_hdl)); + return ret; +} + +esp_err_t ext_hub_dev_gone(uint8_t dev_addr) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + esp_err_t ret; + + ext_hub_dev_t *ext_hub_dev = NULL; + bool call_proc_req_cb = false; + + EXT_HUB_CHECK(dev_addr != 0, ESP_ERR_INVALID_ARG); + // Find device with dev_addr in the devices TAILQ + // TODO: IDF-10058 + // Release all devices by dev_addr + ret = get_dev_by_addr(dev_addr, &ext_hub_dev); + if (ret != ESP_OK) { + ESP_LOGD(EXT_HUB_TAG, "No device with address %d was found", dev_addr); + return ret; + } + + ESP_LOGE(EXT_HUB_TAG, "[%d] Device gone", ext_hub_dev->constant.dev_addr); + + for (uint8_t i = 0; i < ext_hub_dev->constant.maxchild; i++) { + if (p_ext_hub_driver->constant.port_driver) { + p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); + } + } + + // Close the device + ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl)); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.flags.is_gone = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_GONE); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + return ret; +} + +esp_err_t ext_hub_all_free(void) +{ + ext_hub_dev_t *hub = NULL; + bool call_proc_req_cb = false; + + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) { + hub->dynamic.flags.waiting_free = 1; + _device_set_actions(hub, DEV_ACTION_RELEASE); + hub->dynamic.state = EXT_HUB_STATE_RELEASED; + call_proc_req_cb = true; + } + TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) { + hub->dynamic.flags.waiting_free = 1; + hub->dynamic.state = EXT_HUB_STATE_RELEASED; + _device_set_actions(hub, DEV_ACTION_RELEASE); + call_proc_req_cb = true; + } + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + EXT_HUB_CHECK(ext_hub_dev->dynamic.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + + ESP_LOGD(EXT_HUB_TAG, "[%d] Status handle complete, wait status change ...", ext_hub_hdl->constant.dev_addr); + + return device_enable_int_ep(ext_hub_dev); +} + +esp_err_t ext_hub_process(void) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + // Keep processing until all device's with pending events have been handled + while (!TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq)) { + // Move the device back into the idle device list, + ext_hub_dev_t *ext_hub_dev = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq); + TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry); + TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry); + // Clear the device's flags + uint32_t action_flags = ext_hub_dev->dynamic.action_flags; + ext_hub_dev->dynamic.action_flags = 0; + ext_hub_dev->dynamic.flags.in_pending_list = 0; + + /* --------------------------------------------------------------------- + Exit critical section to handle device action flags in their listed order + --------------------------------------------------------------------- */ + EXT_HUB_EXIT_CRITICAL(); + ESP_LOGD(EXT_HUB_TAG, "[%d] Processing actions 0x%"PRIx32"", ext_hub_dev->constant.dev_addr, action_flags); + + if (action_flags & DEV_ACTION_REQ || + action_flags & DEV_ACTION_EP0_COMPLETE) { + handle_device(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_FLUSH) { + handle_ep1_flush(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_DEQUEUE) { + handle_ep1_dequeue(ext_hub_dev); + } + if (action_flags & DEV_ACTION_EP1_CLEAR) { + handle_ep1_clear(ext_hub_dev); + } + if (action_flags & DEV_ACTION_ERROR) { + handle_error(ext_hub_dev); + } + if (action_flags & DEV_ACTION_GONE) { + handle_gone(ext_hub_dev); + } + if (action_flags & DEV_ACTION_RELEASE) { + device_release(ext_hub_dev); + } + if (action_flags & DEV_ACTION_FREE) { + device_free(ext_hub_dev); + } + EXT_HUB_ENTER_CRITICAL(); + /* --------------------------------------------------------------------- + Re-enter critical sections. All device action flags should have been handled. + --------------------------------------------------------------------- */ + + } + EXT_HUB_EXIT_CRITICAL(); + + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// --------------------- External Hub - Device related ------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_get_hub_status(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = EXT_HUB_STAGE_GET_HUB_STATUS; + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +esp_err_t ext_hub_get_status(ext_hub_handle_t ext_hub_hdl) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = EXT_HUB_STAGE_GET_DEVICE_STATUS; + bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ); + EXT_HUB_EXIT_CRITICAL(); + + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); + } + + return ESP_OK; +} + +// ----------------------------------------------------------------------------- +// --------------------- External Hub - Port related --------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + if (p_ext_hub_driver->constant.port_driver) { + ret = p_ext_hub_driver->constant.port_driver->recycle(ext_hub_dev->constant.ports[port_idx]); + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + if (p_ext_hub_driver->constant.port_driver) { + ret = p_ext_hub_driver->constant.port_driver->reset(ext_hub_dev->constant.ports[port_idx]); + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + if (p_ext_hub_driver->constant.port_driver) { + ret = p_ext_hub_driver->constant.port_driver->active(ext_hub_dev->constant.ports[port_idx]); + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + if (p_ext_hub_driver->constant.port_driver) { + ret = p_ext_hub_driver->constant.port_driver->disable(ext_hub_dev->constant.ports[port_idx]); + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, usb_speed_t *speed) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + uint8_t port_idx = port_num - 1; + EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + if (p_ext_hub_driver->constant.port_driver) { + ret = p_ext_hub_driver->constant.port_driver->get_speed(ext_hub_dev->constant.ports[port_idx], speed); + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +// ----------------------------------------------------------------------------- +// --------------------------- USB Chapter 11 ---------------------------------- +// ----------------------------------------------------------------------------- + +esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + + USB_SETUP_PACKET_INIT_SET_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = EXT_HUB_STAGE_PORT_FEATURE; + EXT_HUB_EXIT_CRITICAL(); + + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb, error %#x", ret); + } + return ret; +} + +esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + + USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = EXT_HUB_STAGE_PORT_FEATURE; + EXT_HUB_EXIT_CRITICAL(); + + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb, error %#x", ret); + } + return ret; +} + +esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) +{ + EXT_HUB_ENTER_CRITICAL(); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE); + EXT_HUB_EXIT_CRITICAL(); + + esp_err_t ret; + EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl; + usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer; + + EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.maxchild, ESP_ERR_INVALID_SIZE); + + USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, port_num); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_port_status_t); + + EXT_HUB_ENTER_CRITICAL(); + ext_hub_dev->dynamic.stage = EXT_HUB_STAGE_PORT_STATUS_REQUEST; + EXT_HUB_EXIT_CRITICAL(); + + ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb); + if (ret != ESP_OK) { + ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb, error %#x", ret); + } + return ret; +} diff --git a/components/usb/hub.c b/components/usb/hub.c index 4ee318290e..a11947952f 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -19,6 +19,10 @@ #include "hub.h" #include "usb/usb_helpers.h" +#if ENABLE_USB_HUBS +#include "ext_hub.h" +#endif // ENABLE_USB_HUBS + /* Implementation of the HUB driver that only supports the Root Hub with a single port. Therefore, we currently don't implement the bare minimum to control the root HCD port. @@ -35,13 +39,21 @@ implement the bare minimum to control the root HCD port. #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED #endif -// 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 PORT_REQ_DISABLE 0x01 #define PORT_REQ_RECOVER 0x02 +/** + * @brief Hub driver action flags + */ +typedef enum { + HUB_DRIVER_ACTION_ROOT_EVENT = (1 << 0), + HUB_DRIVER_ACTION_ROOT_REQ = (1 << 1), +#if ENABLE_USB_HUBS + HUB_DRIVER_ACTION_EXT_HUB = (1 << 6), + HUB_DRIVER_ACTION_EXT_PORT = (1 << 7) +#endif // ENABLE_USB_HUBS +} hub_flag_action_t; + /** * @brief Root port states */ @@ -57,7 +69,6 @@ typedef enum { * @brief Hub device tree node * * Object type of a node in the USB device tree that is maintained by the Hub driver - * */ struct dev_tree_node_s { TAILQ_ENTRY(dev_tree_node_s) tailq_entry; /**< Entry for the device tree node object tailq */ @@ -72,11 +83,11 @@ typedef struct { struct { union { struct { - uint32_t actions: 8; - uint32_t reserved24: 24; + hub_flag_action_t actions: 8; /**< Hub actions */ + uint32_t reserved24: 24; /**< Reserved */ }; - uint32_t val; /**< Root port flag value */ - } flags; /**< Root port flags */ + uint32_t val; /**< Hub flag action value */ + } flags; /**< Hub flags */ root_port_state_t root_port_state; /**< Root port state */ unsigned int port_reqs; /**< Root port request flag */ } dynamic; /**< Dynamic members. Require a critical section */ @@ -145,7 +156,7 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port */ static esp_err_t new_dev_tree_node(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed) { - esp_err_t ret = ESP_FAIL; + esp_err_t ret; unsigned int node_uid = p_hub_driver_obj->single_thread.next_uid; dev_tree_node_t *dev_tree_node = heap_caps_calloc(1, sizeof(dev_tree_node_t), MALLOC_CAP_DEFAULT); @@ -165,6 +176,8 @@ static esp_err_t new_dev_tree_node(usb_device_handle_t parent_dev_hdl, uint8_t p ret = usbh_devs_add(¶ms); if (ret != ESP_OK) { + // USBH devs add could failed due to lack of free hcd channels + // TODO: IDF-10044 Hub should recover after running out of hcd channels goto fail; } @@ -293,12 +306,22 @@ static esp_err_t dev_tree_node_remove_by_parent(usb_device_handle_t parent_dev_h static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr) { HUB_DRIVER_ENTER_CRITICAL_SAFE(); - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); assert(in_isr); // Currently, this callback should only ever be called from an ISR context return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); } +#ifdef ENABLE_USB_HUBS +static bool ext_hub_callback(bool in_isr, void *user_arg) +{ + HUB_DRIVER_ENTER_CRITICAL_SAFE(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_HUB; + HUB_DRIVER_EXIT_CRITICAL_SAFE(); + return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg); +} +#endif // ENABLE_USB_HUBS + // ---------------------- Handlers ------------------------- static void root_port_handle_events(hcd_port_handle_t root_port_hdl) { @@ -344,7 +367,7 @@ reset_err: case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled // Therefore, there's no device object to clean up, and we can go straight to port recovery p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER; - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ; break; case ROOT_PORT_STATE_ENABLED: // There is an enabled (active) device. We need to indicate to USBH that the device is gone @@ -409,7 +432,7 @@ static esp_err_t root_port_recycle(void) abort(); // Should never occur break; } - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ; + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ; HUB_DRIVER_EXIT_CRITICAL(); ESP_ERROR_CHECK(dev_tree_node_remove_by_parent(NULL, 0)); @@ -434,7 +457,20 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) return ESP_ERR_NO_MEM; } +#if ENABLE_USB_HUBS + // Install External HUB driver + ext_hub_config_t ext_hub_config = { + .proc_req_cb = ext_hub_callback, + .port_driver = NULL, + }; + ret = ext_hub_install(&ext_hub_config); + if (ret != ESP_OK) { + goto err_ext_hub; + } + *client_ret = ext_hub_get_client(); +#else *client_ret = NULL; +#endif // ENABLE_USB_HUBS // Install HCD port hcd_port_config_t port_config = { @@ -474,6 +510,10 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) assign_err: ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl)); err: +#if ENABLE_USB_HUBS + ext_hub_uninstall(); +err_ext_hub: +#endif // ENABLE_USB_HUBS heap_caps_free(hub_driver_obj); return ret; } @@ -487,6 +527,10 @@ esp_err_t hub_uninstall(void) p_hub_driver_obj = NULL; HUB_DRIVER_EXIT_CRITICAL(); +#if ENABLE_USB_HUBS + ESP_ERROR_CHECK(ext_hub_uninstall()); +#endif // ENABLE_USB_HUBS + ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl)); // Free Hub driver resources heap_caps_free(hub_driver_obj); @@ -531,14 +575,19 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); HUB_DRIVER_EXIT_CRITICAL(); - - esp_err_t ret = ESP_FAIL; + esp_err_t ret; if (parent_port_num == 0) { ret = root_port_recycle(); } else { - ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port has not been implemented yet"); - return ESP_ERR_NOT_SUPPORTED; +#if ENABLE_USB_HUBS + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_recycle(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS } return ret; @@ -549,8 +598,7 @@ esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port HUB_DRIVER_ENTER_CRITICAL(); HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); HUB_DRIVER_EXIT_CRITICAL(); - - esp_err_t ret = ESP_FAIL; + esp_err_t ret; if (parent_port_num == 0) { ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET); @@ -559,13 +607,68 @@ esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port } ret = dev_tree_node_reset_completed(NULL, 0); } else { - ESP_LOGW(HUB_DRIVER_TAG, "Reset External Port has not been implemented yet"); - return ESP_ERR_NOT_SUPPORTED; +#if ENABLE_USB_HUBS + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_reset(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Resetting External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS } - return ret; } +esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num) +{ + esp_err_t ret; + + if (parent_port_num == 0) { + // Root port no need to be activated + ret = ESP_OK; + } else { +#if ENABLE_USB_HUBS + // External Hub port + ext_hub_handle_t ext_hub_hdl = NULL; + ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl); + ret = ext_hub_port_active(ext_hub_hdl, parent_port_num); +#else + ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)"); + ret = ESP_ERR_NOT_SUPPORTED; +#endif // ENABLE_USB_HUBS + } + return ret; +} + +#if ENABLE_USB_HUBS +esp_err_t hub_notify_new_dev(uint8_t dev_addr) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_new_dev(dev_addr); +} + +esp_err_t hub_notify_dev_gone(uint8_t dev_addr) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_dev_gone(dev_addr); +} + +esp_err_t hub_notify_all_free(void) +{ + HUB_DRIVER_ENTER_CRITICAL(); + HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE); + HUB_DRIVER_EXIT_CRITICAL(); + + return ext_hub_all_free(); +} +#endif // ENABLE_USB_HUBS + esp_err_t hub_process(void) { HUB_DRIVER_ENTER_CRITICAL(); @@ -574,10 +677,21 @@ esp_err_t hub_process(void) HUB_DRIVER_EXIT_CRITICAL(); while (action_flags) { - if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { +#if ENABLE_USB_HUBS + if (action_flags & HUB_DRIVER_ACTION_EXT_PORT) { + ESP_LOGW(HUB_DRIVER_TAG, "ext_port_process() has not been implemented yet"); + /* + ESP_ERROR_CHECK(ext_port_process()); + */ + } + if (action_flags & HUB_DRIVER_ACTION_EXT_HUB) { + ESP_ERROR_CHECK(ext_hub_process()); + } +#endif // ENABLE_USB_HUBS + if (action_flags & HUB_DRIVER_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); } - if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) { + if (action_flags & HUB_DRIVER_ACTION_ROOT_REQ) { root_port_req(p_hub_driver_obj->constant.root_port_hdl); } @@ -586,5 +700,6 @@ esp_err_t hub_process(void) p_hub_driver_obj->dynamic.flags.actions = 0; HUB_DRIVER_EXIT_CRITICAL(); } + return ESP_OK; } diff --git a/components/usb/include/usb/usb_types_ch11.h b/components/usb/include/usb/usb_types_ch11.h index 6d13998cc5..a30e9d3919 100644 --- a/components/usb/include/usb/usb_types_ch11.h +++ b/components/usb/include/usb/usb_types_ch11.h @@ -66,7 +66,34 @@ typedef enum { } usb_hub_port_feature_t; /** - * @brief Size of a USB Hub Port Status and Hub Change results + * @brief USB Hub characteristics + * + * See USB 2.0 spec Table 11-13, offset 3 + */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL (0) /**< All ports power control at once */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV (1) /**< Individual port power control */ +#define USB_W_HUB_CHARS_PORT_PWR_CTRL_NO (2) /**< No power switching */ + +#define USB_W_HUB_CHARS_PORT_OVER_CURR_ALL (0) /**< All ports Over-Current reporting */ +#define USB_W_HUB_CHARS_PORT_OVER_CURR_INDV (1) /**< Individual port Over-current reporting */ +#define USB_W_HUB_CHARS_PORT_OVER_CURR_NO (2) /**< No Over-current Protection support */ + +#define USB_W_HUB_CHARS_TTTT_8_BITS (0) /**< TT requires at most 8 FS bit times of inter transaction gap on a full-/low-speed downstream bus */ +#define USB_W_HUB_CHARS_TTTT_16_BITS (1) /**< TT requires at most 16 FS bit times */ +#define USB_W_HUB_CHARS_TTTT_24_BITS (2) /**< TT requires at most 24 FS bit times */ +#define USB_W_HUB_CHARS_TTTT_32_BITS (3) /**< TT requires at most 32 FS bit times */ + +/** + * @brief USB Hub bDeviceProtocol + */ +#define USB_B_DEV_PROTOCOL_HUB_FS (0) /**< Full speed hub */ +#define USB_B_DEV_PROTOCOL_HUB_HS_NO_TT (0) /**< Hi-speed hub without TT */ +#define USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT (1) /**< Hi-speed hub with single TT */ +#define USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT (2) /**< Hi-speed hub with multiple TT */ +#define USB_B_DEV_PROTOCOL_HUB_SS (3) /**< Super speed hub */ + +/** + * @brief USB Hub Port Status and Hub Change results size */ #define USB_PORT_STATUS_SIZE 4 @@ -148,7 +175,17 @@ typedef struct { uint8_t bDescLength; /**< Number of bytes in this descriptor, including this byte */ uint8_t bDescriptorType; /**< Descriptor Type, value: 29H for Hub descriptor */ uint8_t bNbrPorts; /**< Number of downstream facing ports that this Hub supports */ - uint16_t wHubCharacteristics; /**< Logical Power Switching Mode, Compound Device, Over-current Protection Mode, TT Think Time, Port Indicators Supported */ + union { + struct { + uint16_t power_switching: 2; + uint16_t compound: 1; + uint16_t ovr_current_protect: 2; + uint16_t tt_think_time: 2; + uint16_t indicator_support: 1; + uint16_t reserved: 8; + }; + uint16_t val; /**< Hub Characteristics value */ + } wHubCharacteristics; /**< Hub Characteristics */ uint8_t bPwrOn2PwrGood; /**< Time (in 2 ms intervals) from the time the power-on sequence begins on a port until power is good on that port */ uint8_t bHubContrCurrent; /**< Maximum current requirements of the Hub Controller electronics in mA. */ } __attribute__((packed)) usb_hub_descriptor_t; diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index c09440eb00..500a5dc51c 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/components/usb/include/usb/usb_types_ch9.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -101,6 +101,21 @@ typedef union { } usb_setup_packet_t; ESP_STATIC_ASSERT(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb_setup_packet_t incorrect"); +/** + * @brief Structure representing a USB device status + * + * See Figures 9-4 Information Returned by a GetStatus() Request to a Device of USB2.0 specification for more details + */ +typedef union { + struct { + uint16_t self_powered: 1; /**< 1 - Device is currently self-powered, 0 - bus powered */ + uint16_t remote_wakeup: 1; /**< 1 - the ability of the device to signal remote wakeup is enabled, 0 - the ability of the device to signal remote wakeup is disabled. */ + uint16_t reserved: 14; /**< reserved */ + } USB_DESC_ATTR; /**< Packed */ + uint16_t val; /**< Device status value */ +} usb_device_status_t; +ESP_STATIC_ASSERT(sizeof(usb_device_status_t) == sizeof(uint16_t), "Size of usb_device_status_t incorrect"); + /** * @brief Bit masks belonging to the bmRequestType field of a setup packet */ @@ -144,6 +159,19 @@ ESP_STATIC_ASSERT(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of #define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07 #define USB_W_VALUE_DT_INTERFACE_POWER 0x08 +/** + * @brief Initializer for a GET_STATUS request + * + * Sets the address of a connected device + */ +#define USB_SETUP_PACKET_INIT_GET_STATUS(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_STATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 2; \ +}) + /** * @brief Initializer for a SET_ADDRESS request * diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h new file mode 100644 index 0000000000..9fde880fe7 --- /dev/null +++ b/components/usb/private_include/ext_hub.h @@ -0,0 +1,248 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "hcd.h" +#include "usbh.h" +#include "usb/usb_types_stack.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_types_ch11.h" + +#if CONFIG_USB_HOST_HUB_MULTI_LEVEL +#define ENABLE_MULTIPLE_HUBS 1 +#endif // CONFIG_USB_HOST_HUB_MULTI_LEVEL + +#ifdef __cplusplus +extern "C" { +#endif + +// ----------------------------- Handles --------------------------------------- + +typedef struct ext_hub_s *ext_hub_handle_t; + +// ---------------------------- Callbacks -------------------------------------- + +/** + * @brief Callback used to indicate that the External Hub Driver requires process callback + * For Hub Driver only + */ +typedef bool (*ext_hub_cb_t)(bool in_isr, void *user_arg); + +// ------------------------ External Port API typedefs ------------------------- + +/** + * @brief External Hub Port driver + */ +typedef struct { + esp_err_t (*new)(void *port_cfg, void **port_hdl); + esp_err_t (*reset)(void *port_hdl); + esp_err_t (*recycle)(void *port_hdl); + esp_err_t (*active)(void *port_hdl); + esp_err_t (*disable)(void *port_hdl); + esp_err_t (*gone)(void *port_hdl); + esp_err_t (*free)(void *port_hdl); + esp_err_t (*get_speed)(void *por_hdl, usb_speed_t *speed); + esp_err_t (*get_status)(void *port_hdl); + esp_err_t (*set_status)(void *port_hdl, const usb_port_status_t *status); +} ext_hub_port_driver_t; + +/** + * @brief External Hub Driver configuration + */ +typedef struct { + ext_hub_cb_t proc_req_cb; /**< External Hub process callback */ + void *proc_req_cb_arg; /**< External Hub process callback argument */ + const ext_hub_port_driver_t* port_driver; /**< External Port Driver */ +} ext_hub_config_t; + +// ------------------------------ Driver --------------------------------------- + +/** + * @brief Install External Hub Driver + * + * Entry: + * - should be called within Hub Driver + * + * @param[in] config External Hub driver configuration + * @return esp_err_t + */ +esp_err_t ext_hub_install(const ext_hub_config_t* config); + +/** + * @brief Uninstall External Hub Driver + * + * Entry: + * - should be called within Hub Driver + * + * @return esp_err_t + */ +esp_err_t ext_hub_uninstall(void); + +/** + * @brief External Hub Driver get client pointer + * + * Entry: + * - should be called within Hub Driver + * + * @param[in] config External Hub driver configuration + * @return Unique pointer to identify the External Hub as a USB Host client + */ +void *ext_hub_get_client(void); + +// -------------------------- External Hub API --------------------------------- + +/** + * @brief Get External Hub device handle by USBH device handle + * + * @param[in] dev_hdl USBH device handle + * @param[out] ext_hub_hdl External Hub device handle + * @return esp_err_t + */ +esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_hub_hdl); + +/** + * @brief Add new device + * + * After attaching new device: + * - configure it's parameters (requesting hub descriptor) + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_new_dev(uint8_t dev_addr); + +/** + * @brief Device gone + * + * After device were detached: + * - prepare the device to be freed + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_dev_gone(uint8_t dev_addr); + +/** + * @brief Marks all devices to be freed + * + * Entry: + * - should be called within Hub Driver when USB Host library need to be uninstalled + * + * @param[in] dev_addr Device bus address + * @return esp_err_t + */ +esp_err_t ext_hub_all_free(void); + +/** + * @brief The External Hub or Ports statuses change completed + * + * Enables Interrupt IN endpoint to get information about Hub or Ports statuses change + * + * @param[in] ext_hub_hdl External Hub device handle + * @return esp_err_t + */ +esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl); + +/** + * @brief External Hub driver's process function + * + * External Hub driver process function that must be called repeatedly to process the driver's actions and events. + * If blocking, the caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB + * to run this function. + */ +esp_err_t ext_hub_process(void); + +// -------------------- External Hub - Port related ---------------------------- + +/** + * @brief Indicate to the External Hub driver that a device's port can be recycled + * + * The device connected to the port has been freed. The Hub driver can now + * recycle the port. + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port need a reset + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port has a device and device has been enumerated + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Indicate to the External Hub driver that a device's port should be disabled + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +/** + * @brief Returns device speed of the device, attached to the port + * + * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_num Port number + * @param[out] speed Devices' speed + * @retval ESP_OK: Success + */ +esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, usb_speed_t *speed); + +// --------------------------- USB Chapter 11 ---------------------------------- + +/** + * @brief Set Port Feature request + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @param[in] feature Port Feature to set + * @return esp_err_t + */ +esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); + +/** + * @brief Clear Port Feature request + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @param[in] feature Port Feature to clear + * @return esp_err_t + */ +esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); + +/** + * @brief Get Port Status request + * + * Sends the request to retrieve port status data. + * Actual port status data could be requested by calling ext_hub_get_port_status() after request completion. + * + * @param[in] ext_hub_hdl External Hub device handle + * @param[in] port_num Port number + * @return esp_err_t + */ +esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); + +#ifdef __cplusplus +} +#endif diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index 5c8361bfb2..067eef790b 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -8,10 +8,15 @@ #include #include +#include "sdkconfig.h" #include "esp_err.h" #include "usb_private.h" #include "usbh.h" +#if CONFIG_USB_HOST_HUBS_SUPPORTED +#define ENABLE_USB_HUBS 1 +#endif // CONFIG_USB_HOST_HUBS_SUPPORTED + #ifdef __cplusplus extern "C" { #endif @@ -138,6 +143,50 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po */ esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); +/** + * @brief Activate the port + * + * @note This function should only be called from the Host Library task + * + * @param[in] parent_dev_hdl Parent device handle (is used to get the External Hub handle) + * @param[in] parent_port_num Parent number (is used to specify the External Port) + * @return + * - ESP_OK: Success + */ +esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num); + +#if ENABLE_USB_HUBS +/** + * @brief Notify Hub driver that new device has been attached + * + * If device is has a HUB class, then it will be added as External Hub to Hub Driver. + * + * @param[in] dev_addr Device bus address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_new_dev(uint8_t dev_addr); + +/** + * @brief Notify Hub driver that device has been removed + * + * If the device was an External Hub, then it will be removed from the Hub Driver. + * + * @param[in] dev_addr Device bus address + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_dev_gone(uint8_t dev_addr); + +/** + * @brief Notify Hub driver that all devices should be freed + * + * @return + * - ESP_OK: Success + */ +esp_err_t hub_notify_all_free(void); +#endif // ENABLE_USB_HUBS + /** * @brief Hub driver's processing function * diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 1e1ca4c16f..9bd773007b 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -227,6 +227,11 @@ static inline bool _is_internal_client(void *client) if (p_host_lib_obj->constant.enum_client && (client == p_host_lib_obj->constant.enum_client)) { return true; } +#if ENABLE_USB_HUBS + if (p_host_lib_obj->constant.hub_client && (client == p_host_lib_obj->constant.hub_client)) { + return true; + } +#endif // ENABLE_USB_HUBS return false; } @@ -311,9 +316,15 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) .new_dev.address = event_data->new_dev_data.dev_addr, }; send_event_msg_to_clients(&event_msg, true, 0); +#if ENABLE_USB_HUBS + hub_notify_new_dev(event_data->new_dev_data.dev_addr); +#endif // ENABLE_USB_HUBS break; } case USBH_EVENT_DEV_GONE: { +#if ENABLE_USB_HUBS + hub_notify_dev_gone(event_data->new_dev_data.dev_addr); +#endif // ENABLE_USB_HUBS // Prepare event msg, send only to clients that have opened the device usb_host_client_event_msg_t event_msg = { .event = USB_HOST_CLIENT_EVENT_DEV_GONE, @@ -324,9 +335,10 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg) } case USBH_EVENT_DEV_FREE: { // Let the Hub driver know that the device is free and its port can be recycled - ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.parent_dev_hdl, - event_data->dev_free_data.port_num, - event_data->dev_free_data.dev_uid)); + // Port could be absent, no need to verify + hub_port_recycle(event_data->dev_free_data.parent_dev_hdl, + event_data->dev_free_data.port_num, + event_data->dev_free_data.dev_uid); break; } case USBH_EVENT_ALL_FREE: { @@ -378,6 +390,8 @@ static void enum_event_callback(enum_event_data_t *event_data, void *arg) hub_port_reset(event_data->reset_req.parent_dev_hdl, event_data->reset_req.parent_port_num); break; case ENUM_EVENT_COMPLETED: + // Notify port that device completed enumeration + hub_port_active(event_data->complete.parent_dev_hdl, event_data->complete.parent_port_num); // Propagate a new device event ESP_ERROR_CHECK(usbh_devs_new_dev_event(event_data->complete.dev_hdl)); break; @@ -995,6 +1009,9 @@ esp_err_t usb_host_device_free_all(void) HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.flags.num_clients == 0, ESP_ERR_INVALID_STATE); // All clients must have been deregistered HOST_EXIT_CRITICAL(); esp_err_t ret; +#if ENABLE_USB_HUBS + hub_notify_all_free(); +#endif // ENABLE_USB_HUBS ret = usbh_devs_mark_all_free(); // If ESP_ERR_NOT_FINISHED is returned, caller must wait for USB_HOST_LIB_EVENT_FLAGS_ALL_FREE to confirm all devices are free return ret; From 5527ee588ed5df90c43967631e674464230573f4 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 23 May 2024 13:13:13 +0200 Subject: [PATCH 2/2] docs(ext_hub): Added description for External Hub Driver --- docs/conf_common.py | 3 +- docs/docs_not_updated/esp32c5.txt | 1 + docs/docs_not_updated/esp32p4.txt | 1 + .../usb_host/usb_host_notes_ext_hub.rst | 65 +++++++++++++++++++ .../usb_host/usb_host_notes_index.rst | 6 +- .../usb_host/usb_host_notes_ext_hub.rst | 1 + .../usb_host/usb_host_notes_index.rst | 57 +--------------- 7 files changed, 76 insertions(+), 58 deletions(-) create mode 100644 docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst create mode 100644 docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst diff --git a/docs/conf_common.py b/docs/conf_common.py index 053c47d3b7..fc29c45028 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -117,7 +117,8 @@ USB_DOCS = ['api-reference/peripherals/usb_device.rst', 'api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst', 'api-reference/peripherals/usb_host/usb_host_notes_index.rst', 'api-reference/peripherals/usb_host/usb_host_notes_usbh.rst', - 'api-reference/peripherals/usb_host/usb_host_notes_enum.rst'] + 'api-reference/peripherals/usb_host/usb_host_notes_enum.rst', + 'api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst'] I80_LCD_DOCS = ['api-reference/peripherals/lcd/i80_lcd.rst'] RGB_LCD_DOCS = ['api-reference/peripherals/lcd/rgb_lcd.rst'] diff --git a/docs/docs_not_updated/esp32c5.txt b/docs/docs_not_updated/esp32c5.txt index dc5ef2a728..bd92346df9 100644 --- a/docs/docs_not_updated/esp32c5.txt +++ b/docs/docs_not_updated/esp32c5.txt @@ -86,6 +86,7 @@ api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst api-reference/peripherals/usb_host/usb_host_notes_design.rst api-reference/peripherals/usb_host/usb_host_notes_usbh.rst api-reference/peripherals/usb_host/usb_host_notes_enum.rst +api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst api-reference/peripherals/usb_device.rst api-reference/peripherals/sdspi_host.rst api-reference/peripherals/spi_slave.rst diff --git a/docs/docs_not_updated/esp32p4.txt b/docs/docs_not_updated/esp32p4.txt index 828989eb39..06a53790b7 100644 --- a/docs/docs_not_updated/esp32p4.txt +++ b/docs/docs_not_updated/esp32p4.txt @@ -18,6 +18,7 @@ api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst api-reference/peripherals/usb_host/usb_host_notes_design.rst api-reference/peripherals/usb_host/usb_host_notes_usbh.rst api-reference/peripherals/usb_host/usb_host_notes_enum.rst +api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst api-reference/peripherals/usb_device.rst api-reference/peripherals/touch_element.rst api-reference/peripherals/spi_flash/xip_from_psram.inc diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst new file mode 100644 index 0000000000..3b2876a7a4 --- /dev/null +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst @@ -0,0 +1,65 @@ +USB Host External Hub Driver (Ext Hub) +====================================== + +Introduction +------------ + +The External Hub Driver (henceforth referred to as Ext Hub Driver) + +Requirements +------------ + +USB Specification Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Chapter 11 of the USB 2.0 specification outlines some aspects, when a USB Hub device is attached to a powered port. + +The design of the Ext Driver takes into consideration the following: + +- **Connectivity behavior** +- **Power management** +- **Device connect/disconnect detection** +- **Bus fault detection and recovery** +- **High-, full-, and low-speed device support** + +.. note:: + + For more detailed information, please refer to `USB 2.0 Specification `_ > Chapter 11.1 **Overview**. + +Host Stack Requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the USB 2.0 specification requirements, the Ext Hub Driver also takes into consideration the requirements set for the overall Host Stack (see :doc:`./usb_host_notes_design`): + +- Ext Hub Driver must not instantiate any tasks/threads +- Ext Hub Driver must be event driven, providing event callbacks and an event processing function +- Ext Hub Driver must use only API from underlying layer (USBH) + +Implementation & Usage +---------------------- + +Host Stack Interaction +^^^^^^^^^^^^^^^^^^^^^^ + +The Ext Hub Driver takes place between USB Host layer and USBH layer, next to the Hub Driver. The Hub Driver and the Ext Hub Driver were split into two Drivers to achieve the goal of logic distinguishing between root Hub and external Hub. + +Device handling +^^^^^^^^^^^^^^^ + +The Ext Hub Driver can be installed via ``ext_hub_install()`` call and uninstalled via ``ext_hub_uninstall()`` call. After installation the Ext Hub provides the following APIs for external Hub addition and removal: + +- ``ext_hub_new_dev()`` which will verify the device class (`HUB_CLASSCODE (09H)`) and, if the device has the Hub class, the Ext Hub Driver: + + - allocates a new device object + - adds it to the external device pool + - starts the process of Hub configuration (retrieving Hub Descriptor, Device status and Hub status) + +- ``ext_hub_dev_gone()`` which will verify the device in the Ext Hub Driver list and start the process of external Hub device removing. + +Events & Processing +^^^^^^^^^^^^^^^^^^^ + +The Ext Hub Driver is completely event driven and all event handling is done via the ``ext_hub_process()`` function. The ``ext_hub_config_t.proc_req_cb`` callback provided on the Ext Hub Driver installation will be called when processing is required. Typically, ``ext_hub_process()`` will be called from the Hub Driver ``hub_process()`` processing function. + +The Ext Hub Driver does not expose any event callback. + diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 6b1c6fa9ac..738ac7a270 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -23,6 +23,7 @@ This document is split into the following sections: usb_host_notes_dwc_otg usb_host_notes_usbh usb_host_notes_enum + usb_host_notes_ext_hub Todo: @@ -45,6 +46,10 @@ Features & Limitations **The Host Stack currently supports the following notable features:** +.. only:: esp32p4 + + - Supports HS (High Speed) + - Supports FS (Full Speed) and LS (Low Speed) devices - Supports all transfer types (Control, Bulk, Isochronous, and Interrupt) - Automatically enumerates connected devices @@ -52,5 +57,4 @@ Features & Limitations **The Host Stack currently has the following notable limitations:** -- No HS (High Speed) support - No Hub support (currently only supports a single device) diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst new file mode 100644 index 0000000000..e8bab47973 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst @@ -0,0 +1 @@ +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst index c6318a690a..09d410debf 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -1,56 +1 @@ -USB 主机维护者注意事项(简介) -============================== - -:link_to_translation:`en:[English]` - -本文档包含有关 USB 主机协议栈实现细节的信息,面向 USB 主机协议栈的维护者和第三方贡献者。USB 主机协议栈的用户请参考 :doc:`../usb_host`。 - -.. warning:: - - USB 主机协议栈的实现细节属于私有 API,因此,除 USB 主机库外的所有层均不遵循 :ref:`ESP-IDF 版本简介 `,即允许进行重大更改。 - -.. figure:: ../../../../_static/usb_host/stack-overview.png - :align: center - :alt: 主机协议栈层次结构图 - -本文档分为以下几个部分: - -.. toctree:: - :maxdepth: 1 - - usb_host_notes_design - usb_host_notes_arch - usb_host_notes_dwc_otg - usb_host_notes_usbh - usb_host_notes_enum - -待写章节: - -- USB 主机维护者注意事项(HAL 和 LL) -- USB 主机维护者注意事项(HCD) -- USB 主机维护者注意事项(Hub) -- USB 主机维护者注意事项(USB Host Library) - -.. -------------------------------------------------- Introduction ----------------------------------------------------- - -简介 ----- - -ESP-IDF USB 主机协议栈允许 {IDF_TARGET_NAME} 作为 USB 主机运行,此时,{IDF_TARGET_NAME} 能够与各种 USB 设备通信。然而,大多数 USB 主机协议栈实现都不运行在嵌入式硬件上(即在电脑和手机端运行),因此,相对来说具有更多的资源(即,具有更高内存和 CPU 速度)。 - -ESP-IDF USB 主机协议栈(以下简称为主机协议栈)的实现考虑到了 {IDF_TARGET_NAME} 的嵌入式特性,这体现在主机协议栈设计的各个方面。 - -特性和局限性 -^^^^^^^^^^^^ - -**主机协议栈目前支持以下显著特性:** - -- 支持 FS(全速)和 LS(低速)设备 -- 支持所有传输类型(控制传输、批量传输、同步传输和中断传输) -- 自动枚举已连接设备 -- 允许多个类驱动程序(即 USB 主机库的客户端)同时运行并共享同一设备(即组合设备) - -**主机协议栈目前存在以下显著局限:** - -- 不支持 HS(高速)设备 -- 不支持集线器(当前仅支持单个设备) +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_index.rst