Skip to content

Commit 48d3c83

Browse files
Merge pull request #389 from espressif/feat/hub_multi_root_port
feat(hub): Added support for multiple root ports
2 parents 79f66f7 + 82ace30 commit 48d3c83

File tree

15 files changed

+501
-175
lines changed

15 files changed

+501
-175
lines changed

host/class/cdc/usb_host_cdc_acm/test_app/main/test_app_main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -38,6 +38,6 @@ void app_main(void)
3838
printf(" \\/ \\/ \\/ \\/ \r\n");
3939

4040
unity_utils_setup_heap_record(80);
41-
unity_utils_set_leak_level(30);
41+
unity_utils_set_leak_level(40);
4242
unity_run_menu();
4343
}

host/usb/private_include/enum.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -109,6 +109,7 @@ esp_err_t enum_uninstall(void);
109109
* @return
110110
* - ESP_OK: Enumeration process started
111111
* - ESP_ERR_NOT_FOUND: No device at address 0
112+
* - ESP_ERR_NO_MEM: Pending enumeration queue is full
112113
*/
113114
esp_err_t enum_start(unsigned int uid);
114115

host/usb/private_include/ext_hub.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -97,6 +97,15 @@ void *ext_hub_get_client(void);
9797
*/
9898
esp_err_t ext_hub_get_speed(ext_hub_handle_t ext_hub_hdl, usb_speed_t *speed);
9999

100+
/**
101+
* @brief Get the root port handle of an External Hub device
102+
*
103+
* @param[in] ext_hub_hdl External Hub device handle
104+
*
105+
* @return Root port handle on success, NULL on error
106+
*/
107+
hcd_port_handle_t ext_hub_get_root_port(ext_hub_handle_t ext_hub_hdl);
108+
100109
/**
101110
* @brief Add new device
102111
*

host/usb/private_include/hcd.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ extern "C" {
1414
#include <stdbool.h>
1515
#include <sys/queue.h>
1616
#include "esp_err.h"
17+
#include "soc/soc_caps.h"
1718
#include "esp_idf_version.h"
1819
#include "usb_private.h"
1920
#include "usb/usb_types_ch9.h"
2021

2122
// ------------------------------------------------- Macros & Types ----------------------------------------------------
2223

24+
// ----------------------- Configs -------------------------
25+
26+
#define HCD_NUM_PORTS SOC_USB_OTG_PERIPH_NUM // Each peripheral is a root port
27+
2328
// ----------------------- States --------------------------
2429

2530
/**

host/usb/private_include/hub.h

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ typedef struct {
7777
* - USBH must already be installed
7878
* Exit:
7979
* - Install Hub driver memory resources
80-
* - Initializes the HCD root port
80+
* - Initializes the HCD root ports selected by hub_config->port_map
8181
*
8282
* @param[in] hub_config Hub driver configuration
8383
* @param[out] client_ret Unique pointer to identify the Hub as a USB Host client
@@ -95,44 +95,43 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret);
9595
*
9696
* This must be called before uninstalling the USBH
9797
* Entry:
98-
* - Must have stopped the root port
98+
* - Must have stopped all enabled root ports
9999
* Exit:
100-
* - HCD root port deinitialized
100+
* - HCD root ports deinitialized
101101
*
102102
* @return
103103
* - ESP_OK: Hub driver uninstalled successfully
104-
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or root port is in other state than not powered
104+
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or any enabled root port is in other state than not powered
105105
*/
106106
esp_err_t hub_uninstall(void);
107107

108108
/**
109-
* @brief Start the Hub driver's root port
109+
* @brief Start the Hub driver's root ports
110110
*
111-
* This will power the root port ON
112-
*
113-
* @note This function should only be called from the Host Library task
111+
* This will power ON all enabled root ports.
114112
*
115113
* @return
116-
* - ESP_OK: Root port has been powered on
117-
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or root port is in other state than not powered
114+
* - ESP_OK: All enabled root ports have been powered on
115+
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or any enabled root port is in other state than not powered
118116
*/
119117
esp_err_t hub_root_start(void);
120118

