diff --git a/Kconfig b/Kconfig index 13d6c564cb..7b6d5b12ea 100644 --- a/Kconfig +++ b/Kconfig @@ -678,5 +678,4 @@ mainmenu "Espressif IoT Development Framework Configuration" - CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH - CONFIG_ESP_WIFI_EAP_TLS1_3 - CONFIG_ESP_WIFI_ENABLE_ROAMING_APP - - CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS - CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS diff --git a/components/usb/Kconfig b/components/usb/Kconfig index 376b7fc58c..ce8a032065 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -115,13 +115,6 @@ menu "USB-OTG" menu "Downstream Port configuration" depends on USB_HOST_HUBS_SUPPORTED - config USB_HOST_EXT_PORT_SUPPORT_LS - depends on IDF_EXPERIMENTAL_FEATURES - bool "Support LS" - default n - help - Enables support of Low-speed devices, connected through the external Hub. - config USB_HOST_EXT_PORT_RESET_ATTEMPTS depends on IDF_EXPERIMENTAL_FEATURES # Invisible config option diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c index 8b02f4191f..dbfdd43df2 100644 --- a/components/usb/ext_hub.c +++ b/components/usb/ext_hub.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,7 +29,7 @@ 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_FAILED /**< Device has internal error */ + EXT_HUB_STATE_RELEASED, /**< Device released and its ports are not available */ } ext_hub_state_t; /** @@ -53,10 +53,10 @@ typedef enum { 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 */ + // Stages, don't required get stage handling + EXT_HUB_STAGE_CHECK_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_CHECK_PORT_STATUS, /**< 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_ERROR /**< Device has internal error and requires handling */ } ext_hub_stage_t; const char *const ext_hub_stage_strings[] = { @@ -76,15 +76,14 @@ const char *const ext_hub_stage_strings[] = { * @brief Device action flags */ typedef enum { - DEV_ACTION_EP0_COMPLETE = (1 << 1), /**< Device complete one of stages, requires handling */ + DEV_ACTION_EP0_COMPLETE = (1 << 1), /**< Device's Control EP transfer completed */ 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_REQ = (1 << 5), /**< Device has new actions and requires 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_RELEASE = (1 << 7), /**< Device should be released */ + DEV_ACTION_FREE = (1 << 8), /**< Device should be freed */ } dev_action_t; typedef struct ext_hub_s ext_hub_dev_t; @@ -146,7 +145,7 @@ typedef struct { 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 */ + const ext_port_driver_api_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; @@ -301,14 +300,13 @@ static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flag // 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; } + ext_hub_dev->dynamic.action_flags |= action_flags; return call_proc_req_cb; } @@ -317,7 +315,8 @@ static esp_err_t device_enable_int_ep(ext_hub_dev_t *ext_hub_dev) ESP_LOGD(EXT_HUB_TAG, "[%d] Enable EP IN", ext_hub_dev->constant.dev_addr); esp_err_t 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: %s", esp_err_to_name(ret)); + ESP_LOGE(EXT_HUB_TAG, "[%d] Failed to submit in urb: %s", ext_hub_dev->constant.dev_addr, esp_err_to_name(ret)); + device_error(ext_hub_dev); } return ret; } @@ -369,7 +368,7 @@ static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_ assert(i < ext_hub_dev->single_thread.maxchild); // Port should be in range assert(p_ext_hub_driver->constant.port_driver); // Port driver call should be valid // Request Port status to handle changes - p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[i]); + ESP_ERROR_CHECK(p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[i])); } } } else { @@ -384,7 +383,9 @@ 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_dev->dynamic.flags.waiting_release = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_ERROR | + DEV_ACTION_RELEASE); EXT_HUB_EXIT_CRITICAL(); if (call_proc_req_cb) { @@ -395,7 +396,7 @@ static void device_error(ext_hub_dev_t *ext_hub_dev) static esp_err_t device_port_new(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx) { ext_port_config_t port_config = { - .ext_hub_hdl = (ext_hub_handle_t) ext_hub_dev, + .context = (void*) ext_hub_dev, .parent_dev_hdl = ext_hub_dev->constant.dev_hdl, .parent_port_num = port_idx + 1, .port_power_delay_ms = ext_hub_dev->constant.hub_desc->bPwrOn2PwrGood * 2, @@ -462,45 +463,72 @@ exit: return ret; } +static esp_err_t device_release_ports(ext_hub_dev_t *ext_hub_dev) +{ + esp_err_t ret = ESP_OK; + // Mark all ports as gone + for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { + // Only for ports, that were created + if (ext_hub_dev->constant.ports[i] != NULL) { + // Mark port as gone + ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); + if (ret == ESP_OK) { + // Port doesn't have a device and can be recycled right now + ESP_ERROR_CHECK(device_port_free(ext_hub_dev, i)); + } else if (ret == ESP_ERR_NOT_FINISHED) { + // Port has a device and will be recycled after USBH device will be released by all clients and freed + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone", ext_hub_dev->constant.dev_addr, i + 1); + // Not an error case, but it's good to notify this with the error message + // TODO: IDF-12173 remove the error, instead set up the flag and verify the flag while recycle + ret = ESP_OK; + } else { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s", + ext_hub_dev->constant.dev_addr, i + 1, esp_err_to_name(ret)); + return ret; + } + } else { + ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port was not created", ext_hub_dev->constant.dev_addr, i + 1); + } + } + return ret; +} + static void device_release(ext_hub_dev_t *ext_hub_dev) { - esp_err_t ret; - ESP_LOGD(EXT_HUB_TAG, "[%d] Device release", ext_hub_dev->constant.dev_addr); - // Release IN EP - ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); - EXT_HUB_ENTER_CRITICAL(); + assert(ext_hub_dev->dynamic.flags.waiting_release); // Device should waiting the release ext_hub_dev->dynamic.flags.is_gone = 1; ext_hub_dev->dynamic.flags.waiting_release = 0; EXT_HUB_EXIT_CRITICAL(); - if (ext_hub_dev->single_thread.state >= EXT_HUB_STATE_CONFIGURED) { - // Hub device was configured and has a descriptor - assert(ext_hub_dev->constant.hub_desc != NULL); - assert(p_ext_hub_driver->constant.port_driver); - // Mark all ports as gone - for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { - if (ext_hub_dev->constant.ports[i]) { - ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]); - if (ret == ESP_OK) { - // Port doesn't have a device and can be recycled right now - ret = device_port_free(ext_hub_dev, i); - if (ret != ESP_OK) { - // Hub runs into an error state - // TODO: IDF-10057 Hub handling error - } - } else if (ret == ESP_ERR_NOT_FINISHED) { - // Port has a device and will be recycled after USBH device will be released by all clients and freed - ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone", ext_hub_dev->constant.dev_addr, i + 1); - } else { - ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s", - ext_hub_dev->constant.dev_addr, i + 1, esp_err_to_name(ret)); - } - } + // Release IN EP + ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT)); + + switch (ext_hub_dev->single_thread.state) { + case EXT_HUB_STATE_ATTACHED: + // Device has no configured ports, release the USBH device object + ESP_LOGD(EXT_HUB_TAG, "[%d] Release USBH device object", ext_hub_dev->constant.dev_addr); + ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl)); + break; + case EXT_HUB_STATE_CONFIGURED: + case EXT_HUB_STATE_SUSPENDED: + assert(ext_hub_dev->constant.hub_desc != NULL); // Device should have a Hub descriptor + assert(p_ext_hub_driver->constant.port_driver); // Port driver should be available + + // Release ports if device has them + if (ext_hub_dev->constant.hub_desc->bNbrPorts) { + ESP_ERROR_CHECK(device_release_ports(ext_hub_dev)); } + break; + default: + // Should never occur + abort(); + break; } + + ext_hub_dev->single_thread.state = EXT_HUB_STATE_RELEASED; } static esp_err_t device_alloc_desc(ext_hub_dev_t *ext_hub_hdl, const usb_hub_descriptor_t *hub_desc) @@ -520,6 +548,9 @@ static esp_err_t device_alloc_desc(ext_hub_dev_t *ext_hub_hdl, const usb_hub_des static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_dev) { + EXT_HUB_CHECK(config != NULL, ESP_ERR_INVALID_ARG); + EXT_HUB_CHECK(config->dev_addr != 0, ESP_ERR_NOT_ALLOWED); + esp_err_t ret; urb_t *ctrl_urb = NULL; urb_t *in_urb = NULL; @@ -537,7 +568,8 @@ static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_d 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"); + ESP_LOGE(EXT_HUB_TAG, "[%d] Unable to allocate device", + config->dev_addr); ret = ESP_ERR_NO_MEM; goto fail; } @@ -545,13 +577,16 @@ static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_d // 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"); + ESP_LOGE(EXT_HUB_TAG, "[%d] Unable to allocate Control URB", + config->dev_addr); ret = ESP_ERR_NO_MEM; goto ctrl_urb_fail; } if (config->ep_in_desc->wMaxPacketSize > EXT_HUB_MAX_STATUS_BYTES_SIZE) { - ESP_LOGE(EXT_HUB_TAG, "wMaxPacketSize=%d is not supported", config->ep_in_desc->wMaxPacketSize); + ESP_LOGE(EXT_HUB_TAG, "[%d] wMaxPacketSize=%d is not supported", + config->dev_addr, + config->ep_in_desc->wMaxPacketSize); ret = ESP_ERR_NOT_SUPPORTED; goto in_urb_fail; } @@ -559,7 +594,8 @@ static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_d 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"); + ESP_LOGE(EXT_HUB_TAG, "[%d] Unable to allocate Interrupt URB", + config->dev_addr); ret = ESP_ERR_NO_MEM; goto in_urb_fail; } @@ -576,7 +612,9 @@ static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_d ret = usbh_ep_alloc(config->dev_hdl, &ep_config, &ep_hdl); if (ret != ESP_OK) { - ESP_LOGE(EXT_HUB_TAG, "Endpoint allocation failure: %s", esp_err_to_name(ret)); + ESP_LOGE(EXT_HUB_TAG, "[%d] Interrupt EP allocation failure: %s", + config->dev_addr, + esp_err_to_name(ret)); goto ep_fail; } // Configure Control transfer URB @@ -678,15 +716,22 @@ static esp_err_t device_configure(ext_hub_dev_t *ext_hub_dev) 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 + if (hub_desc->bNbrPorts == 0) { + ESP_LOGW(EXT_HUB_TAG, "[%d] Device doesn't have any ports", ext_hub_dev->constant.dev_addr); + // Nothing to configure, keep the device in EXT_HUB_STATE_ATTACHED + return ESP_OK; + } + + // Device has external ports + // Create flexible array for port object pointers 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 list allocation error"); + ESP_LOGE(EXT_HUB_TAG, "[%d] Ports list allocation error", ext_hub_dev->constant.dev_addr); ret = ESP_ERR_NO_MEM; goto fail; } - // Create port and add it to pending list + // Create each port object for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) { ext_hub_dev->constant.ports[i] = NULL; ret = device_port_new(ext_hub_dev, i); @@ -807,6 +852,13 @@ exit: // ----------------------------------------------------------------------------- // -------------------------- Device handling --------------------------------- // ----------------------------------------------------------------------------- +static void handle_error(ext_hub_dev_t *ext_hub_dev) +{ + ESP_LOGE(EXT_HUB_TAG, "[%d] Device is not working properly, wait device removal", + ext_hub_dev->constant.dev_addr); + // Force change the stage + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_ERROR; +} static bool handle_hub_descriptor(ext_hub_dev_t *ext_hub_dev) { @@ -815,13 +867,13 @@ static bool handle_hub_descriptor(ext_hub_dev_t *ext_hub_dev) 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->single_thread.stage); + ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE); + + if (hub_desc->bDescriptorType != USB_CLASS_DESCRIPTOR_TYPE_HUB) { + ESP_LOGE(EXT_HUB_TAG, "[%d] Hub Descriptor has wrong bDescriptorType", ext_hub_dev->constant.dev_addr); 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; @@ -886,7 +938,8 @@ static void handle_port_feature(ext_hub_dev_t *ext_hub_dev) assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts); assert(p_ext_hub_driver->constant.port_driver); - p_ext_hub_driver->constant.port_driver->req_process(ext_hub_dev->constant.ports[port_idx]); + // [TODO: IDF-12174] Revisit the External Hub Driver to ensure consistent error handling. + ESP_ERROR_CHECK(p_ext_hub_driver->constant.port_driver->req_process(ext_hub_dev->constant.ports[port_idx])); } static void handle_port_status(ext_hub_dev_t *ext_hub_dev) @@ -899,7 +952,8 @@ static void handle_port_status(ext_hub_dev_t *ext_hub_dev) assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts); assert(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); + // [TODO: IDF-12174] Revisit the External Hub Driver to ensure consistent error handling. + ESP_ERROR_CHECK(p_ext_hub_driver->constant.port_driver->set_status(ext_hub_dev->constant.ports[port_idx], new_status)); } static bool device_control_request(ext_hub_dev_t *ext_hub_dev) @@ -942,7 +996,8 @@ static bool device_control_response_handling(ext_hub_dev_t *ext_hub_dev) // Check transfer status if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(EXT_HUB_TAG, "Control request bad transfer status %d", + ESP_LOGE(EXT_HUB_TAG, "[%d] Control request bad transfer status %d", + ext_hub_dev->constant.dev_addr, ctrl_xfer->status); return stage_pass; } @@ -960,11 +1015,11 @@ static bool device_control_response_handling(ext_hub_dev_t *ext_hub_dev) case EXT_HUB_STAGE_CHECK_HUB_STATUS: stage_pass = handle_hub_status(ext_hub_dev); break; - case EXT_HUB_STAGE_PORT_FEATURE: + case EXT_HUB_STAGE_CHECK_PORT_FEATURE: handle_port_feature(ext_hub_dev); - stage_pass = true; + stage_pass = true; break; - case EXT_HUB_STAGE_PORT_STATUS_REQUEST: + case EXT_HUB_STAGE_CHECK_PORT_STATUS: handle_port_status(ext_hub_dev); stage_pass = true; break; @@ -986,8 +1041,6 @@ static bool stage_need_process(ext_hub_stage_t stage) 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: @@ -1002,10 +1055,12 @@ static bool stage_need_process(ext_hub_stage_t stage) // false - terminal stage static bool device_set_next_stage(ext_hub_dev_t *ext_hub_dev, bool last_stage_pass) { + bool call_proc_req_cb = false; ext_hub_stage_t last_stage = ext_hub_dev->single_thread.stage; ext_hub_stage_t next_stage; if (last_stage_pass) { + // Device doesn't have an error 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 || @@ -1016,24 +1071,18 @@ static bool device_set_next_stage(ext_hub_dev_t *ext_hub_dev, bool last_stage_pa // Terminal stages, move to IDLE next_stage = EXT_HUB_STAGE_IDLE; } + ext_hub_dev->single_thread.stage = next_stage; + call_proc_req_cb = stage_need_process(next_stage); } else { + // Device has an error 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; + // Set error and wait device to be removed + EXT_HUB_ENTER_CRITICAL(); + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_ERROR); + EXT_HUB_EXIT_CRITICAL(); } - ext_hub_dev->single_thread.stage = next_stage; - return stage_need_process(next_stage); -} - -static void handle_error(ext_hub_dev_t *ext_hub_dev) -{ - ext_hub_dev->single_thread.state = EXT_HUB_STATE_FAILED; - // TODO: IDF-10057 Hub handling error - ESP_LOGW(EXT_HUB_TAG, "%s has not been implemented yet", __FUNCTION__); + return call_proc_req_cb; } static void handle_device(ext_hub_dev_t *ext_hub_dev) @@ -1042,9 +1091,6 @@ static void handle_device(ext_hub_dev_t *ext_hub_dev) bool stage_pass = false; // FSM for external Hub switch (ext_hub_dev->single_thread.stage) { - case EXT_HUB_STAGE_IDLE: - stage_pass = true; - break; case EXT_HUB_STAGE_GET_DEVICE_STATUS: case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR: case EXT_HUB_STAGE_GET_HUB_STATUS: @@ -1053,16 +1099,12 @@ static void handle_device(ext_hub_dev_t *ext_hub_dev) case EXT_HUB_STAGE_CHECK_DEVICE_STATUS: case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR: case EXT_HUB_STAGE_CHECK_HUB_STATUS: - case EXT_HUB_STAGE_PORT_FEATURE: - case EXT_HUB_STAGE_PORT_STATUS_REQUEST: + case EXT_HUB_STAGE_CHECK_PORT_FEATURE: + case EXT_HUB_STAGE_CHECK_PORT_STATUS: stage_pass = device_control_response_handling(ext_hub_dev); break; - case EXT_HUB_STAGE_FAILURE: - handle_error(ext_hub_dev); - stage_pass = true; - break; default: - // Should never occur + // No one must handle the device when EXT_HUB_STAGE_IDLE, so it should never occur abort(); break; } @@ -1101,21 +1143,6 @@ static void handle_ep1_clear(ext_hub_dev_t *ext_hub_dev) usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_CLEAR); } -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 --------------------------------------- // ----------------------------------------------------------------------------- @@ -1279,7 +1306,7 @@ next_iface: 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); esp_err_t ret; ext_hub_dev_t *hub_dev = NULL; @@ -1317,7 +1344,7 @@ esp_err_t ext_hub_new_dev(uint8_t dev_addr) // Create External Hub device ret = device_alloc(&hub_config, &hub_dev); if (ret != ESP_OK) { - ESP_LOGE(EXT_HUB_TAG, "External HUB device alloc error %s", esp_err_to_name(ret)); + ESP_LOGE(EXT_HUB_TAG, "[%d] Unable to add device, error %s", dev_addr, esp_err_to_name(ret)); goto exit; } @@ -1341,17 +1368,16 @@ exit: 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); esp_err_t ret; - ext_hub_dev_t *ext_hub_dev = NULL; - bool in_pending = false; + 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 + // TODO: IDF-10058 Hubs support interface selection (HS) + // All objects which are linked to dev_addr should be freed, but before IDF-10058, one address - one device 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); @@ -1363,23 +1389,30 @@ esp_err_t ext_hub_dev_gone(uint8_t dev_addr) EXT_HUB_ENTER_CRITICAL(); if (ext_hub_dev->dynamic.flags.waiting_release || ext_hub_dev->dynamic.flags.waiting_children) { - // External Hub was already released or waiting children to be freed EXT_HUB_EXIT_CRITICAL(); + // External Hub was already released or waiting children to be freed ret = ESP_ERR_INVALID_STATE; goto exit; } - if (ext_hub_dev->dynamic.flags.in_pending_list) { - in_pending = true; - // TODO: IDF-10490 - // test case: - // - detach the external Hub device right before the Hub Descriptor request. - _device_set_actions(ext_hub_dev, DEV_ACTION_RELEASE); + + if ((ext_hub_dev->single_thread.maxchild == 0) && !ext_hub_dev->dynamic.flags.in_pending_list) { + // Not in pending list and doesn't have any children + ext_hub_dev->dynamic.flags.waiting_free = 1; + ext_hub_dev->dynamic.flags.waiting_release = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_RELEASE | + DEV_ACTION_FREE); + } else { + // If external Hub was configured and enabled, its EP In is active + // After detachment the Hub, the driver handles the EP In interrupt and added the device to the list + // Usually, when device was detached, device object is already in pending list + // with DEV_ACTION_EP1_DEQUEUE action. Then just add the RELEASE action + ext_hub_dev->dynamic.flags.waiting_release = 1; + call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_RELEASE); } EXT_HUB_EXIT_CRITICAL(); - // Device not in pending, can be released immediately - if (!in_pending) { - device_release(ext_hub_dev); + if (call_proc_req_cb) { + p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg); } ret = ESP_OK; @@ -1433,7 +1466,7 @@ esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl) 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); + EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); // 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, @@ -1467,9 +1500,6 @@ esp_err_t ext_hub_process(void) 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); } @@ -1540,7 +1570,7 @@ esp_err_t ext_hub_get_status(ext_hub_handle_t ext_hub_hdl) 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); 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; @@ -1552,7 +1582,8 @@ esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); - EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED || + ext_hub_dev->single_thread.state == EXT_HUB_STATE_RELEASED, ESP_ERR_INVALID_STATE); EXT_HUB_ENTER_CRITICAL(); if (ext_hub_dev->dynamic.flags.waiting_release) { @@ -1610,7 +1641,7 @@ exit: 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); @@ -1625,7 +1656,7 @@ esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); @@ -1640,7 +1671,7 @@ esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); @@ -1648,6 +1679,8 @@ esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) uint8_t port_idx = port_num - 1; EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED || + ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, ESP_ERR_INVALID_STATE); return p_ext_hub_driver->constant.port_driver->disable(ext_hub_dev->constant.ports[port_idx]); } @@ -1655,7 +1688,7 @@ esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num) 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_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); @@ -1663,96 +1696,82 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t port_idx = port_num - 1; EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED || + ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, ESP_ERR_INVALID_STATE); return p_ext_hub_driver->constant.port_driver->get_speed(ext_hub_dev->constant.ports[port_idx], speed); } -// ----------------------------------------------------------------------------- -// --------------------------- USB Chapter 11 ---------------------------------- -// ----------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- +// ---------------------------------- 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) +static esp_err_t ext_hub_control_request(ext_hub_dev_t *ext_hub_dev, uint8_t port1, uint8_t request, uint8_t feature) { - EXT_HUB_ENTER_CRITICAL(); - EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); - 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; + switch (request) { + case USB_B_REQUEST_HUB_GET_PORT_STATUS: + USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, port1); + transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_port_status_t); + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_PORT_STATUS; + break; + case USB_B_REQUEST_HUB_SET_PORT_FEATURE: + USB_SETUP_PACKET_INIT_SET_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port1, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_PORT_FEATURE; + break; + case USB_B_REQUEST_HUB_CLEAR_FEATURE: + USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port1, feature); + transfer->num_bytes = sizeof(usb_setup_packet_t); + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_PORT_FEATURE; + break; + default: + ESP_LOGE(EXT_HUB_TAG, "Request %X not supported", request); + return ESP_ERR_NOT_SUPPORTED; + } - EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); - EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - - 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_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE; 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, "[%d:%d] Set port feature %#x, failed to submit ctrl urb: %s", - ext_hub_dev->constant.dev_addr, - port_num, - feature, + ESP_LOGE(EXT_HUB_TAG, "Request %X, port %d, feature %d: failed to submit ctrl urb: %s", + request, port1, feature, esp_err_to_name(ret)); + device_error(ext_hub_dev); } return ret; } -esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature) +// ------------------------------------------------------------------------------------------------- +// ---------------------------------- Parent Request ----------------------------------------------- +// ------------------------------------------------------------------------------------------------- + +esp_err_t ext_hub_request(ext_port_hdl_t port_hdl, ext_port_parent_request_data_t *data, void *user_arg) { EXT_HUB_ENTER_CRITICAL(); EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED); EXT_HUB_EXIT_CRITICAL(); + EXT_HUB_CHECK(data != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG); - esp_err_t ret; - EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); + uint8_t port1; + ext_hub_handle_t ext_hub_hdl = (ext_hub_handle_t)ext_port_get_context(port_hdl); 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.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); - EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); + ESP_ERROR_CHECK(ext_port_get_port_num(port_hdl, &port1)); + EXT_HUB_CHECK(port1 != 0 && port1 <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); + EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED || + ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, ESP_ERR_INVALID_STATE); - 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); + switch (data->type) { + case EXT_PORT_PARENT_REQ_CONTROL: + return ext_hub_control_request(ext_hub_dev, port1, data->control.req, data->control.feature); - ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE; - 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, "[%d:%d] Clear port feature %#x, failed to submit ctrl urb: %s", - ext_hub_dev->constant.dev_addr, - port_num, - feature, - esp_err_to_name(ret)); + case EXT_PORT_PARENT_REQ_PROC_COMPLETED: + return ext_hub_status_handle_complete(ext_hub_hdl); + + default: + break; } - 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_NOT_ALLOWED); - 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.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE); - EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - - 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_dev->single_thread.stage = EXT_HUB_STAGE_PORT_STATUS_REQUEST; - 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, "[%d:%d] Get port status, failed to submit ctrl urb: %s", - ext_hub_dev->constant.dev_addr, - port_num, - esp_err_to_name(ret)); - } - return ret; + + ESP_LOGE(EXT_HUB_TAG, "Request type %d not supported", data->type); + return ESP_ERR_NOT_SUPPORTED; } diff --git a/components/usb/ext_port.c b/components/usb/ext_port.c index efce9ed7de..5fedc49ae3 100644 --- a/components/usb/ext_port.c +++ b/components/usb/ext_port.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -78,7 +78,7 @@ struct ext_port_s { usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ uint8_t parent_dev_addr; /**< Ports' parent device bus address */ // Port related constant members - ext_hub_handle_t ext_hub_hdl; /**< Ports' parent External Hub handle */ + void* context; /**< Ports' parent External Hub handle */ uint8_t port_num; /**< Ports' parent External Hub Port number */ int power_on_delay_ms; /**< Ports' Power on time to Power Good, ms */ } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ @@ -102,6 +102,8 @@ typedef struct { void *proc_req_cb_arg; /**< External Port process callback argument */ ext_port_event_cb_t event_cb; /**< External Port event callback */ void *event_cb_arg; /**< External Port event callback argument */ + ext_port_parent_request_cb_t hub_request_cb; /**< External Port Hub request callback */ + void *hub_request_cb_arg; /**< External Port Hub request callback argument */ } constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */ } ext_port_driver_t; @@ -225,17 +227,27 @@ static inline bool port_has_finished_reset(ext_port_t *ext_port) * - ESP_ERR_INVALID_ARG: The parent hub handle couldn't be NULL * - ESP_ERR_INVALID_SIZE: The port number should be in a range: [1, .. , bNbrPort] * - ESP_ERR_INVALID_STATE: The parent hub device wasn't configured + * - ESP_ERR_NOT_SUPPORTED: The request type is not supported by the External Hub Driver * - ESP_OK: Status has been requested */ static esp_err_t port_request_status(ext_port_t* ext_port) { - esp_err_t ret = ext_hub_get_port_status(ext_port->constant.ext_hub_hdl, ext_port->constant.port_num); + ext_port_parent_request_data_t data = { + .type = EXT_PORT_PARENT_REQ_CONTROL, + .control = { + .req = USB_B_REQUEST_HUB_GET_PORT_STATUS, + } + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb((ext_port_hdl_t)ext_port, + &data, + p_ext_port_driver->constant.hub_request_cb_arg); if (ret != ESP_OK) { return ret; } // Port is requesting status, lock the status ext_port->flags.status_lock = 1; - return ret; + return ESP_OK; } /** @@ -254,9 +266,17 @@ static esp_err_t port_request_status(ext_port_t* ext_port) */ static esp_err_t port_set_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature) { - esp_err_t ret = ext_hub_set_port_feature(ext_port->constant.ext_hub_hdl, - ext_port->constant.port_num, - feature); + ext_port_parent_request_data_t data = { + .type = EXT_PORT_PARENT_REQ_CONTROL, + .control = { + .req = USB_B_REQUEST_HUB_SET_PORT_FEATURE, + .feature = feature, + } + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb((ext_port_hdl_t)ext_port, + &data, + p_ext_port_driver->constant.hub_request_cb_arg); if (ret != ESP_OK) { return ret; } @@ -293,9 +313,17 @@ static esp_err_t port_set_feature(ext_port_t *ext_port, const usb_hub_port_featu */ static esp_err_t port_clear_feature(ext_port_t *ext_port, const usb_hub_port_feature_t feature) { - esp_err_t ret = ext_hub_clear_port_feature(ext_port->constant.ext_hub_hdl, - ext_port->constant.port_num, - feature); + ext_port_parent_request_data_t data = { + .type = EXT_PORT_PARENT_REQ_CONTROL, + .control = { + .req = USB_B_REQUEST_HUB_CLEAR_FEATURE, + .feature = feature, + } + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb((ext_port_hdl_t)ext_port, + &data, + p_ext_port_driver->constant.hub_request_cb_arg); if (ret != ESP_OK) { return ret; } @@ -388,7 +416,6 @@ static void port_event(ext_port_t *ext_port, ext_port_event_t event) }; switch (event) { case EXT_PORT_CONNECTED: - event_data.connected.ext_hub_hdl = ext_port->constant.ext_hub_hdl; event_data.connected.parent_dev_hdl = ext_port->constant.parent_dev_hdl; event_data.connected.parent_port_num = ext_port->constant.port_num; break; @@ -406,7 +433,7 @@ static void port_event(ext_port_t *ext_port, ext_port_event_t event) break; } - p_ext_port_driver->constant.event_cb(&event_data, p_ext_port_driver->constant.event_cb_arg); + p_ext_port_driver->constant.event_cb((ext_port_hdl_t)ext_port, &event_data, p_ext_port_driver->constant.event_cb_arg); } /** @@ -431,7 +458,7 @@ static ext_port_t *get_port_from_pending_list(void) * - Port state: USB_PORT_STATE_NOT_CONFIGURED * - Port device status: PORT_DEV_NOT_PRESENT * - * @param[in] ext_hub_hdl Ports' parent hub handle + * @param[in] context Ports' parent hub handle * @param[in] parent_dev_hdl Ports' parent device handle * @param[in] parent_port_num Ports' parent port number * @param[in] port_delay_ms Ports' Power on time to Power Good, ms @@ -442,10 +469,10 @@ static ext_port_t *get_port_from_pending_list(void) * - ESP_ERR_NOT_FINISHED: Unable to allocate the port object: parent device is not available * - ESP_OK: Port object created successfully */ -static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, uint16_t port_delay_ms, ext_port_t **port_obj) +static esp_err_t port_alloc(void* context, usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, uint16_t port_delay_ms, ext_port_t **port_obj) { uint8_t parent_dev_addr = 0; - EXT_PORT_CHECK(ext_hub_hdl != NULL && parent_dev_hdl != NULL, ESP_ERR_INVALID_ARG); + EXT_PORT_CHECK(context != NULL && parent_dev_hdl != NULL, ESP_ERR_INVALID_ARG); // This is the one exception from the requirement to use only the Ext Hub Driver API. // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH EXT_PORT_CHECK(usbh_dev_get_addr(parent_dev_hdl, &parent_dev_addr) == ESP_OK, ESP_ERR_NOT_FINISHED); @@ -457,7 +484,7 @@ static esp_err_t port_alloc(ext_hub_handle_t ext_hub_hdl, usb_device_handle_t pa ext_port->constant.parent_dev_hdl = parent_dev_hdl; ext_port->constant.parent_dev_addr = parent_dev_addr; - ext_port->constant.ext_hub_hdl = ext_hub_hdl; + ext_port->constant.context = context; ext_port->constant.port_num = parent_port_num; #if (EXT_PORT_POWER_ON_CUSTOM_DELAY) ext_port->constant.power_on_delay_ms = EXT_PORT_POWER_ON_CUSTOM_DELAY_MS; @@ -523,7 +550,7 @@ static esp_err_t handle_complete(ext_port_t *ext_port) // When multiply Hubs are attached, we can have ports in pending list, but for another parent ext_port_t *port = NULL; TAILQ_FOREACH(port, &p_ext_port_driver->single_thread.pending_tailq, tailq_entry) { - if (port->constant.ext_hub_hdl == ext_port->constant.ext_hub_hdl) { + if (port->constant.context == ext_port->constant.context) { // Port with same parent has been found all_ports_were_handled = false; break; @@ -537,7 +564,13 @@ static esp_err_t handle_complete(ext_port_t *ext_port) if (all_ports_were_handled) { // Notify parent to enable Interrupt EP - ext_hub_status_handle_complete(ext_port->constant.ext_hub_hdl); + ext_port_parent_request_data_t data = { + .type = EXT_PORT_PARENT_REQ_PROC_COMPLETED, + }; + + ESP_ERROR_CHECK(p_ext_port_driver->constant.hub_request_cb((ext_port_hdl_t)ext_port, + &data, + p_ext_port_driver->constant.hub_request_cb_arg)); } if (has_pending_ports) { @@ -684,7 +717,7 @@ static void handle_port_state(ext_port_t *ext_port) switch (curr_state) { case USB_PORT_STATE_NOT_CONFIGURED: new_state = USB_PORT_STATE_POWERED_OFF; - port_request_status(ext_port); + ESP_ERROR_CHECK(port_request_status(ext_port)); need_handling = true; break; case USB_PORT_STATE_POWERED_OFF: @@ -943,7 +976,7 @@ static esp_err_t port_new(void *port_cfg, void **port_hdl) ext_port_t *port = NULL; ext_port_config_t *config = (ext_port_config_t *)port_cfg; - esp_err_t ret = port_alloc(config->ext_hub_hdl, + esp_err_t ret = port_alloc(config->context, config->parent_dev_hdl, config->parent_port_num, config->port_power_delay_ms, @@ -980,6 +1013,7 @@ static esp_err_t port_recycle(void *port_hdl) EXT_PORT_CHECK(port_hdl != NULL, ESP_ERR_INVALID_ARG); ext_port_t *ext_port = (ext_port_t *) port_hdl; + EXT_PORT_CHECK(ext_port->flags.is_gone == 0, ESP_ERR_INVALID_STATE); ESP_LOGD(EXT_PORT_TAG, "Port %d request recycle, state=%d", ext_port->constant.port_num, ext_port->state); port_set_actions(ext_port, PORT_ACTION_RECYCLE); @@ -1285,7 +1319,7 @@ static esp_err_t port_req_process(void* port_hdl) /** * @brief External Port Driver API */ -const ext_hub_port_driver_t ext_port_driver = { +const ext_port_driver_api_t ext_port_driver = { .new = port_new, .reset = port_reset, .recycle = port_recycle, @@ -1302,6 +1336,10 @@ const ext_hub_port_driver_t ext_port_driver = { esp_err_t ext_port_install(const ext_port_driver_config_t *config) { EXT_PORT_CHECK(p_ext_port_driver == NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(config != NULL, ESP_ERR_INVALID_ARG); + EXT_PORT_CHECK(config->proc_req_cb != NULL, ESP_ERR_INVALID_ARG); + EXT_PORT_CHECK(config->event_cb != NULL, ESP_ERR_INVALID_ARG); + EXT_PORT_CHECK(config->hub_request_cb != NULL, ESP_ERR_INVALID_ARG); ext_port_driver_t *ext_port_drv = heap_caps_calloc(1, sizeof(ext_port_driver_t), MALLOC_CAP_DEFAULT); EXT_PORT_CHECK(ext_port_drv != NULL, ESP_ERR_NO_MEM); @@ -1311,6 +1349,8 @@ esp_err_t ext_port_install(const ext_port_driver_config_t *config) ext_port_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg; ext_port_drv->constant.event_cb = config->event_cb; ext_port_drv->constant.event_cb_arg = config->event_cb_arg; + ext_port_drv->constant.hub_request_cb = config->hub_request_cb; + ext_port_drv->constant.hub_request_cb_arg = config->hub_request_cb_arg; TAILQ_INIT(&ext_port_drv->single_thread.pending_tailq); p_ext_port_driver = ext_port_drv; @@ -1397,8 +1437,24 @@ esp_err_t ext_port_process(void) return ESP_OK; } -const ext_hub_port_driver_t *ext_port_get_driver(void) +const ext_port_driver_api_t *ext_port_get_driver(void) { EXT_PORT_CHECK(p_ext_port_driver != NULL, NULL); return &ext_port_driver; } + +void* ext_port_get_context(ext_port_hdl_t port_hdl) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, NULL); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + return ext_port->constant.context; +} + +esp_err_t ext_port_get_port_num(ext_port_hdl_t port_hdl, uint8_t *port1) +{ + EXT_PORT_CHECK(p_ext_port_driver != NULL, ESP_ERR_NOT_ALLOWED); + EXT_PORT_CHECK(port_hdl != NULL && port1 != NULL, ESP_ERR_INVALID_ARG); + ext_port_t *ext_port = (ext_port_t *)port_hdl; + *port1 = ext_port->constant.port_num; + return ESP_OK; +} diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 6d7398231d..36d8ab84de 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -392,7 +392,7 @@ static inline bool _buffer_check_done(pipe_t *pipe) } #if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) // The HW can't handle two transactions with preamble in one frame. - // TODO: IDF-15060 + // TODO: IDF-12986 if (pipe->ep_char.ls_via_fs_hub) { esp_rom_delay_us(1000); } @@ -1630,7 +1630,7 @@ static void pipe_set_ep_char(const hcd_pipe_config_t *pipe_config, usb_transfer_ ep_char->dev_addr = pipe_config->dev_addr; ep_char->ls_via_fs_hub = 0; if (pipe_idx > 0) { - // TODO: remove warning after IDF-15060 + // TODO: remove warning after IDF-12986 if (port_speed == USB_SPEED_FULL && pipe_config->dev_speed == USB_SPEED_LOW) { ESP_LOGW(HCD_DWC_TAG, "Low-speed, extra delay will be applied in ISR"); ep_char->ls_via_fs_hub = 1; diff --git a/components/usb/hub.c b/components/usb/hub.c index e39e741d69..67c9b0a53f 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,7 +19,7 @@ #include "usb/usb_helpers.h" #if ENABLE_USB_HUBS -#include "ext_port.h" +#include "ext_hub.h" #endif // ENABLE_USB_HUBS /* @@ -28,7 +28,6 @@ implement the bare minimum to control the root HCD port. */ #define HUB_ROOT_PORT_NUM 1 // HCD only supports one port -#define HUB_ROOT_DEV_UID 1 // Unique device ID #ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_RX @@ -93,7 +92,6 @@ typedef struct { struct { TAILQ_HEAD(tailhead_devs, dev_tree_node_s) dev_nodes_tailq; /**< Tailq of attached devices */ - unsigned int next_uid; /**< Unique ID for next upcoming device */ } single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */ struct { @@ -153,53 +151,52 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port * * @return esp_err_t */ -static esp_err_t new_dev_tree_node(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed) +static esp_err_t dev_tree_node_new(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, usb_speed_t speed) { esp_err_t ret; - unsigned int node_uid = p_hub_driver_obj->single_thread.next_uid; - + // Allocate memory for a new device tree node dev_tree_node_t *dev_tree_node = heap_caps_calloc(1, sizeof(dev_tree_node_t), MALLOC_CAP_DEFAULT); if (dev_tree_node == NULL) { return ESP_ERR_NO_MEM; } + // Assign initial UID based on the current number of registered devices + int device_num = 0; + ESP_ERROR_CHECK(usbh_devs_num(&device_num)); + dev_tree_node->uid = device_num + 1; + // Ensure the UID is unique + while (usbh_devs_is_uid_in_use(dev_tree_node->uid)) { + dev_tree_node->uid++; + assert(dev_tree_node->uid != 0); // No overflow possible + } - // Allocate a new USBH device + dev_tree_node->parent_dev_hdl = parent_dev_hdl; + dev_tree_node->parent_port_num = parent_port_num; + + // Initialize and register a new USBH Device with the assigned UID usbh_dev_params_t params = { - .uid = node_uid, + .uid = dev_tree_node->uid, .speed = speed, .root_port_hdl = p_hub_driver_obj->constant.root_port_hdl, // Always the same for all devices - // TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH + // TODO: IDF-10023 Move parent-child tree management responsibility to Hub Driver .parent_dev_hdl = parent_dev_hdl, .parent_port_num = parent_port_num, }; 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 + // Device registration may fail if there are no available HCD channels. + // TODO: IDF-10044 Implement hub recovery mechanism for running out of HCD channels. goto fail; } - dev_tree_node->uid = node_uid; - dev_tree_node->parent_dev_hdl = parent_dev_hdl; - dev_tree_node->parent_port_num = parent_port_num; TAILQ_INSERT_TAIL(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry); - p_hub_driver_obj->single_thread.next_uid++; - if (p_hub_driver_obj->single_thread.next_uid == 0) { - ESP_LOGW(HUB_DRIVER_TAG, "Counter overflowed, possibility of uid collisions"); - p_hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID; - } - // Verify presence of a device with newly prepared uid in USBH - // TODO: IDF-10022 Provide a mechanism to request presence status of a device with uid in USBH device object list - // Return if device uid is not in USBH device object list, repeat until uid will be founded - - ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): new", node_uid); + ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): new", dev_tree_node->uid); hub_event_data_t event_data = { .event = HUB_EVENT_CONNECTED, .connected = { - .uid = node_uid, + .uid = dev_tree_node->uid, }, }; p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg); @@ -328,14 +325,16 @@ static void ext_port_callback(void *user_arg) p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg); } -static void ext_port_event_callback(ext_port_event_data_t *event_data, void *arg) +static void ext_port_event_callback(ext_port_hdl_t port_hdl, ext_port_event_data_t *event_data, void *arg) { + ext_hub_handle_t ext_hub_hdl = (ext_hub_handle_t) ext_port_get_context(port_hdl); + switch (event_data->event) { case EXT_PORT_CONNECTED: // First reset is done by ext_port logic usb_speed_t port_speed; - if (ext_hub_port_get_speed(event_data->connected.ext_hub_hdl, + if (ext_hub_port_get_speed(ext_hub_hdl, event_data->connected.parent_port_num, &port_speed) != ESP_OK) { goto new_ds_dev_err; @@ -354,23 +353,13 @@ static void ext_port_event_callback(ext_port_event_data_t *event_data, void *arg } } -#if (!CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS) - if (port_speed == USB_SPEED_LOW) { - ESP_LOGE(HUB_DRIVER_TAG, "Connected %s-speed device, not supported", - (char *[]) { - "Low", "Full", "High" - }[port_speed]); - goto new_ds_dev_err; - } -#endif // CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS - - if (new_dev_tree_node(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) { + if (dev_tree_node_new(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new downstream device"); goto new_ds_dev_err; } break; new_ds_dev_err: - ext_hub_port_disable(event_data->connected.ext_hub_hdl, event_data->connected.parent_port_num); + ext_hub_port_disable(ext_hub_hdl, event_data->connected.parent_port_num); break; case EXT_PORT_RESET_COMPLETED: ESP_ERROR_CHECK(dev_tree_node_reset_completed(event_data->reset_completed.parent_dev_hdl, event_data->reset_completed.parent_port_num)); @@ -406,7 +395,7 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) goto new_dev_err; } - if (new_dev_tree_node(NULL, 0, speed) != ESP_OK) { + if (dev_tree_node_new(NULL, 0, speed) != ESP_OK) { ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new device"); goto new_dev_err; } @@ -548,6 +537,7 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) ext_port_driver_config_t ext_port_config = { .proc_req_cb = ext_port_callback, .event_cb = ext_port_event_callback, + .hub_request_cb = ext_hub_request, }; ret = ext_port_install(&ext_port_config); if (ret != ESP_OK) { @@ -589,7 +579,6 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret) hub_driver_obj->constant.proc_req_cb_arg = hub_config->proc_req_cb_arg; hub_driver_obj->constant.event_cb = hub_config->event_cb; hub_driver_obj->constant.event_cb_arg = hub_config->event_cb_arg; - hub_driver_obj->single_thread.next_uid = HUB_ROOT_DEV_UID; TAILQ_INIT(&hub_driver_obj->single_thread.dev_nodes_tailq); // Driver is not installed, we can modify dynamic section outside of the critical section hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED; diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h index 93432542a7..be95dbefe3 100644 --- a/components/usb/private_include/ext_hub.h +++ b/components/usb/private_include/ext_hub.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,10 +13,7 @@ #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 +#include "ext_port.h" #ifdef __cplusplus extern "C" { @@ -34,32 +31,13 @@ typedef struct ext_hub_s *ext_hub_handle_t; */ 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 (*del)(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); - esp_err_t (*req_process)(void *port_hdl); -} 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 */ + const ext_port_driver_api_t* port_driver; /**< External Port Driver */ } ext_hub_config_t; // ------------------------------ Driver --------------------------------------- @@ -127,7 +105,7 @@ esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_ * * @return * - ESP_OK: New device added successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed, or not in correct state to add a new device + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed */ esp_err_t ext_hub_new_dev(uint8_t dev_addr); @@ -141,7 +119,7 @@ esp_err_t ext_hub_new_dev(uint8_t dev_addr); * * @return * - ESP_OK: Device freed successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_NOT_FOUND: Device address not found */ @@ -182,7 +160,7 @@ esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl); * * @return * - ESP_OK: All events handled - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed */ esp_err_t ext_hub_process(void); @@ -199,10 +177,11 @@ esp_err_t ext_hub_process(void); * * @return * - ESP_OK: The port can be recycled - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range * - ESP_ERR_NOT_SUPPORTED: External Port Driver has not been installed + * - ESP_ERR_INVALID_STATE: External Hub wasn't configured or suspended */ esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); @@ -214,7 +193,7 @@ esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); * * @return * - ESP_OK: The port can be reset - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range * - ESP_ERR_NOT_SUPPORTED: External Port Driver has not been installed @@ -229,7 +208,7 @@ esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); * * @return * - ESP_OK: Device on the External hub's port has been enumerated - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range * - ESP_ERR_NOT_SUPPORTED: External Port Driver has not been installed @@ -244,10 +223,11 @@ esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); * * @return * - ESP_OK: Device on the External hub's port can be disabled - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range * - ESP_ERR_NOT_SUPPORTED: External Port Driver has not been installed + * - ESP_ERR_INVALID_STATE: External Hub wasn't configured or suspended */ esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); @@ -260,61 +240,34 @@ esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); * * @return * - ESP_OK: The device's speed obtained successfully - * - ESP_ERR_INVALID_STATE: External Hub driver is not installed + * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed * - ESP_ERR_INVALID_ARG: Invalid argument * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range * - ESP_ERR_NOT_SUPPORTED: External Port Driver has not been installed + * - ESP_ERR_INVALID_STATE: External Hub wasn't configured or suspended */ 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 + * @brief USB Hub Request * - * @param[in] ext_hub_hdl External Hub device handle - * @param[in] port_num Port number - * @param[in] feature Port Feature to set + * @note This function designed to be called from the External Port driver + * + * @param[in] port_hdl External Port handle + * @param[in] data Request data + * @param[in] user_arg User argument * * @return - * - ESP_OK: Port's feature set successfully - * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed - * - ESP_ERR_INVALID_ARG: Invalid argument - * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range + * - ESP_ERR_NOT_ALLOWED if External Hub Driver is not installed + * - ESP_ERR_INVALID_ARG if invalid argument + * - ESP_ERR_INVALID_SIZE if port number is out of the hub's range + * - ESP_ERR_INVALID_STATE if the hub device wasn't configured + * - ESP_ERR_NOT_SUPPORTED if the request is not supported + * - ESP_OK if control transfer was successfully submitted */ -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_OK: Port's feature cleared successfully - * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed - * - ESP_ERR_INVALID_ARG: Invalid argument - * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range - */ -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_OK: Port's status obtained successfully - * - ESP_ERR_NOT_ALLOWED: External Hub driver is not installed - * - ESP_ERR_INVALID_ARG: Invalid argument - * - ESP_ERR_INVALID_SIZE: External Hub port number out of the hub's range - */ -esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); +esp_err_t ext_hub_request(ext_port_hdl_t port_hdl, ext_port_parent_request_data_t *data, void *user_arg); #ifdef __cplusplus } diff --git a/components/usb/private_include/ext_port.h b/components/usb/private_include/ext_port.h index 5795e41575..1758b0c140 100644 --- a/components/usb/private_include/ext_port.h +++ b/components/usb/private_include/ext_port.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,15 +7,38 @@ #include #include "esp_err.h" -#include "ext_hub.h" #include "usb/usb_types_stack.h" +#include "sdkconfig.h" #ifdef __cplusplus extern "C" { #endif +#if CONFIG_USB_HOST_HUB_MULTI_LEVEL +#define ENABLE_MULTIPLE_HUBS 1 +#endif // CONFIG_USB_HOST_HUB_MULTI_LEVEL + typedef struct ext_port_s *ext_port_hdl_t; +// ------------------------ 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 (*del)(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); + esp_err_t (*req_process)(void *port_hdl); +} ext_port_driver_api_t; + // ------------------------------ Events --------------------------------------- typedef enum { EXT_PORT_CONNECTED = 0x01, /**< Port has a device connection event */ @@ -30,7 +53,6 @@ typedef struct { ext_port_event_t event; union { struct { - ext_hub_handle_t ext_hub_hdl; /**< Ports' parent external Hub handle */ usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ uint8_t parent_port_num; /**< Ports' parent port number */ } connected; /**< EXT_PORT_CONNECTED event specific data */ @@ -47,6 +69,24 @@ typedef struct { }; } ext_port_event_data_t; +typedef enum { + EXT_PORT_PARENT_REQ_CONTROL = 0x01, /** Port requires action from the parent Hub */ + EXT_PORT_PARENT_REQ_PROC_COMPLETED /** All ports were handled */ +} ext_port_parent_request_type_t; + +/** + * @brief Specific data for External Port parent Hub class specific request + */ +typedef struct { + ext_port_parent_request_type_t type; + union { + struct { + usb_hub_class_request_t req; + uint8_t feature; + } control; + }; +} ext_port_parent_request_data_t; + // ------------------------------ Callbacks ------------------------------------ /** * @brief Callback used to indicate that the External Port Driver requires process callback @@ -60,7 +100,12 @@ typedef void (*ext_port_cb_t)(void *user_arg); * * @note For the Hub Driver only */ -typedef void (*ext_port_event_cb_t)(ext_port_event_data_t *event_data, void *arg); +typedef void (*ext_port_event_cb_t)(ext_port_hdl_t port_hdl, ext_port_event_data_t *event_data, void *arg); + +/** + * @brief Callback used to indicate that the External Port driver requires a Hub class specific request + */ +typedef esp_err_t (*ext_port_parent_request_cb_t)(ext_port_hdl_t port_hdl, ext_port_parent_request_data_t *data, void *user_arg); // ----------------- External Port Driver configuration ------------------------ @@ -72,6 +117,8 @@ typedef struct { void *proc_req_cb_arg; /**< External Port process callback argument */ ext_port_event_cb_t event_cb; /**< External Port event callback */ void *event_cb_arg; /**< External Port event callback argument */ + ext_port_parent_request_cb_t hub_request_cb;/**< External Port Hub request callback */ + void *hub_request_cb_arg; /**< External Port Hub request callback argument */ } ext_port_driver_config_t; /** @@ -80,7 +127,7 @@ typedef struct { * Structure is used to create new port */ typedef struct { - ext_hub_handle_t ext_hub_hdl; /**< Ports' parent external Hub handle */ + void* context; /**< Ports' parent external Hub handle */ usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */ uint8_t parent_port_num; /**< Ports' parent port number */ uint16_t port_power_delay_ms; /**< Ports' Power on time to Power Good, ms */ @@ -136,7 +183,28 @@ esp_err_t ext_port_process(void); * - NULL: The Driver has not been installed * - not NULL: Pointer to the External Port Driver API */ -const ext_hub_port_driver_t *ext_port_get_driver(void); +const ext_port_driver_api_t *ext_port_get_driver(void); + +/** + * @brief Returns External Port Driver's context + * + * @param[in] port_hdl Port object handle + * @return + * - Pointer to the External Port Driver context + */ +void* ext_port_get_context(ext_port_hdl_t port_hdl); + +/** + * @brief Returns External Port's port number + * + * @param[in] port_hdl Port object handle + * @param[out] port1 Port number, starting from 1 + * @return + * - ESP_ERR_NOT_ALLOWED if the External Port Driver has not been installed + * - ESP_ERR_INVALID_ARG if the Port handle is NULL + * - ESP_OK if the port number was successfully returned + */ +esp_err_t ext_port_get_port_num(ext_port_hdl_t port_hdl, uint8_t *port1); #ifdef __cplusplus } diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 7777acd6e2..a07ff44767 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -194,6 +194,18 @@ esp_err_t usbh_uninstall(void); esp_err_t usbh_process(void); // ---------------------- Device Pool Functions -------------------------------- +/** + * @brief Determines whether a UID is currently assigned in the USBH device list + * + * @note This function may block execution while checking the device list. + * + * @param[in] uid Unique ID to check + * + * @return + * - true if UID is already in use. + * - false if UID is available for assignment. + */ +bool usbh_devs_is_uid_in_use(uint32_t uid); /** * @brief Get the current number of devices diff --git a/components/usb/usbh.c b/components/usb/usbh.c index 54355ace68..cb047b4279 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -781,6 +781,14 @@ esp_err_t usbh_process(void) // ----------------------------------------------------------------------------- // ------------------------- Device Pool Functions ----------------------------- // ----------------------------------------------------------------------------- +bool usbh_devs_is_uid_in_use(uint32_t uid) +{ + bool uid_in_use; + USBH_ENTER_CRITICAL(); + uid_in_use = (_find_dev_from_uid(uid) != NULL); // Check if UID exists + USBH_EXIT_CRITICAL(); + return uid_in_use; +} esp_err_t usbh_devs_num(int *num_devs_ret) { diff --git a/docs/en/api-reference/peripherals/usb_host.rst b/docs/en/api-reference/peripherals/usb_host.rst index b5d05eee68..d90b6dc819 100644 --- a/docs/en/api-reference/peripherals/usb_host.rst +++ b/docs/en/api-reference/peripherals/usb_host.rst @@ -47,7 +47,6 @@ Currently, the Host Library and the underlying Host Stack has the following limi - Only supports using one configuration. Changing to other configurations after enumeration is not supported yet. - Transfer timeouts are not supported yet. :esp32p4: - {IDF_TARGET_NAME} contains two USB-OTG peripherals USB 2.0 OTG High-Speed and USB 2.0 OTG Full-Speed. Only the High-Speed instance is supported now. - - The External Hub Driver: Supports only devices with the same speed as upstream port speed (e.g., Low-speed device won't work through Full-speed external Hub). - The External Hub Driver: Remote Wakeup feature is not supported (External Hubs are active, even if there are no devices inserted). - The External Hub Driver: Doesn't handle error cases (overcurrent handling, errors during initialization etc. are not implemented yet). - The External Hub Driver: No Interface selection. The Driver uses the first available Interface with Hub Class code (09h). diff --git a/docs/zh_CN/api-reference/peripherals/usb_host.rst b/docs/zh_CN/api-reference/peripherals/usb_host.rst index 1c55f82cff..61350c9c5d 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host.rst @@ -47,7 +47,6 @@ USB 主机库(以下简称主机库)是 USB 主机栈的最底层,提供 - 仅支持使用发现的首个配置,尚不支持变更为其他配置。 - 尚不支持传输超时。 :esp32p4: - {IDF_TARGET_NAME} 包含两个 USB-OTG 外设:USB 2.0 OTG 高速和 USB 2.0 OTG 全速。目前仅支持高速实例。 - - 外部 Hub 驱动:仅支持与上游端口速率相同的设备。(例如,低速设备无法通过全速外部 Hub 工作。) - 外部 Hub 驱动:不支持远程唤醒功能(即使没有设备插入,外部 Hub 也处于工作状态)。 - 外部 Hub 驱动:不处理错误用例(尚未实现过流处理、初始化错误等功能)。 - 外部 Hub 驱动:不支持接口选择。驱动程序使用具有 Hub 类代码 (09h) 的第一个可用接口。