Merge branch 'feature/usb_dual_host_2_backport_v5.3' into 'release/v5.3'

feat(usb/host): Add option to choose peripheral for USB host library (backport v5.3)

See merge request espressif/esp-idf!39723
This commit is contained in:
morris
2025-07-02 12:09:57 +08:00
10 changed files with 121 additions and 65 deletions
+1 -1
View File
@@ -106,9 +106,9 @@ extern const usb_dwc_info_t usb_dwc_info;
/* ------------------------------- Deprecated ------------------------------- */
/* Todo: Remove in ESP-IDF v6.0 (IDF-9052) */
#include <stdint.h>
#include "soc/periph_defs.h"
#if SOC_USB_OTG_SUPPORTED
#include "soc/periph_defs.h"
/*
Stores a bunch of USB-peripheral data.
+75 -48
View File
@@ -42,7 +42,7 @@
#define CTRL_EP_MAX_MPS_LS 8 // Largest Maximum Packet Size for Low Speed control endpoints
#define CTRL_EP_MAX_MPS_HSFS 64 // Largest Maximum Packet Size for High & Full Speed control endpoints
#define NUM_PORTS 1 // The controller only has one port.
#define NUM_PORTS SOC_USB_OTG_PERIPH_NUM // Each peripheral is a root port
// ----------------------- Configs -------------------------
@@ -106,7 +106,6 @@ const char *HCD_DWC_TAG = "HCD DWC";
* For other SOCs this is no-operation
*/
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define CACHE_SYNC_FRAME_LIST(frame_list) cache_sync_frame_list(frame_list)
#define CACHE_SYNC_XFER_DESCRIPTOR_LIST_M2C(buffer) cache_sync_xfer_descriptor_list(buffer, true)
#define CACHE_SYNC_XFER_DESCRIPTOR_LIST_C2M(buffer) cache_sync_xfer_descriptor_list(buffer, false)
@@ -255,15 +254,15 @@ struct port_obj {
void *callback_arg;
SemaphoreHandle_t port_mux;
void *context;
intr_handle_t isr_hdl; // Interrupt handle for this root port (USB-OTG peripheral)
};
/**
* @brief Object representing the HCD
*/
typedef struct {
// Ports (Hardware only has one)
port_t *port_obj;
intr_handle_t isr_hdl;
// Ports: Each peripheral is a root port
port_t *port_obj[NUM_PORTS];
} hcd_obj_t;
static portMUX_TYPE hcd_lock = portMUX_INITIALIZER_UNLOCKED;
@@ -1040,52 +1039,66 @@ esp_err_t hcd_install(const hcd_config_t *config)
HCD_CHECK_FROM_CRIT(s_hcd_obj == NULL, ESP_ERR_INVALID_STATE);
HCD_EXIT_CRITICAL();
// Check if peripheral_map does not have bits set outside of valid range. Valid bits are BIT0 - BIT(NUM_PORTS - 1)
HCD_CHECK((config->peripheral_map != 0) && (config->peripheral_map < BIT(NUM_PORTS)), ESP_ERR_INVALID_ARG);
esp_err_t err_ret;
// Allocate memory for the driver object
hcd_obj_t *p_hcd_obj_dmy = calloc(1, sizeof(hcd_obj_t));
if (p_hcd_obj_dmy == NULL) {
return ESP_ERR_NO_MEM;
}
// Allocate each port object (the hardware currently only has one port)
p_hcd_obj_dmy->port_obj = port_obj_alloc();
if (p_hcd_obj_dmy->port_obj == NULL) {
err_ret = ESP_ERR_NO_MEM;
goto port_alloc_err;
}
// Allocate interrupt
err_ret = esp_intr_alloc(usb_dwc_info.controllers[0].irq,
config->intr_flags | ESP_INTR_FLAG_INTRDISABLED, // The interrupt must be disabled until the port is initialized
intr_hdlr_main,
(void *)p_hcd_obj_dmy->port_obj,
&p_hcd_obj_dmy->isr_hdl);
if (err_ret != ESP_OK) {
ESP_LOGE(HCD_DWC_TAG, "Interrupt alloc error: %s", esp_err_to_name(err_ret));
goto intr_alloc_err;
}
// Apply custom FIFO config if provided, otherwise mark as default (all zeros)
memset(&p_hcd_obj_dmy->port_obj->fifo_config, 0, sizeof(p_hcd_obj_dmy->port_obj->fifo_config));
if (config->fifo_config != NULL) {
// Convert and validate user-provided config
err_ret = convert_fifo_config_to_hal_config(config->fifo_config, &p_hcd_obj_dmy->port_obj->fifo_config);
if (err_ret != ESP_OK) {
goto assign_err;
// Allocate each port object
for (int i = 0; i < NUM_PORTS; i++) {
if (BIT(i) & config->peripheral_map) {
p_hcd_obj_dmy->port_obj[i] = port_obj_alloc();
if (p_hcd_obj_dmy->port_obj[i] == NULL) {
err_ret = ESP_ERR_NO_MEM;
goto clean_up;
}
// Allocate interrupt
const int irq_index = usb_dwc_info.controllers[i].irq;
err_ret = esp_intr_alloc(irq_index,
config->intr_flags | ESP_INTR_FLAG_INTRDISABLED, // The interrupt must be disabled until the port is initialized
intr_hdlr_main,
(void *)p_hcd_obj_dmy->port_obj[i],
&p_hcd_obj_dmy->port_obj[i]->isr_hdl);
if (err_ret != ESP_OK) {
ESP_LOGE(HCD_DWC_TAG, "Interrupt alloc error: %s", esp_err_to_name(err_ret));
goto clean_up;
}
// Apply custom FIFO config if provided
if (config->fifo_config != NULL) {
// Convert and validate user-provided config
err_ret = convert_fifo_config_to_hal_config(config->fifo_config, &p_hcd_obj_dmy->port_obj[i]->fifo_config);
if (err_ret != ESP_OK) {
goto clean_up;
}
}
}
}
HCD_ENTER_CRITICAL();
if (s_hcd_obj != NULL) {
HCD_EXIT_CRITICAL();
err_ret = ESP_ERR_INVALID_STATE;
goto assign_err;
goto clean_up;
}
s_hcd_obj = p_hcd_obj_dmy;
HCD_EXIT_CRITICAL();
return ESP_OK;
assign_err:
esp_intr_free(p_hcd_obj_dmy->isr_hdl);
intr_alloc_err:
port_obj_free(p_hcd_obj_dmy->port_obj);
port_alloc_err:
clean_up:
// Free resources
for (int i = 0; i < NUM_PORTS; i++) {
if (p_hcd_obj_dmy->port_obj[i]) {
esp_intr_free(p_hcd_obj_dmy->port_obj[i]->isr_hdl);
port_obj_free(p_hcd_obj_dmy->port_obj[i]);
}
}
free(p_hcd_obj_dmy);
return err_ret;
}
@@ -1093,8 +1106,14 @@ port_alloc_err:
esp_err_t hcd_uninstall(void)
{
HCD_ENTER_CRITICAL();
// Check that all ports have been disabled (there's only one port)
if (s_hcd_obj == NULL || s_hcd_obj->port_obj->initialized) {
// Check that all ports have been disabled
bool all_ports_disabled = true;
for (int i = 0; i < NUM_PORTS; i++) {
if (s_hcd_obj->port_obj[i]) {
all_ports_disabled = all_ports_disabled && !(s_hcd_obj->port_obj[i]->initialized);
}
}
if (s_hcd_obj == NULL || !all_ports_disabled) {
HCD_EXIT_CRITICAL();
return ESP_ERR_INVALID_STATE;
}
@@ -1103,8 +1122,12 @@ esp_err_t hcd_uninstall(void)
HCD_EXIT_CRITICAL();
// Free resources
port_obj_free(p_hcd_obj_dmy->port_obj);
esp_intr_free(p_hcd_obj_dmy->isr_hdl);
for (int i = 0; i < NUM_PORTS; i++) {
if (p_hcd_obj_dmy->port_obj[i]) {
esp_intr_free(p_hcd_obj_dmy->port_obj[i]->isr_hdl);
port_obj_free(p_hcd_obj_dmy->port_obj[i]);
}
}
free(p_hcd_obj_dmy);
return ESP_OK;
}
@@ -1364,13 +1387,13 @@ exit:
esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, hcd_port_handle_t *port_hdl)
{
HCD_CHECK(port_number > 0 && port_config != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG);
HCD_CHECK(port_number <= NUM_PORTS, ESP_ERR_NOT_FOUND);
HCD_CHECK(port_number >= 0 && port_config != NULL && port_hdl != NULL, ESP_ERR_INVALID_ARG);
HCD_CHECK(port_number < NUM_PORTS, ESP_ERR_NOT_FOUND);
HCD_ENTER_CRITICAL();
HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && !s_hcd_obj->port_obj->initialized, ESP_ERR_INVALID_STATE);
HCD_CHECK_FROM_CRIT(s_hcd_obj != NULL && !s_hcd_obj->port_obj[port_number]->initialized, ESP_ERR_INVALID_STATE);
// Port object memory and resources (such as the mutex) already be allocated. Just need to initialize necessary fields only
port_t *port_obj = s_hcd_obj->port_obj;
port_t *port_obj = s_hcd_obj->port_obj[port_number];
TAILQ_INIT(&port_obj->pipes_idle_tailq);
TAILQ_INIT(&port_obj->pipes_active_tailq);
port_obj->state = HCD_PORT_STATE_NOT_POWERED;
@@ -1378,18 +1401,22 @@ esp_err_t hcd_port_init(int port_number, const hcd_port_config_t *port_config, h
port_obj->callback = port_config->callback;
port_obj->callback_arg = port_config->callback_arg;
port_obj->context = port_config->context;
usb_dwc_hal_init(port_obj->hal, 0);
// USB-HAL's size is dependent on its configuration, namely on number of channels in the configuration
// We must first initialize the HAL, to get the number of channels and then allocate memory for the channels
usb_dwc_hal_init(port_obj->hal, port_number);
port_obj->hal->channels.hdls = calloc(port_obj->hal->constant_config.chan_num_total, sizeof(usb_dwc_hal_chan_t*));
HCD_CHECK_FROM_CRIT(port_obj->hal->channels.hdls != NULL, ESP_ERR_NO_MEM);
port_obj->initialized = true;
// Clear the frame list. We set the frame list register and enable periodic scheduling after a successful reset
// Clear the frame list. We will set the frame list register and enable periodic scheduling after a successful reset
memset(port_obj->frame_list, 0, FRAME_LIST_LEN * sizeof(uint32_t));
// If FIFO config is zeroed -> calculate from bias
if (_is_fifo_config_by_bias(&port_obj->fifo_config)) {
// Calculate default FIFO sizes based on Kconfig bias settings
_calculate_fifo_from_bias(port_obj, port_obj->hal);
}
esp_intr_enable(s_hcd_obj->isr_hdl);
esp_intr_enable(port_obj->isr_hdl);
*port_hdl = (hcd_port_handle_t)port_obj;
HCD_EXIT_CRITICAL();
ESP_LOGD(HCD_DWC_TAG, "FIFO config lines: RX=%" PRIu32 ", PTX=%" PRIu32 ", NPTX=%" PRIu32,
@@ -1411,7 +1438,7 @@ esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl)
&& port->task_waiting_port_notif == NULL,
ESP_ERR_INVALID_STATE);
port->initialized = false;
esp_intr_disable(s_hcd_obj->isr_hdl);
esp_intr_disable(port->isr_hdl);
free(port->hal->channels.hdls);
usb_dwc_hal_deinit(port->hal);
HCD_EXIT_CRITICAL();
@@ -1527,7 +1554,7 @@ esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl)
ESP_ERR_INVALID_STATE);
// We are about to do a soft reset on the peripheral. Disable the peripheral throughout
esp_intr_disable(s_hcd_obj->isr_hdl);
esp_intr_disable(port->isr_hdl);
usb_dwc_hal_core_soft_reset(port->hal);
port->state = HCD_PORT_STATE_NOT_POWERED;
port->last_event = HCD_PORT_EVENT_NONE;
@@ -1535,7 +1562,7 @@ esp_err_t hcd_port_recover(hcd_port_handle_t port_hdl)
// Clear the frame list. We set the frame list register and enable periodic scheduling after a successful reset
memset(port->frame_list, 0, FRAME_LIST_LEN * sizeof(uint32_t));
esp_intr_enable(s_hcd_obj->isr_hdl);
esp_intr_enable(port->isr_hdl);
HCD_EXIT_CRITICAL();
return ESP_OK;
}
+9 -4
View File
@@ -13,6 +13,7 @@
#include "freertos/portmacro.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_bit_defs.h"
#include "esp_log.h"
#include "usb_private.h"
#include "hcd.h"
@@ -28,8 +29,6 @@ Implementation of the HUB driver that only supports the Root Hub with a single p
implement the bare minimum to control the root HCD port.
*/
#define HUB_ROOT_PORT_NUM 1 // HCD only supports one port
#ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_RX
#elif CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT
@@ -61,7 +60,6 @@ typedef enum {
ROOT_PORT_STATE_POWERED, /**< Root port is powered, device is not connected */
ROOT_PORT_STATE_DISABLED, /**< A device is connected but is disabled (i.e., not reset, no SOFs are sent) */
ROOT_PORT_STATE_ENABLED, /**< A device is connected, port has been reset, SOFs are sent */
ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */
} root_port_state_t;
/**
@@ -567,7 +565,14 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
.context = NULL,
};
hcd_port_handle_t root_port_hdl;
ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &root_port_hdl);
// Right now we support only one root port, can be extended in future
int root_port_index = 0;
if (hub_config->port_map & BIT1) {
root_port_index = 1;
}
ret = hcd_port_init(root_port_index, &port_config, &root_port_hdl);
if (ret != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "HCD Port init error: %s", esp_err_to_name(ret));
goto err;
+7
View File
@@ -121,6 +121,13 @@ typedef struct {
} fifo_settings_custom; /**< Optional custom FIFO configuration (advanced use).
RX and NPTX must be > 0. If all fields are zero,
a default configuration will be selected based on Kconfig bias. */
unsigned peripheral_map; /**< Selects the USB peripheral(s) to use.
- On targets with multiple USB peripherals, this field can be used to specify which ones to enable.
- Set to 0 to use the default peripheral.
- On High-Speed capable targets, the default is the High-Speed peripheral.
- On Full-Speed only targets, the default is the Full-Speed peripheral.
- Example: peripheral_map = BIT1; installs USB host on peripheral 1.
- The mapping of bits to specific peripherals is defined in the USB_DWC_LL_GET_HW() macro. */
} usb_host_config_t;
/**
+5 -5
View File
@@ -154,9 +154,10 @@ typedef struct {
* @brief HCD configuration structure
*/
typedef struct {
int intr_flags; /**< Interrupt flags for HCD interrupt */
const hcd_fifo_settings_t *fifo_config;/**< Optional pointer to custom FIFO config.
If NULL, default configuration is used. */
int intr_flags; /**< Interrupt flags for HCD interrupt */
const hcd_fifo_settings_t *fifo_config; /**< Optional pointer to custom FIFO config.
If NULL, default configuration is used. */
unsigned peripheral_map; /**< Bit map of USB-OTG peripherals that belong to HCD. Set to zero to use default */
} hcd_config_t;
/**
@@ -166,6 +167,7 @@ typedef struct {
hcd_port_callback_t callback; /**< HCD port event callback */
void *callback_arg; /**< User argument for HCD port callback */
void *context; /**< Context variable used to associate the port with upper layer object */
unsigned peripheral_idx; /**< Index of USB peripheral this port belongs to. Index numbers are defined in USB_DWC_LL_GET_HW */
} hcd_port_config_t;
/**
@@ -225,8 +227,6 @@ esp_err_t hcd_uninstall(void);
*
* After a port is initialized, it will be put into the HCD_PORT_STATE_NOT_POWERED state.
*
* @note The host controller only has one port, thus the only valid port_number is 1
*
* @param[in] port_number Port number
* @param[in] port_config Port configuration
* @param[out] port_hdl Port handle
+1
View File
@@ -60,6 +60,7 @@ typedef void (*hub_event_cb_t)(hub_event_data_t *event_data, void *arg);
* @brief Hub driver configuration
*/
typedef struct {
unsigned port_map; /**< Bitmap of root ports to enable */
usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */
void *proc_req_cb_arg; /**< Processing request callback argument */
hub_event_cb_t event_cb; /**< Hub event callback */
+19 -5
View File
@@ -16,9 +16,11 @@ Warning: The USB Host Library API is still a beta version and may be subject to
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "soc/usb_dwc_periph.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "esp_bit_defs.h"
#include "hub.h"
#include "enum.h"
#include "usbh.h"
@@ -466,16 +468,26 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
- Hub
*/
// For backward compatibility accept 0 too
const unsigned peripheral_map = config->peripheral_map == 0 ? BIT0 : config->peripheral_map;
// Install USB PHY (if necessary). USB PHY driver will also enable the underlying Host Controller
if (!config->skip_phy_setup) {
bool init_utmi_phy = false; // Default value for Linux simulation
#if SOC_USB_OTG_SUPPORTED // In case we run on a real target, select the PHY from usb_dwc_info description structure
// Right now we support only one peripheral, can be extended in future
int peripheral_index = 0;
if (peripheral_map & BIT1) {
peripheral_index = 1;
}
init_utmi_phy = (usb_dwc_info.controllers[peripheral_index].supported_phys == USB_PHY_INST_UTMI_0);
#endif // SOC_USB_OTG_SUPPORTED
// Host Library defaults to internal PHY
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
#if CONFIG_IDF_TARGET_ESP32P4 // ESP32-P4 has 2 USB-DWC peripherals, each with its dedicated PHY. We support HS+UTMI only ATM.
.target = USB_PHY_TARGET_UTMI,
#else
.target = USB_PHY_TARGET_INT,
#endif
.target = init_utmi_phy ? USB_PHY_TARGET_UTMI : USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, // In Host mode, the speed is determined by the connected device
.ext_io_conf = NULL,
@@ -493,6 +505,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
hcd_fifo_settings_t user_fifo_config;
hcd_config_t hcd_config = {
.intr_flags = config->intr_flags,
.peripheral_map = peripheral_map,
.fifo_config = NULL, // Default: use bias strategy from Kconfig
};
@@ -547,6 +560,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
// Install Hub
hub_config_t hub_config = {
.port_map = peripheral_map, // Each USB-OTG peripheral maps to a root port
.proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL,
.event_cb = hub_event_callback,