121119
/**
122-
* @brief Stops the Hub driver's root port
120+
* @brief Stops the Hub driver's root ports
123121
*
124-
* This will power OFF the root port
122+
* This will power OFF all enabled root ports.
125123
*
126124
* @return
127-
* - ESP_OK: Root port has been powered off
128-
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or root port is in not powered state
125+
* - ESP_OK: All enabled root ports have been powered off
126+
* - ESP_ERR_INVALID_STATE: Hub driver is not installed, or all enabled root ports are already not powered
129127
*/
130128
esp_err_t hub_root_stop(void);
131129

132130
/**
133-
* @brief Check if root port is in suspended state
131+
* @brief Check if a root port is in suspended state
134132
*
135-
* This will check root port state
133+
* This will check root port state. In multi-port configurations, this currently
134+
* checks the first enabled root port only.
136135
*
137136
* @return
138137
* - true: Root port is in suspended state
@@ -143,7 +142,8 @@ bool hub_root_is_suspended(void);
143142
/**
144143
* @brief Check if the Hub driver's root port can be suspended
145144
*
146-
* This will check if all HCD pipes are idle and which state the root port is in
145+
* This will check if all HCD pipes are idle and which state the root port is in.
146+
* In multi-port configurations, this currently checks the first enabled root port only.
147147
*
148148
* @return
149149
* - ESP_OK: Hub driver's root port can be suspended
@@ -157,6 +157,8 @@ esp_err_t hub_root_can_suspend(void);
157157
/**
158158
* @brief Check if the Hub driver's root port can be resumed
159159
*
160+
* In multi-port configurations, this currently checks the first enabled root port only.
161+
*
160162
* @return
161163
* - ESP_OK: Hub driver's root port can be resumed
162164
* - ESP_ERR_INVALID_STATE: Hub driver is not installed
@@ -169,7 +171,8 @@ esp_err_t hub_root_can_resume(void);
169171
/**
170172
* @brief Mark the Hub driver's root port as ready for suspend
171173
*
172-
* This will mark the root port, as ready to be suspended and and will be processed by the hub processing loop
174+
* This will mark the root port as ready to be suspended and will be processed by the hub processing loop.
175+
* In multi-port configurations, this currently targets the first enabled root port only.
173176
*
174177
* @return
175178
* - ESP_OK: Hub driver suspended successfully
@@ -181,7 +184,8 @@ esp_err_t hub_root_mark_suspend(void);
181184
/**
182185
* @brief Mark the Hub driver's root port as ready for resume
183186
*
184-
* This will mark the root port, as ready to be resumed and and will be processed by the hub processing loop
187+
* This will mark the root port as ready to be resumed and will be processed by the hub processing loop.
188+
* In multi-port configurations, this currently targets the first enabled root port only.
185189
*
186190
* @return
187191
* - ESP_OK: Hub driver resumed successfully

host/usb/private_include/usbh.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -336,6 +336,23 @@ void usbh_devs_set_pm_actions_all(usbh_dev_ctrl_t device_ctrl);
336336
*/
337337
esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl);
338338

339+
/**
340+
* @brief Open a device by UID
341+
*
342+
* A device must be opened before it can be used
343+
*
344+
* @param[in] uid Unique ID assigned to the device
345+
* @param[out] dev_hdl Device handle
346+
*
347+
* @return
348+
* - ESP_OK: Device opened successfully
349+
* - ESP_ERR_INVALID_ARG: Invalid argument
350+
* - ESP_ERR_INVALID_STATE: Device is in invalid state, either already gone (disconnected), or waiting to be freed
351+
* - ESP_ERR_NOT_ALLOWED: It is not allowed to open the device, it is locked for the enumeration
352+
* - ESP_ERR_NOT_FOUND: Device with provided UID not found
353+
*/
354+
esp_err_t usbh_devs_open_uid(unsigned int uid, usb_device_handle_t *dev_hdl);
355+
339356
/**
340357
* @brief Trigger a USBH_EVENT_NEW_DEV event for the device
341358
*
@@ -395,6 +412,18 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr);
395412
*/
396413
esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info);
397414

