From cfaf175cfaff88f1029fa093c81d894e6ebbc944 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Mon, 13 Jan 2025 10:11:31 +0100 Subject: [PATCH 1/6] docs(usb/host): Update maintainer docs for ESP32-P4 (partial, only the common part of the doc) --- .../usb_host/usb_host_notes_arch.rst | 2 +- .../usb_host/usb_host_notes_dwc_otg.rst | 63 +++++++++++++------ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_arch.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_arch.rst index 1770120b00..ac6e7ff008 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_arch.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_arch.rst @@ -37,7 +37,7 @@ The layers of the Host Stack are described in the following table. The layers ar - ``usb_host.h``, ``usb_host.c`` - The USB Host Library layer is the lowest public API layer of the Host Stack and presents the concept of USB Host Clients. The abstraction of clients allows for multiple class drivers to coexist simultaneously (where each class roughly maps to a single client) and also acts as a mechanism for division of labor (where each client is responsible for its own processing and event handling). * - Host Class Drivers - - See the `ESP-IDF Extra Components repository `_ or the USB Host examples in ESP-IDF (via :example:`peripherals/usb/host`). + - See the `ESP-USB repository `_ or the USB Host examples in ESP-IDF (via :example:`peripherals/usb/host`). - The Host Class Drivers implement the host side of a particular device class (e.g., CDC, MSC, HID). The exposed API is specific to each class driver. Layer Dependencies diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst index 3415e02007..e54dbd8c61 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_dwc_otg.rst @@ -89,26 +89,51 @@ Hardware Configuration The DWC_OTG IP is configurable. The notable Host related configurations of the {IDF_TARGET_NAME}'s DWC_OTG are listed below: -.. list-table:: {IDF_TARGET_NAME}'s DWC_OTG Configuration - :widths: 70 30 - :header-rows: 1 +.. only:: esp32p4 - * - Description - - Configuration - * - Host and Device Mode support with OTG - - ``OTG_MODE = 0`` - * - Full Speed (FS) and Low Speed (LS) support - - ``OTG_FSPHY_INTERFACE = 1``, ``OTG_HSPHY_INTERFACE = 0`` - * - Internal DMA controller with Scatter/Gather DMA - - ``OTG_ARCHITECTURE = 2``, ``OTG_EN_DESC_DMA = 1`` - * - FS Hubs are supported but HS Hub are not (i.e., split transfers not supported) - - ``OTG_SINGLE_POINT = 0`` - * - 8 Host Mode channels - - ``OTG_NUM_HOST_CHAN = 8`` - * - All transfer types supported, including ISOC and INTR OUT transfers - - ``OTG_EN_PERIO_HOST = 1`` - * - Dynamically sized Data FIFO of 1024 bytes (256 lines) - - ``OTG_DFIFO_DYNAMIC = 1``, ``OTG_DFIFO_DEPTH = 256`` + .. list-table:: {IDF_TARGET_NAME}'s DWC_OTG Configuration + :widths: 70 30 + :header-rows: 1 + + * - Description + - Configuration + * - Host and Device Mode support with OTG + - ``OTG_MODE = 0`` + * - High Speed (HS), Full Speed (FS) and Low Speed (LS) support + - ``OTG_FSPHY_INTERFACE = 2``, ``OTG_HSPHY_INTERFACE = 3`` + * - Internal DMA controller with Scatter/Gather DMA + - ``OTG_ARCHITECTURE = 2``, ``OTG_EN_DESC_DMA = 1`` + * - Split transfers not supported + - ``OTG_SINGLE_POINT = 1`` + * - 16 Host Mode channels + - ``OTG_NUM_HOST_CHAN = 16`` + * - All transfer types supported, including ISOC and INTR OUT transfers + - ``OTG_EN_PERIO_HOST = 1`` + * - Dynamically sized Data FIFO of 4096 bytes (1024 lines) + - ``OTG_DFIFO_DYNAMIC = 1``, ``OTG_DFIFO_DEPTH = 1024`` + * - Only 4 periodic and 4 non-periodic transactions per microframe + - ``OTG_NPERIO_TX_QUEUE_DEPTH = 4``, ``OTG_PERIO_TX_QUEUE_DEPTH = 4`` + +.. only:: esp32s2 or esp32s3 + + .. list-table:: {IDF_TARGET_NAME}'s DWC_OTG Configuration + :widths: 70 30 + :header-rows: 1 + + * - Description + - Configuration + * - Host and Device Mode support with OTG + - ``OTG_MODE = 0`` + * - Full Speed (FS) and Low Speed (LS) support + - ``OTG_FSPHY_INTERFACE = 1``, ``OTG_HSPHY_INTERFACE = 0`` + * - Internal DMA controller with Scatter/Gather DMA + - ``OTG_ARCHITECTURE = 2``, ``OTG_EN_DESC_DMA = 1`` + * - 8 Host Mode channels + - ``OTG_NUM_HOST_CHAN = 8`` + * - All transfer types supported, including ISOC and INTR OUT transfers + - ``OTG_EN_PERIO_HOST = 1`` + * - Dynamically sized Data FIFO of 1024 bytes (256 lines) + - ``OTG_DFIFO_DYNAMIC = 1``, ``OTG_DFIFO_DEPTH = 256`` Scatter/Gather DMA Transfer --------------------------- From 10bdcad7dbd40ccb46e11e4896cb710cc3ce62e9 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Thu, 21 Nov 2024 19:48:44 +0800 Subject: [PATCH 2/6] docs(ext_port): Maintainers Notes for the External Port Driver --- docs/_static/usb_host/poweron-timings.png | Bin 39711 -> 39353 bytes docs/conf_common.py | 1 + .../en/api-reference/peripherals/usb_host.rst | 76 ++++++++++++--- .../usb_host/usb_host_notes_ext_port.rst | 85 +++++++++++++++++ .../usb_host/usb_host_notes_index.rst | 20 +--- .../api-reference/peripherals/usb_host.rst | 89 +++++++++++++++--- .../usb_host/usb_host_notes_ext_port.rst | 1 + 7 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst create mode 100644 docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst diff --git a/docs/_static/usb_host/poweron-timings.png b/docs/_static/usb_host/poweron-timings.png index 867253da18d734fef4d86af9f0628431169986df..59973ec9cef28b29a8e387e4dccb3622ff80b4b1 100644 GIT binary patch literal 39353 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 R2?J0-c)I$ztaD0e0s!*YYyAKK literal 39711 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 F0RZNjWkUb} diff --git a/docs/conf_common.py b/docs/conf_common.py index f7c41aea9f..cf9a5c6203 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -122,6 +122,7 @@ USB_DOCS = ['api-reference/peripherals/usb_device.rst', 'api-reference/peripherals/usb_host/usb_host_notes_usbh.rst', 'api-reference/peripherals/usb_host/usb_host_notes_enum.rst', 'api-reference/peripherals/usb_host/usb_host_notes_ext_hub.rst', + 'api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst', 'api-guides/usb-otg-console.rst', 'api-guides/dfu.rst'] diff --git a/docs/en/api-reference/peripherals/usb_host.rst b/docs/en/api-reference/peripherals/usb_host.rst index 55782c0d46..ce17f90851 100644 --- a/docs/en/api-reference/peripherals/usb_host.rst +++ b/docs/en/api-reference/peripherals/usb_host.rst @@ -25,18 +25,32 @@ Features & Limitations The Host Library has the following features: -- Supports Full Speed (FS) and Low Speed (LS) Devices. -- Supports all four transfer types, i.e., Control, Bulk, Interrupt, and Isochronous. -- Allows multiple class drivers to run simultaneously, i.e., multiple clients of the Host Library. -- A single device can be used by multiple clients simultaneously, e.g., composite devices. -- The Host Library itself and the underlying Host Stack does not internally instantiate any OS tasks. The number of tasks is entirely controlled by how the Host Library interface is used. However, a general rule of thumb regarding the number of tasks is ``(the number of host class drivers running + 1)``. +.. list:: + + :esp32s2 or esp32s3: - Supports Full Speed (FS) and Low Speed (LS) Devices. + :esp32p4: - Supports High Speed (HS), Full Speed (FS) and Low Speed (LS) Devices. + - Supports all four transfer types: Control, Bulk, Interrupt, and Isochronous. + :esp32p4: - Supports High-Bandwidth Isochronous endpoints. + - Allows multiple class drivers to run simultaneously, i.e., multiple clients of the Host Library. + - A single device can be used by multiple clients simultaneously, e.g., composite devices. + - The Host Library itself and the underlying Host Stack does not internally instantiate any OS tasks. The number of tasks is entirely controlled by how the Host Library interface is used. However, a general rule of thumb regarding the number of tasks is ``(the number of host class drivers running + 1)``. + - Allows single Hub support (If option :ref:`CONFIG_USB_HOST_HUBS_SUPPORTED` is enabled). + - Allows multiple Hubs support (If option :ref:`CONFIG_USB_HOST_HUB_MULTI_LEVEL` is enabled). Currently, the Host Library and the underlying Host Stack has the following limitations: -- Only supports a single device, but the Host Library's API is designed for multiple device support. -- Only supports Asynchronous transfers. -- Only supports using the first configuration found. Changing to other configurations is not supported yet. -- Transfer timeouts are not supported yet. +.. list:: + + - Only supports Asynchronous transfers. + - 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). + - The External Port Driver: No downstream port debounce mechanism (not implemented yet) + :esp32p4: - The External Hub Driver: No Transaction Translator layer (No FS/LS Devices support when a Hub is attached to HS Host). .. -------------------------------------------------- Architecture ----------------------------------------------------- @@ -89,7 +103,7 @@ Therefore, in addition to the client tasks, the Host Library also requires a tas Devices ^^^^^^^ -The Host Library shields clients from the details of device handling, encompassing details such as connection, memory allocation, and enumeration. The clients are provided only with a list of already connected and enumerated devices to choose from. During enumeration, each device is automatically configured to use the first configuration found, namely, the first configuration descriptor returned on a Get Configuration Descriptor request. For most standard devices, the first configuration will have a ``bConfigurationValue`` of ``1``. +The Host Library shields clients from the details of device handling, encompassing details such as connection, memory allocation, and enumeration. The clients are provided only with a list of already connected and enumerated devices to choose from. By default during enumeration, each device is automatically configured to use the first configuration found, namely, the first configuration descriptor returned on a Get Configuration Descriptor request. For most standard devices, the first configuration will have a ``bConfigurationValue`` of ``1``. If option :ref:`CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK` is enabled, a different ``bConfigurationValue`` can be selected, see `Multiple Configuration Support`_ for more details. It is possible for two or more clients to simultaneously communicate with the same device as long as they are not communicating to the same interface. However, multiple clients can simultaneously communicate with the same device's default endpoint (i.e., EP0), which will result in their control transfers being serialized. @@ -158,8 +172,8 @@ Lifecycle The graph above illustrates the typical lifecycle of the Host Library with multiple clients and devices. Specifically, the example involves: -- two registered clients (Client 1 and Client 2). -- two connected devices (Device 1 and Device 2), where Client 1 communicates with Device 1 and Client 2 communicates with Device 2. +- Two registered clients (Client 1 and Client 2). +- Two connected devices (Device 1 and Device 2), where Client 1 communicates with Device 1 and Client 2 communicates with Device 2. With reference to the graph above, the typical lifecycle involves the following key stages. @@ -431,6 +445,44 @@ Configurable parameters of the USB host stack can be configured with multiple op * For reset recovery interval, refer to :ref:`CONFIG_USB_HOST_RESET_RECOVERY_MS`. * For ``SetAddress()`` recovery interval, refer to :ref:`CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS`. +Downstream Port Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When external Hubs feature is supported, there are several parameters which could be configured for the external Hubs port. + +Each external Hub has a Hub Descriptor which describes the device characteristics. + +.. note:: + + For detailed information about Hub Descriptor, please refer to `USB 2.0 Specification `_ > Chapter 11.23.2.1 *Hub Descriptor*. + +Configurable parameters of the downstream port can be configured with multiple options via Menuconfig. + +* For custom value to stabilize the power after powering on the port (PwrOn2PwrGood value), refer to :ref:`CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS`. +* For reset recovery interval, refer to :ref:`CONFIG_USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS`. + +.. note:: + + The specification claims, that for a hub with no power switches, PwrOn2PwrGood must be set to zero. Meanwhile, for some devices, this value could be increased to give extra time for device to power-up. To enable this feature, refer to :ref:`CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE`. + +Host Channels +""""""""""""" + +When external Hubs support feature is enabled (:ref:`CONFIG_USB_HOST_HUBS_SUPPORTED`), the amount of Host channels plays important role, as each downstream device requires vacant channel. + +To handle each attached device, different amount of channels are required. This amount does depend on the device class (EPs number). + +Supported amount of channels for {IDF_TARGET_NAME} is {OTG_NUM_HOST_CHAN}. + +.. note:: + + - One free channel is required to enumerate the device. + + - From 1 to N (when N - number of EPs) free channels are required to claim the interface. + + - When there are no more free Host channels available, the device could not be enumerated and its interface cannot be claimed. + + Multiple Configuration Support ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst new file mode 100644 index 0000000000..1dc74d90e2 --- /dev/null +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst @@ -0,0 +1,85 @@ +USB Host External Port Driver (Ext Port) +======================================== + +Introduction +------------ + +The External Port Driver (henceforth referred to as Ext Port Driver) isolates the handling process for downstream facing ports, which are provided by the Ext Hub Driver. + +.. note:: + + For more detailed information, please refer to `USB 2.0 Specification `_ > Chapter 11.5 **Downstream Facing Ports**. + +Requirements +------------ + +Host Stack Requirements +^^^^^^^^^^^^^^^^^^^^^^^ + +The Ext Port Driver takes into consideration the requirements set for the overall Host Stack (see :doc:`./usb_host_notes_design`): + +- The Ext Port Driver must not instantiate any tasks/threads +- The Ext Port Driver must be event driven, providing event callbacks and an event processing function +- The Ext Port Driver must use only API from underlying layer (The Ext Hub Driver) + +Implementation & Usage +---------------------- + +Host Stack Interaction +^^^^^^^^^^^^^^^^^^^^^^ + +The Ext Port Driver is a part of The Ext Hub Driver, so the interaction and hierarchical place in USB Host Stack is the same as for the Ext Hub Driver. The Ext Hub and the Ext Port Drivers were split into two Drivers to achieve the goal of logic distinguishing between external Hubs and Downstream Facing Ports handling. + +Ports handling +^^^^^^^^^^^^^^^ + +The Ext Port Driver can be installed via ``ext_port_install()`` call and uninstalled via ``ext_port_uninstall()`` call. + +After installation, the Ext Port Driver API could be requested via ``ext_port_get_driver()`` call. + +The Ext Port Driver API +----------------------- + +The Ext Port Driver provides an API, which could be split into three groups: object control, device control and general. + +The Ext Port Driver: Object Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Create object + - Delete object + +The Ext Port Driver: Port Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Reset + - Disable + - Recycle + - Activate + - Get Speed + - Get status + - Set status + - Gone + +The Ext Port Driver: General Purpose +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Request processing + +Events & Processing +------------------- + +The Ext Port Driver is completely event driven and all event handling is done via the ``ext_port_process()`` function. The ``ext_port_driver_config_t.proc_req_cb`` callback provided on the Ext Port Driver installation will be called when processing is required. Typically, ``ext_port_process()`` will be called from the Hub Driver ``hub_process()`` processing function. + +The Ext Port Driver exposes the following events via ``ext_port_driver_config_t.event_cb``: + +- ``EXT_PORT_CONNECTED`` Downstream facing port has a device connection event +- ``EXT_PORT_RESET_COMPLETED`` Downstream facing port has a device and completed the port reset +- ``EXT_PORT_DISCONNECTED`` Downstream facing port has a device disconnection event + +The Ext Port Driver ports processing is based on the Hub class-specific request ``Get Port Status``. + +After successful completion of the class-specific request ``Get Port Status`` and setting the new port status, the Ext Port Driver continues the port handling while it is required by ports' state and status. + +.. note:: + + For more detailed information, please refer to `USB 2.0 Specification `_ > Chapter 11.24.2.7 **Get Port Status** diff --git a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst index 74d4bac7f2..60d4a23db8 100644 --- a/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst +++ b/docs/en/api-reference/peripherals/usb_host/usb_host_notes_index.rst @@ -23,6 +23,7 @@ This document is split into the following sections: usb_host_notes_usbh usb_host_notes_enum usb_host_notes_ext_hub + usb_host_notes_ext_port Todo: @@ -39,22 +40,3 @@ Introduction The ESP-IDF USB Host Stack allows the {IDF_TARGET_NAME} to operate as a USB Host. Operating as a USB Host allows the {IDF_TARGET_NAME} to communicate with a wide range of USB devices. However, most USB Host Stack implementations do not run on embedded hardware (i.e., runs on PCs and smartphones), thus have comparatively more resources (i.e., memory and CPU speed). The implementation of the ESP-IDF USB Host Stack (henceforth referred to as the Host Stack) takes into account the embedded nature of the {IDF_TARGET_NAME} which is reflected in various aspects of the Host Stack's design. - -Features & Limitations -^^^^^^^^^^^^^^^^^^^^^^ - -**The Host Stack currently supports the following notable features:** - -.. only:: esp32p4 - - - Supports HS (High Speed) - -- Supports FS (Full Speed) and LS (Low Speed) devices -- Supports all transfer types (Control, Bulk, Isochronous, and Interrupt) -- Automatically enumerates connected devices -- Allows multiple class drivers (i.e., Clients of the USB Host Library) to run simultaneously and share the same device (i.e., composite devices). - -**The Host Stack currently has the following notable limitations:** - -- No Hub support (currently only supports a single device) - diff --git a/docs/zh_CN/api-reference/peripherals/usb_host.rst b/docs/zh_CN/api-reference/peripherals/usb_host.rst index dc0881d227..ff20dbd683 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host.rst @@ -18,25 +18,39 @@ USB 主机库(以下简称主机库)是 USB 主机栈的最底层,提供 然而,由于以下的某些原因(但不仅限于此),有时你可能需要直接使用主机库: - 需要实现自定义主机 Class 驱动程序 -- 需要更低级别的抽象 +- 需要更低级别的 USB 主机 API 特性和限制 ^^^^^^^^^^^^^^^^^^^^^^ 主机库具有以下特性: -- 支持全速 (FS) 和低速 (LS) 设备。 -- 支持四种传输类型,即控制传输、块传输、中断传输和同步传输。 -- 支持多个 Class 驱动程序同时运行,即主机的多个客户端同时运行。 -- 单个设备可以由多个客户端同时使用,如复合设备。 -- 主机库及其底层主机栈不会在内部自动创建操作系统任务,任务数量完全由主机库接口的使用方式决定。一般来说,任务数量为 ``(运行中的主机 Class 驱动程序数量 + 1)``。 +.. list:: + + :esp32s2 or esp32s3: - 支持全速 (FS) 和低速 (LS) 设备。 + :esp32p4: - 支持高速 (HS)、全速 (FS) 和低速 (LS) 设备。 + - 支持四种传输类型,即控制传输、块传输、中断传输和同步传输。 + :esp32p4: - 支持高带宽等时性端点。 + - 支持多个 Class 驱动程序同时运行,即主机的多个客户端同时运行。 + - 单个设备可以由多个客户端同时使用,如复合设备。 + - 主机库及其底层主机栈不会在内部自动创建操作系统任务,任务数量完全由主机库接口的使用方式决定。一般来说,任务数量为 ``(运行中的主机 Class 驱动程序数量 + 1)``。 + - 支持单个 Hub(启用选项 :ref:`CONFIG_USB_HOST_HUBS_SUPPORTED`)。 + - 支持多个 Hub(启用选项 :ref:`CONFIG_USB_HOST_HUB_MULTI_LEVEL`)。 目前,主机库及其底层主机栈存在以下限制: -- 仅支持单个设备,而主机库的 API 支持多设备。 -- 仅支持异步传输。 -- 仅支持使用发现的首个配置,尚不支持变更为其他配置。 -- 尚不支持传输超时。 +.. list:: + + - 仅支持异步传输。 + - 仅支持使用发现的首个配置,尚不支持变更为其他配置。 + - 尚不支持传输超时。 + :esp32p4: - {IDF_TARGET_NAME} 包含两个 USB-OTG 外设:USB 2.0 OTG 高速和 USB 2.0 OTG 全速。目前仅支持高速实例。 + - 外部 Hub 驱动:仅支持与上游端口速率相同的设备。(例如,低速设备无法通过全速外部 Hub 工作。) + - 外部 Hub 驱动:不支持远程唤醒功能(即使没有设备插入,外部 Hub 也处于工作状态)。 + - 外部 Hub 驱动:不处理错误用例(尚未实现过流处理、初始化错误等功能)。 + - 外部 Hub 驱动:不支持接口选择。驱动程序使用具有 Hub 类代码 (09h) 的第一个可用接口。 + - 外部端口驱动:无下游端口去抖动机制(尚未实现)。 + :esp32p4: - 外部 Hub 驱动:无事务转换层(当 Hub 连接到高速主机时,不支持全速/低速设备)。 .. -------------------------------------------------- Architecture ----------------------------------------------------- @@ -89,7 +103,7 @@ USB 主机库(以下简称主机库)是 USB 主机栈的最底层,提供 设备 ^^^^^^^ -主机库隔离了客户端与设备处理的细节,包括连接、内存分配和枚举等,客户端只需提供已连接且已枚举的设备列表供选择。在枚举过程中,每个设备都会自动配置为使用找到的第一个配置,即通过获取配置描述符请求返回的第一个配置描述符。对于大多数标准设备,通常将第一个配置的 ``bConfigurationValue`` 设置为 ``1``。 +主机库隔离了客户端与设备处理的细节,包括连接、内存分配和枚举等,客户端只需提供已连接且已枚举的设备列表供选择。默认情况下,在枚举过程中,每个设备都会自动配置为使用找到的第一个配置,即通过获取配置描述符请求返回的第一个配置描述符。对于大多数标准设备,通常将第一个配置的 ``bConfigurationValue`` 设置为 ``1``。启用选项 :ref:`CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK` 后,可以选择不同的 ``bConfigurationValue``。获取更多详细信息,请参阅 `多项配置支持`_。 只要不与相同接口通信,两个及以上的客户端可以同时与同一设备通信。然而,多个客户端同时与相同设备的默认端点(即 EP0)通信,将导致它们的控制传输序列化。 @@ -431,6 +445,59 @@ USB 设备可能是热插拔的,因此必须配置电源开关和设备连接 * :ref:`CONFIG_USB_HOST_RESET_RECOVERY_MS` 用于配置重置恢复时间。 * :ref:`CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS` 用于配置 ``SetAddress()`` 恢复时间。 +下游端口配置 +^^^^^^^^^^^^ + +当支持外部 Hub 功能时,可以为外部 Hub 端口配置多个参数。 + +每个外部 Hub 都有一个 Hub 描述符,用于描述设备特性。 + +.. note:: + + 有关 Hub 描述符的详细信息,请参考 `USB 2.0 规范 `_ > 章节 11.23.2.1 *Hub Descriptor*。 + +可以通过 Menuconfig 配置下游端口的可配置参数。 + +* 对于在端口上电后稳定电源的自定义值(PwrOn2PwrGood 值),请参阅 :ref:`CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS`。 +* 对于复位恢复间隔,请参阅 :ref:`CONFIG_USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS`。 + +.. note:: + + 规范规定,对于没有电源开关的 Hub,PwrOn2PwrGood 必须设置为零。同时,对于某些设备,可以增加此值以提供额外的上电时间。如需启用此功能,请参考 :ref:`CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE`。 + +主机通道 +""""""""""""" + +当启用外部 Hub 支持功能(:ref:`CONFIG_USB_HOST_HUBS_SUPPORTED`)时,主机通道的数量非常重要,因为每个下游设备都需要空闲通道。 + +每个连接的设备需要不同数量的通道,而所需通道数则取决于设备类别(EP 数量)。 + +对于 {IDF_TARGET_NAME},支持的通道数量为 {OTG_NUM_HOST_CHAN}。 + +.. note:: + + - 需要一个空闲通道来枚举设备。 + + - 需要 1 到 N(N 为 EP 数量)个空闲通道来占用接口。 + + - 如果所有的主机通道都已经被占用,则设备无法进行枚举,也无法获取接口。 + + +多项配置支持 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +对于具有多项配置的 USB 设备,可以在设备枚举过程中指定所需的配置编号。 + +枚举过滤器 +"""""""""""""""""" + +枚举过滤器是类型为 :cpp:type:`usb_host_enum_filter_cb_t` 的回调函数。从新连接的 USB 设备上读取设备描述符后,USB 主机栈会在枚举过程开始时调用枚举过滤器,从而为用户提供读取的设备描述符。借助此回调,用户得以: + +* 选择 USB 设备的配置。 +* 过滤应该进行枚举的 USB 设备。 + +在 menuconfig 中启用 :ref:`CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK` 选项即可启用枚举过滤器。可以通过设置 :cpp:member:`usb_host_config_t::enum_filter_cb` 来指定回调函数,该函数会在调用 :cpp:func:`usb_host_install` 时传递至主机库。 + .. -------------------------------------------------- API Reference ---------------------------------------------------- API 参考 diff --git a/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst new file mode 100644 index 0000000000..6833910aa4 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst @@ -0,0 +1 @@ +.. include:: ../../../../en/api-reference/peripherals/usb_host/usb_host_notes_ext_port.rst From dacb29e07624d230e64416a4a0f5f93a699e67e2 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 25 Feb 2025 17:50:59 +0800 Subject: [PATCH 3/6] feat(usbh): Added uid presence check in USBH device object list --- components/usb/hub.c | 52 +++++++++++++-------------- components/usb/private_include/usbh.h | 14 +++++++- components/usb/usbh.c | 10 +++++- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/components/usb/hub.c b/components/usb/hub.c index d273b2e04f..0d63903553 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 */ @@ -29,7 +29,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 @@ -94,7 +93,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 { @@ -154,53 +152,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); @@ -365,7 +362,7 @@ static void ext_port_event_callback(ext_port_event_data_t *event_data, void *arg } #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; } @@ -407,7 +404,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; } @@ -590,7 +587,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/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 c515fc01ca..3c045378f2 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 */ @@ -783,6 +783,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) { From 9c98a17a0d6b0f810bbfb24ef2440d11f2deb39a Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Tue, 25 Feb 2025 17:52:27 +0800 Subject: [PATCH 4/6] feat(ext_hub): Added device error handling --- Kconfig | 1 - components/usb/Kconfig | 7 - components/usb/ext_hub.c | 313 ++++++++++-------- components/usb/hub.c | 10 - components/usb/private_include/ext_hub.h | 24 +- .../en/api-reference/peripherals/usb_host.rst | 1 - .../api-reference/peripherals/usb_host.rst | 1 - 7 files changed, 192 insertions(+), 165 deletions(-) diff --git a/Kconfig b/Kconfig index 61cb1634c0..ff2e66800b 100644 --- a/Kconfig +++ b/Kconfig @@ -620,5 +620,4 @@ mainmenu "Espressif IoT Development Framework Configuration" - CONFIG_ESPTOOLPY_FLASHFREQ_120M && CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_DTR - CONFIG_SPIRAM_SPEED_120M && CONFIG_SPIRAM_MODE_OCT - CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH - - 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 9dd19984de..7e18c46287 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 5577bd6674..c771861b41 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 */ @@ -30,7 +30,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; /** @@ -54,10 +54,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[] = { @@ -77,15 +77,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; @@ -302,14 +301,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; } @@ -318,7 +316,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; } @@ -370,7 +369,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 { @@ -385,7 +384,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) { @@ -463,45 +464,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) @@ -521,6 +549,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; @@ -538,7 +569,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; } @@ -546,13 +578,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; } @@ -560,7 +595,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; } @@ -577,7 +613,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 @@ -679,15 +717,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); @@ -808,6 +853,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) { @@ -816,13 +868,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; @@ -887,7 +939,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) @@ -900,7 +953,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) @@ -943,7 +997,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; } @@ -961,11 +1016,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; @@ -987,8 +1042,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: @@ -1003,10 +1056,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 || @@ -1017,24 +1072,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) @@ -1043,9 +1092,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: @@ -1054,16 +1100,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; } @@ -1102,21 +1144,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 --------------------------------------- // ----------------------------------------------------------------------------- @@ -1280,7 +1307,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; @@ -1318,7 +1345,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; } @@ -1342,17 +1369,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); @@ -1364,23 +1390,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; @@ -1434,7 +1467,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, @@ -1468,9 +1501,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); } @@ -1541,7 +1571,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; @@ -1553,7 +1583,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) { @@ -1611,7 +1642,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); @@ -1626,7 +1657,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); @@ -1641,7 +1672,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); @@ -1649,6 +1680,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]); } @@ -1656,7 +1689,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); @@ -1664,6 +1697,8 @@ 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); } @@ -1684,12 +1719,13 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu 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); + 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_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; + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_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", @@ -1697,6 +1733,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu port_num, feature, esp_err_to_name(ret)); + device_error(ext_hub_dev); } return ret; } @@ -1713,12 +1750,13 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_ 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); + 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); - ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE; + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_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", @@ -1726,6 +1764,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_ port_num, feature, esp_err_to_name(ret)); + device_error(ext_hub_dev); } return ret; } @@ -1742,18 +1781,20 @@ esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num 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); + 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_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; + ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_PORT_STATUS; 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)); + device_error(ext_hub_dev); } return ret; } diff --git a/components/usb/hub.c b/components/usb/hub.c index 0d63903553..bfc8c4ad09 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -352,16 +352,6 @@ 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 (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; diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h index 93432542a7..6b52b3d746 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 */ @@ -127,7 +127,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 +141,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 +182,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 +199,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 +215,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 +230,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 +245,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,10 +262,11 @@ 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); @@ -281,6 +284,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, * - 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_INVALID_STATE: External Hub is not configured or suspended */ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); @@ -296,6 +300,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu * - 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_INVALID_STATE: External Hub is not configured or suspended */ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature); @@ -313,6 +318,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_ * - 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_INVALID_STATE: External Hub is not configured or suspended */ esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); diff --git a/docs/en/api-reference/peripherals/usb_host.rst b/docs/en/api-reference/peripherals/usb_host.rst index ce17f90851..b231ce1b7d 100644 --- a/docs/en/api-reference/peripherals/usb_host.rst +++ b/docs/en/api-reference/peripherals/usb_host.rst @@ -45,7 +45,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 ff20dbd683..f3e9ad5d47 100644 --- a/docs/zh_CN/api-reference/peripherals/usb_host.rst +++ b/docs/zh_CN/api-reference/peripherals/usb_host.rst @@ -45,7 +45,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) 的第一个可用接口。 From c391c835c2ac07f2918b970eb12f7b72754bb7eb Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Wed, 19 Feb 2025 14:05:24 +0100 Subject: [PATCH 5/6] refactor(ext_port): Changed the mechanism for hub class request Merged all Hub Class specific request to one function. Added a callback for the External Port Driver to break the dependency from ext_hub.h --- components/usb/ext_hub.c | 96 ++++++----------------- components/usb/ext_port.c | 48 +++++++++--- components/usb/hcd_dwc.c | 4 +- components/usb/hub.c | 1 + components/usb/private_include/ext_hub.h | 71 +++++++---------- components/usb/private_include/ext_port.h | 4 +- 6 files changed, 96 insertions(+), 128 deletions(-) diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c index c771861b41..ef869d1dcd 100644 --- a/components/usb/ext_hub.c +++ b/components/usb/ext_hub.c @@ -1707,92 +1707,46 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, // --------------------------- 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) +esp_err_t ext_hub_class_request(ext_hub_handle_t ext_hub_hdl, ext_hub_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 && ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); 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(data->port_num != 0 && data->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 || ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, 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); + switch (data->request) { + case USB_B_REQUEST_HUB_GET_PORT_STATUS: + USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, data->port_num); + 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, data->port_num, data->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, data->port_num, data->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", data->request); + return ESP_ERR_NOT_SUPPORTED; + } - ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_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_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) -{ - 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 || - 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); - - ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_CHECK_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)); - device_error(ext_hub_dev); - } - 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 || - ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, 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_CHECK_PORT_STATUS; - 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_LOGE(EXT_HUB_TAG, "Request %X, port %d, feature %d: failed to submit ctrl urb: %s", + data->request, data->port_num, data->feature, esp_err_to_name(ret)); device_error(ext_hub_dev); } diff --git a/components/usb/ext_port.c b/components/usb/ext_port.c index efce9ed7de..e807dcbb38 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 */ @@ -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_hub_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,25 @@ 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_hub_request_data_t req_data = { + .request = USB_B_REQUEST_HUB_GET_PORT_STATUS, + .port_num = ext_port->constant.port_num, + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb(ext_port->constant.ext_hub_hdl, + &req_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 +264,15 @@ 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_hub_request_data_t req_data = { + .request = USB_B_REQUEST_HUB_SET_PORT_FEATURE, + .port_num = ext_port->constant.port_num, + .feature = feature, + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb(ext_port->constant.ext_hub_hdl, + &req_data, + p_ext_port_driver->constant.hub_request_cb_arg); if (ret != ESP_OK) { return ret; } @@ -293,9 +309,15 @@ 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_hub_request_data_t req_data = { + .request = USB_B_REQUEST_HUB_CLEAR_FEATURE, + .port_num = ext_port->constant.port_num, + .feature = feature, + }; + + esp_err_t ret = p_ext_port_driver->constant.hub_request_cb(ext_port->constant.ext_hub_hdl, + &req_data, + p_ext_port_driver->constant.hub_request_cb_arg); if (ret != ESP_OK) { return ret; } @@ -684,7 +706,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: @@ -1302,6 +1324,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 +1337,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; diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index af7de4a3e3..5197141d80 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -314,7 +314,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); } @@ -1539,7 +1539,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 bfc8c4ad09..fe208b397e 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -536,6 +536,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_class_request, }; ret = ext_port_install(&ext_port_config); if (ret != ESP_OK) { diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h index 6b52b3d746..35ed928c09 100644 --- a/components/usb/private_include/ext_hub.h +++ b/components/usb/private_include/ext_hub.h @@ -34,6 +34,20 @@ typedef struct ext_hub_s *ext_hub_handle_t; */ typedef bool (*ext_hub_cb_t)(bool in_isr, void *user_arg); +/** + * @brief Specific data for Hub class specific request + */ +typedef struct { + usb_hub_class_request_t request; + uint8_t port_num; + uint8_t feature; +} ext_hub_request_data_t; + +/** + * @brief Callback used to indicate that the External Port driver requires a Hub class specific request + */ +typedef esp_err_t (*ext_hub_request_cb_t)(ext_hub_handle_t ext_hub_hdl, ext_hub_request_data_t *request_data, void *user_arg); + // ------------------------ External Port API typedefs ------------------------- /** @@ -273,54 +287,23 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, // --------------------------- USB Chapter 11 ---------------------------------- /** - * @brief Set Port Feature request + * @brief USB Hub Class specific 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] ext_hub_hdl External Hub 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_INVALID_STATE: External Hub is not configured or suspended + * - 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_INVALID_STATE: External Hub is not configured or suspended - */ -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_INVALID_STATE: External Hub is not configured or suspended - */ -esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num); +esp_err_t ext_hub_class_request(ext_hub_handle_t ext_hub_hdl, ext_hub_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..002bb68fe2 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 */ @@ -72,6 +72,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_hub_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; /** From b2c3c9471e49627d2ceaed5b6e041d459af6febc Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Mon, 3 Mar 2025 14:55:21 +0100 Subject: [PATCH 6/6] refactor(ext_port): Removed ext_hub dependency --- components/usb/ext_hub.c | 70 ++++++++++++------ components/usb/ext_port.c | 88 +++++++++++++++-------- components/usb/hub.c | 12 ++-- components/usb/private_include/ext_hub.h | 46 ++---------- components/usb/private_include/ext_port.h | 78 ++++++++++++++++++-- 5 files changed, 189 insertions(+), 105 deletions(-) diff --git a/components/usb/ext_hub.c b/components/usb/ext_hub.c index ef869d1dcd..6dcf7d95fd 100644 --- a/components/usb/ext_hub.c +++ b/components/usb/ext_hub.c @@ -146,7 +146,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; @@ -397,7 +397,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, @@ -1703,52 +1703,76 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, 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_class_request(ext_hub_handle_t ext_hub_hdl, ext_hub_request_data_t *data, void *user_arg) +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(); - EXT_HUB_CHECK(data != NULL && ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG); - esp_err_t ret; - 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(data->port_num != 0 && data->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 || - ext_hub_dev->single_thread.state == EXT_HUB_STATE_SUSPENDED, ESP_ERR_INVALID_STATE); - - switch (data->request) { + switch (request) { case USB_B_REQUEST_HUB_GET_PORT_STATUS: - USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, data->port_num); + 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, data->port_num, data->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, data->port_num, data->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", data->request); + ESP_LOGE(EXT_HUB_TAG, "Request %X not supported", request); return ESP_ERR_NOT_SUPPORTED; } 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, "Request %X, port %d, feature %d: failed to submit ctrl urb: %s", - data->request, data->port_num, data->feature, + request, port1, feature, esp_err_to_name(ret)); device_error(ext_hub_dev); } return ret; } + +// ------------------------------------------------------------------------------------------------- +// ---------------------------------- 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); + + 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; + + 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); + + switch (data->type) { + case EXT_PORT_PARENT_REQ_CONTROL: + return ext_hub_control_request(ext_hub_dev, port1, data->control.req, data->control.feature); + + case EXT_PORT_PARENT_REQ_PROC_COMPLETED: + return ext_hub_status_handle_complete(ext_hub_hdl); + + default: + break; + } + + 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 e807dcbb38..5fedc49ae3 100644 --- a/components/usb/ext_port.c +++ b/components/usb/ext_port.c @@ -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,7 +102,7 @@ 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_hub_request_cb_t hub_request_cb; /**< External Port Hub request callback */ + 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; @@ -232,13 +232,15 @@ static inline bool port_has_finished_reset(ext_port_t *ext_port) */ static esp_err_t port_request_status(ext_port_t* ext_port) { - ext_hub_request_data_t req_data = { - .request = USB_B_REQUEST_HUB_GET_PORT_STATUS, - .port_num = 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->constant.ext_hub_hdl, - &req_data, + 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; @@ -264,14 +266,16 @@ 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) { - ext_hub_request_data_t req_data = { - .request = USB_B_REQUEST_HUB_SET_PORT_FEATURE, - .port_num = ext_port->constant.port_num, - .feature = 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->constant.ext_hub_hdl, - &req_data, + 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; @@ -309,14 +313,16 @@ 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) { - ext_hub_request_data_t req_data = { - .request = USB_B_REQUEST_HUB_CLEAR_FEATURE, - .port_num = ext_port->constant.port_num, - .feature = 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->constant.ext_hub_hdl, - &req_data, + 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; @@ -410,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; @@ -428,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); } /** @@ -453,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 @@ -464,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); @@ -479,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; @@ -545,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; @@ -559,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) { @@ -965,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, @@ -1002,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); @@ -1307,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, @@ -1425,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/hub.c b/components/usb/hub.c index fe208b397e..38d51c525e 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -20,7 +20,7 @@ #include "usb/usb_helpers.h" #if ENABLE_USB_HUBS -#include "ext_port.h" +#include "ext_hub.h" #endif // ENABLE_USB_HUBS /* @@ -326,14 +326,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; @@ -358,7 +360,7 @@ static void ext_port_event_callback(ext_port_event_data_t *event_data, void *arg } 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)); @@ -536,7 +538,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_class_request, + .hub_request_cb = ext_hub_request, }; ret = ext_port_install(&ext_port_config); if (ret != ESP_OK) { diff --git a/components/usb/private_include/ext_hub.h b/components/usb/private_include/ext_hub.h index 35ed928c09..be95dbefe3 100644 --- a/components/usb/private_include/ext_hub.h +++ b/components/usb/private_include/ext_hub.h @@ -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,46 +31,13 @@ typedef struct ext_hub_s *ext_hub_handle_t; */ typedef bool (*ext_hub_cb_t)(bool in_isr, void *user_arg); -/** - * @brief Specific data for Hub class specific request - */ -typedef struct { - usb_hub_class_request_t request; - uint8_t port_num; - uint8_t feature; -} ext_hub_request_data_t; - -/** - * @brief Callback used to indicate that the External Port driver requires a Hub class specific request - */ -typedef esp_err_t (*ext_hub_request_cb_t)(ext_hub_handle_t ext_hub_hdl, ext_hub_request_data_t *request_data, 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 --------------------------------------- @@ -287,11 +251,11 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, // --------------------------- USB Chapter 11 ---------------------------------- /** - * @brief USB Hub Class specific request + * @brief USB Hub Request * * @note This function designed to be called from the External Port driver * - * @param[in] ext_hub_hdl External Hub handle + * @param[in] port_hdl External Port handle * @param[in] data Request data * @param[in] user_arg User argument * @@ -303,7 +267,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, * - ESP_ERR_NOT_SUPPORTED if the request is not supported * - ESP_OK if control transfer was successfully submitted */ -esp_err_t ext_hub_class_request(ext_hub_handle_t ext_hub_hdl, ext_hub_request_data_t *data, void *user_arg); +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 002bb68fe2..1758b0c140 100644 --- a/components/usb/private_include/ext_port.h +++ b/components/usb/private_include/ext_port.h @@ -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,7 +117,7 @@ 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_hub_request_cb_t hub_request_cb; /**< External Port Hub request callback */ + 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; @@ -82,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 */ @@ -138,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 }