From 548b03c69f30cd76f150fa5ff53e012480025a5d Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 2 Apr 2024 14:25:11 +0200 Subject: [PATCH] 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;