415+
/**
416+
* @brief Get the root port handle of a device
417+
*
418+
* @param[in] dev_hdl Device handle
419+
* @param[out] root_port_hdl Root port handle
420+
*
421+
* @return
422+
* - ESP_OK: Root port handle obtained successfully
423+
* - ESP_ERR_INVALID_ARG: Invalid argument
424+
*/
425+
esp_err_t usbh_dev_get_root_port_hdl(usb_device_handle_t dev_hdl, hcd_port_handle_t *root_port_hdl);
426+
398427
/**
399428
* @brief Get a device's device descriptor
400429
*

host/usb/src/enum.c

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -26,6 +26,7 @@
2626
#define ENUM_WORST_CASE_MPS_FS_HS 64 // The worst case MPS of EP0 for a FS/HS device
2727
#define ENUM_LANGID 0x409 // Current enumeration only supports English (United States) string descriptors
2828
#define ENUM_MAX_ADDRESS (127) // Maximal device address value
29+
#define ENUM_PENDING_QUEUE_LEN (5)
2930

3031
/**
3132
* @brief Stages of device enumeration listed in their order of execution
@@ -139,6 +140,11 @@ typedef struct {
139140
enum_device_params_t dev_params; /**< Parameters of device under enumeration */
140141
int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */
141142
uint8_t next_dev_addr; /**< Device address for device under enumeration */
143+
// Pending enumeration queue
144+
unsigned int pending_uids[ENUM_PENDING_QUEUE_LEN];
145+
uint16_t pending_head;
146+
uint16_t pending_tail;
147+
uint16_t pending_count;
142148
} single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
143149

144150
struct {
@@ -203,6 +209,30 @@ static uint8_t get_next_free_dev_addr(void)
203209
return new_dev_addr;
204210
}
205211

212+
static bool pending_uid_enqueue(unsigned int uid)
213+
{
214+
if (p_enum_driver->single_thread.pending_count >= ENUM_PENDING_QUEUE_LEN) {
215+
return false;
216+
}
217+
p_enum_driver->single_thread.pending_uids[p_enum_driver->single_thread.pending_tail] = uid;
218+
p_enum_driver->single_thread.pending_tail =
219+
(p_enum_driver->single_thread.pending_tail + 1) % ENUM_PENDING_QUEUE_LEN;
220+
p_enum_driver->single_thread.pending_count++;
221+
return true;
222+
}
223+
224+
static bool pending_uid_dequeue(unsigned int *uid)
225+
{
226+
if (p_enum_driver->single_thread.pending_count == 0) {
227+
return false;
228+
}
229+
*uid = p_enum_driver->single_thread.pending_uids[p_enum_driver->single_thread.pending_head];
230+
p_enum_driver->single_thread.pending_head =
231+
(p_enum_driver->single_thread.pending_head + 1) % ENUM_PENDING_QUEUE_LEN;
232+
p_enum_driver->single_thread.pending_count--;
233+
return true;
234+
}
235+
206236
/**
207237
* @brief Get Configuration descriptor index
208238
*
@@ -1161,11 +1191,19 @@ esp_err_t enum_start(unsigned int uid)
11611191
{
11621192
ENUM_CHECK(p_enum_driver != NULL, ESP_ERR_INVALID_STATE);
11631193

1194+
if (p_enum_driver->single_thread.stage != ENUM_STAGE_IDLE) {
1195+
1196+
// We are already enumerating a device, add the new device to the pending queue
1197+
if (!pending_uid_enqueue(uid)) {
1198+
return ESP_ERR_NO_MEM;
1199+
}
1200+
return ESP_OK;
1201+
}
11641202
esp_err_t ret = ESP_FAIL;
11651203

11661204
// Open device and lock it for enumeration process
11671205
usb_device_handle_t dev_hdl;
1168-
ret = usbh_devs_open(0, &dev_hdl);
1206+
ret = usbh_devs_open_uid(uid, &dev_hdl);
11691207
if (ret != ESP_OK) {
11701208
return ret;
11711209
}
@@ -1176,7 +1214,7 @@ esp_err_t enum_start(unsigned int uid)
11761214
ESP_ERROR_CHECK(usbh_dev_get_info(dev_hdl, &dev_info));
11771215

11781216
// Stage ENUM_STAGE_GET_SHORT_DEV_DESC
1179-
ESP_LOGD(ENUM_TAG, "Start processing device with address %d", 0);
1217+
ESP_LOGD(ENUM_TAG, "Start processing device with uid %d", uid);
11801218

11811219
p_enum_driver->single_thread.stage = ENUM_STAGE_GET_SHORT_DEV_DESC;
11821220
p_enum_driver->single_thread.node_uid = uid;
@@ -1322,5 +1360,17 @@ esp_err_t enum_process(void)
13221360
p_enum_driver->constant.proc_req_cb(USB_PROC_REQ_SOURCE_ENUM, false, p_enum_driver->constant.proc_req_cb_arg);
13231361
}
13241362

1363+
// If we are idle and there are pending devices, start the next one
1364+
while (p_enum_driver->single_thread.pending_count > 0 &&
1365+
p_enum_driver->single_thread.stage == ENUM_STAGE_IDLE) {
1366+
unsigned int next_uid = 0;
1367+
if (!pending_uid_dequeue(&next_uid)) {
1368+
break;
1369+
}
1370+
if (enum_start(next_uid) == ESP_OK) {
1371+
break;
1372+
}
1373+
}
1374+
13251375
return ESP_OK;
13261376
}

host/usb/src/ext_hub.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -1298,6 +1298,18 @@ esp_err_t ext_hub_get_speed(ext_hub_handle_t ext_hub_hdl, usb_speed_t *speed)
12981298
return ESP_OK;
12991299
}
13001300

1301+
hcd_port_handle_t ext_hub_get_root_port(ext_hub_handle_t ext_hub_hdl)
1302+
{
1303+
EXT_HUB_CHECK(ext_hub_hdl != NULL, NULL);
1304+
EXT_HUB_CHECK(dev_is_in_list(ext_hub_hdl), NULL);
1305+
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
1306+
hcd_port_handle_t root_port_hdl = NULL;
1307+
if (usbh_dev_get_root_port_hdl(ext_hub_dev->constant.dev_hdl, &root_port_hdl) != ESP_OK) {
1308+
return NULL;
1309+
}
1310+
return root_port_hdl;
1311+
}
1312+
13011313
esp_err_t ext_hub_new_dev(uint8_t dev_addr)
13021314
{
13031315
EXT_HUB_ENTER_CRITICAL();

host/usb/src/hcd_dwc.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
#include "esp_err.h"
1818
#include "esp_log.h"
1919

20-
#include "soc/soc_caps.h"
2120
#include "hal/usb_dwc_hal.h"
2221
#include "hcd.h"
2322
#include "usb_private.h"
@@ -51,8 +50,6 @@
5150
#define CTRL_EP_MAX_MPS_LS 8 // Largest Maximum Packet Size for Low Speed control endpoints
5251
#define CTRL_EP_MAX_MPS_HSFS 64 // Largest Maximum Packet Size for High & Full Speed control endpoints
5352

54-
#define NUM_PORTS SOC_USB_OTG_PERIPH_NUM // Each peripheral is a root port
55-
5653
// ----------------------- Configs -------------------------
5754

5855
#ifdef CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM // In esp32p4, the USB-DWC internal DMA can access external RAM
@@ -269,7 +266,7 @@ struct port_obj {
269266

270267
// With s_port_inited[] we can check if a port has been initialized -> to provide singleton handle for each root port
271268
// Each peripheral is a root port
272-
static bool s_port_inited[NUM_PORTS] = {0};
269+
static bool s_port_inited[HCD_NUM_PORTS] = {0};
273270

274271
// ------------------------------------------------- Forward Declare ---------------------------------------------------
275272

@@ -1454,7 +1451,7 @@ static esp_err_t _port_cmd_disable(port_t *port)
14541451
esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl)
14551452
{
14561453
HCD_CHECK(port_number >= 0 && port_config != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG);
1457-
HCD_CHECK(port_number < NUM_PORTS, ESP_ERR_NOT_FOUND);
1454+
HCD_CHECK(port_number < HCD_NUM_PORTS, ESP_ERR_NOT_FOUND);
14581455

14591456
HCD_ENTER_CRITICAL();
14601457
HCD_CHECK_FROM_CRIT(!s_port_inited[port_number], ESP_ERR_INVALID_STATE);

0 commit comments

Comments
 (0)