mirror of
https://github.com/espressif/esp-idf.git
synced 2025-09-04 23:27:25 +00:00

Introduce a new HAL API `usb_dwc_hal_set_fifo_config()` that allows advanced users to manually configure RX, Non-Periodic TX, and Periodic TX FIFO sizes. This offers fine-grained control beyond the previous bias-based sizing approach. The HAL function no longer returns `esp_err_t`, and internal validations are enforced via `HAL_ASSERT()`. Responsibility for input validation has been moved to the HCD layer. FIFO configuration must be applied before any USB pipes are created or activated. This feature is intended for use during `usb_host_install()`. If no custom FIFO configuration is provided (i.e., all values are zero), the driver falls back to a bias-based default layout based on Kconfig settings (`CONFIG_USB_HOST_HW_BUFFER_BIAS_*`). Bias resolution is done inside `hcd_port_init()`. The `port_obj_t` structure has been extended with a `fifo_config` field, which stores the configuration to allow re-application after a USB port reset. Obsolete FIFO bias enums (`usb_hal_fifo_bias_t`, `hcd_port_fifo_bias_t`) and related APIs (`hcd_port_set_fifo_bias()`) have been removed in favor of the new structure-based mechanism. The HCD initialization and port reset flow has been updated to use the explicit FIFO configuration. USB Host maintainer documentation (`maintainers.md`) has been updated accordingly. Test cases were updated to remove the usage of removed bias API and now rely on default or custom FIFO configuration.
858 lines
31 KiB
C
858 lines
31 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "sdkconfig.h"
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <sys/queue.h>
|
|
#include "esp_err.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_private/critical_section.h"
|
|
#include "esp_log.h"
|
|
#include "usb_private.h"
|
|
#include "hcd.h"
|
|
#include "hub.h"
|
|
#include "usb/usb_helpers.h"
|
|
|
|
#if ENABLE_USB_HUBS
|
|
#include "ext_hub.h"
|
|
#endif // ENABLE_USB_HUBS
|
|
|
|
/*
|
|
Implementation of the HUB driver that only supports the Root Hub with a single port. Therefore, we currently don't
|
|
implement the bare minimum to control the root HCD port.
|
|
*/
|
|
|
|
#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
|
|
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_PTX
|
|
#else // CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED
|
|
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED
|
|
#endif
|
|
|
|
#define PORT_REQ_DISABLE 0x01
|
|
#define PORT_REQ_RECOVER 0x02
|
|
|
|
/**
|
|
* @brief Hub driver action flags
|
|
*/
|
|
typedef enum {
|
|
HUB_DRIVER_ACTION_ROOT_EVENT = (1 << 0),
|
|
HUB_DRIVER_ACTION_ROOT_REQ = (1 << 1),
|
|
#if ENABLE_USB_HUBS
|
|
HUB_DRIVER_ACTION_EXT_HUB = (1 << 6),
|
|
HUB_DRIVER_ACTION_EXT_PORT = (1 << 7)
|
|
#endif // ENABLE_USB_HUBS
|
|
} hub_flag_action_t;
|
|
|
|
/**
|
|
* @brief Root port states
|
|
*/
|
|
typedef enum {
|
|
ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */
|
|
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;
|
|
|
|
/**
|
|
* @brief Hub device tree node
|
|
*
|
|
* Object type of a node in the USB device tree that is maintained by the Hub driver
|
|
*/
|
|
struct dev_tree_node_s {
|
|
TAILQ_ENTRY(dev_tree_node_s) tailq_entry; /**< Entry for the device tree node object tailq */
|
|
unsigned int uid; /**< Device's unique ID */
|
|
usb_device_handle_t parent_dev_hdl; /**< Device's parent handle */
|
|
uint8_t parent_port_num; /**< Device's parent port number */
|
|
};
|
|
|
|
typedef struct dev_tree_node_s dev_tree_node_t;
|
|
|
|
typedef struct {
|
|
struct {
|
|
union {
|
|
struct {
|
|
hub_flag_action_t actions: 8; /**< Hub actions */
|
|
uint32_t reserved24: 24; /**< Reserved */
|
|
};
|
|
uint32_t val; /**< Hub flag action value */
|
|
} flags; /**< Hub flags */
|
|
root_port_state_t root_port_state; /**< Root port state */
|
|
unsigned int port_reqs; /**< Root port request flag */
|
|
} dynamic; /**< Dynamic members. Require a critical section */
|
|
|
|
struct {
|
|
TAILQ_HEAD(tailhead_devs, dev_tree_node_s) dev_nodes_tailq; /**< Tailq of attached devices */
|
|
} single_thread; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
|
|
|
|
struct {
|
|
hcd_port_handle_t root_port_hdl; /**< Root port HCD handle */
|
|
usb_proc_req_cb_t proc_req_cb; /**< Process request callback */
|
|
void *proc_req_cb_arg; /**< Process request callback argument */
|
|
hub_event_cb_t event_cb; /**< Hub Driver event callback */
|
|
void *event_cb_arg; /**< Event callback argument */
|
|
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
|
|
} hub_driver_t;
|
|
|
|
static hub_driver_t *p_hub_driver_obj = NULL;
|
|
|
|
const char *HUB_DRIVER_TAG = "HUB";
|
|
|
|
DEFINE_CRIT_SECTION_LOCK_STATIC(hub_driver_lock);
|
|
#define HUB_DRIVER_ENTER_CRITICAL_ISR() esp_os_enter_critical_isr(&hub_driver_lock)
|
|
#define HUB_DRIVER_EXIT_CRITICAL_ISR() esp_os_exit_critical_isr(&hub_driver_lock)
|
|
#define HUB_DRIVER_ENTER_CRITICAL() esp_os_enter_critical(&hub_driver_lock)
|
|
#define HUB_DRIVER_EXIT_CRITICAL() esp_os_exit_critical(&hub_driver_lock)
|
|
#define HUB_DRIVER_ENTER_CRITICAL_SAFE() esp_os_enter_critical_safe(&hub_driver_lock)
|
|
#define HUB_DRIVER_EXIT_CRITICAL_SAFE() esp_os_exit_critical_safe(&hub_driver_lock)
|
|
|
|
#define HUB_DRIVER_CHECK(cond, ret_val) ({ \
|
|
if (!(cond)) { \
|
|
return (ret_val); \
|
|
} \
|
|
})
|
|
#define HUB_DRIVER_CHECK_FROM_CRIT(cond, ret_val) ({ \
|
|
if (!(cond)) { \
|
|
HUB_DRIVER_EXIT_CRITICAL(); \
|
|
return ret_val; \
|
|
} \
|
|
})
|
|
|
|
// ------------------------------------------------- Forward Declare ---------------------------------------------------
|
|
|
|
/**
|
|
* @brief HCD port callback for the root port
|
|
*
|
|
* - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process()
|
|
* - Under the current HCD implementation, this callback should only be ever be called in an ISR
|
|
* - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run
|
|
*
|
|
* @param port_hdl HCD port handle
|
|
* @param port_event HCD port event
|
|
* @param user_arg Callback argument
|
|
* @param in_isr Whether callback is in an ISR context
|
|
* @return Whether a yield is required
|
|
*/
|
|
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr);
|
|
|
|
// ---------------------- Internal Logic ------------------------
|
|
|
|
/**
|
|
* @brief Creates new device tree node and propagate HUB_EVENT_CONNECTED event
|
|
*
|
|
* @return esp_err_t
|
|
*/
|
|
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;
|
|
// 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
|
|
}
|
|
|
|
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 = 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 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) {
|
|
// 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;
|
|
}
|
|
|
|
TAILQ_INSERT_TAIL(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry);
|
|
|
|
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 = dev_tree_node->uid,
|
|
},
|
|
};
|
|
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
|
|
|
|
return ret;
|
|
|
|
fail:
|
|
heap_caps_free(dev_tree_node);
|
|
return ret;
|
|
}
|
|
|
|
static esp_err_t dev_tree_node_reset_completed(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
dev_tree_node_t *dev_tree_node = NULL;
|
|
dev_tree_node_t *dev_tree_iter;
|
|
// Search the device tree nodes list for a device node with the specified parent
|
|
TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
|
|
if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
|
|
dev_tree_iter->parent_port_num == parent_port_num) {
|
|
dev_tree_node = dev_tree_iter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dev_tree_node == NULL) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Reset completed, but device tree node with port=%d not found", parent_port_num);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
hub_event_data_t event_data = {
|
|
.event = HUB_EVENT_RESET_COMPLETED,
|
|
.reset_completed = {
|
|
.uid = dev_tree_node->uid,
|
|
},
|
|
};
|
|
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t dev_tree_node_dev_gone(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
dev_tree_node_t *dev_tree_node = NULL;
|
|
dev_tree_node_t *dev_tree_iter;
|
|
// Search the device tree nodes list for a device node with the specified parent
|
|
TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
|
|
if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
|
|
dev_tree_iter->parent_port_num == parent_port_num) {
|
|
dev_tree_node = dev_tree_iter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dev_tree_node == NULL) {
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): device gone", dev_tree_node->uid);
|
|
|
|
hub_event_data_t event_data = {
|
|
.event = HUB_EVENT_DISCONNECTED,
|
|
.disconnected = {
|
|
.uid = dev_tree_node->uid,
|
|
},
|
|
};
|
|
p_hub_driver_obj->constant.event_cb(&event_data, p_hub_driver_obj->constant.event_cb_arg);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Frees device tree node
|
|
*
|
|
* @return esp_err_t
|
|
*/
|
|
static esp_err_t dev_tree_node_remove_by_parent(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
dev_tree_node_t *dev_tree_node = NULL;
|
|
dev_tree_node_t *dev_tree_iter;
|
|
// Search the device tree nodes list for a device node with the specified parent
|
|
TAILQ_FOREACH(dev_tree_iter, &p_hub_driver_obj->single_thread.dev_nodes_tailq, tailq_entry) {
|
|
if (dev_tree_iter->parent_dev_hdl == parent_dev_hdl &&
|
|
dev_tree_iter->parent_port_num == parent_port_num) {
|
|
dev_tree_node = dev_tree_iter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dev_tree_node == NULL) {
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Device tree node (parent_port=%d): not found", parent_port_num);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
ESP_LOGD(HUB_DRIVER_TAG, "Device tree node (uid=%d): freeing", dev_tree_node->uid);
|
|
|
|
TAILQ_REMOVE(&p_hub_driver_obj->single_thread.dev_nodes_tailq, dev_tree_node, tailq_entry);
|
|
heap_caps_free(dev_tree_node);
|
|
return ESP_OK;
|
|
}
|
|
|
|
// ---------------------- Callbacks ------------------------
|
|
|
|
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL_SAFE();
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_EVENT;
|
|
HUB_DRIVER_EXIT_CRITICAL_SAFE();
|
|
assert(in_isr); // Currently, this callback should only ever be called from an ISR context
|
|
return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
#ifdef ENABLE_USB_HUBS
|
|
static bool ext_hub_callback(bool in_isr, void *user_arg)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL_SAFE();
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_HUB;
|
|
HUB_DRIVER_EXIT_CRITICAL_SAFE();
|
|
return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
static void ext_port_callback(void *user_arg)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_EXT_PORT;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
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_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(ext_hub_hdl,
|
|
event_data->connected.parent_port_num,
|
|
&port_speed) != ESP_OK) {
|
|
goto new_ds_dev_err;
|
|
}
|
|
|
|
// TODO: IDF-10023 Move responsibility of parent-child tree building to Hub Driver instead of USBH
|
|
usb_device_info_t parent_dev_info;
|
|
ESP_ERROR_CHECK(usbh_dev_get_info(event_data->connected.parent_dev_hdl, &parent_dev_info));
|
|
if (parent_dev_info.speed == USB_SPEED_HIGH) {
|
|
if (port_speed != parent_dev_info.speed) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Connected device is %s, transaction translator (TT) is not supported",
|
|
(char *[]) {
|
|
"LS", "FS", "HS"
|
|
}[port_speed]);
|
|
goto new_ds_dev_err;
|
|
}
|
|
}
|
|
|
|
if (dev_tree_node_new(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new downstream device");
|
|
goto new_ds_dev_err;
|
|
}
|
|
break;
|
|
new_ds_dev_err:
|
|
ext_hub_port_disable(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));
|
|
break;
|
|
case EXT_PORT_DISCONNECTED:
|
|
// The node could be freed by now, no need to verify the result here
|
|
dev_tree_node_dev_gone(event_data->disconnected.parent_dev_hdl, event_data->disconnected.parent_port_num);
|
|
break;
|
|
default:
|
|
// Should never occur
|
|
abort();
|
|
break;
|
|
}
|
|
}
|
|
#endif // ENABLE_USB_HUBS
|
|
|
|
// ---------------------- Handlers -------------------------
|
|
static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
|
|
{
|
|
hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl);
|
|
switch (port_event) {
|
|
case HCD_PORT_EVENT_NONE:
|
|
// Nothing to do
|
|
break;
|
|
case HCD_PORT_EVENT_CONNECTION: {
|
|
if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed");
|
|
goto reset_err;
|
|
}
|
|
ESP_LOGD(HUB_DRIVER_TAG, "Root port reset");
|
|
usb_speed_t speed;
|
|
if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) {
|
|
goto new_dev_err;
|
|
}
|
|
|
|
if (dev_tree_node_new(NULL, 0, speed) != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new device");
|
|
goto new_dev_err;
|
|
}
|
|
|
|
// Change Port state
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
break;
|
|
new_dev_err:
|
|
// We allow this to fail in case a disconnect/port error happens while disabling.
|
|
hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
|
|
reset_err:
|
|
break;
|
|
}
|
|
case HCD_PORT_EVENT_DISCONNECTION:
|
|
case HCD_PORT_EVENT_ERROR:
|
|
case HCD_PORT_EVENT_OVERCURRENT: {
|
|
bool port_has_device = false;
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
switch (p_hub_driver_obj->dynamic.root_port_state) {
|
|
case ROOT_PORT_STATE_POWERED: // This occurred before enumeration
|
|
case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled
|
|
// Therefore, there's no device object to clean up, and we can go straight to port recovery
|
|
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
|
|
break;
|
|
case ROOT_PORT_STATE_NOT_POWERED: // The user turned off ports' power. Indicate to USBH that the device is gone
|
|
case ROOT_PORT_STATE_ENABLED: // There is an enabled (active) device. Indicate to USBH that the device is gone
|
|
port_has_device = true;
|
|
break;
|
|
default:
|
|
abort(); // Should never occur
|
|
break;
|
|
}
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
if (port_has_device) {
|
|
ESP_ERROR_CHECK(dev_tree_node_dev_gone(NULL, 0));
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
abort(); // Should never occur
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void root_port_req(hcd_port_handle_t root_port_hdl)
|
|
{
|
|
unsigned int port_reqs;
|
|
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
port_reqs = p_hub_driver_obj->dynamic.port_reqs;
|
|
p_hub_driver_obj->dynamic.port_reqs = 0;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
if (port_reqs & PORT_REQ_DISABLE) {
|
|
ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port");
|
|
// We allow this to fail in case a disconnect/port error happens while disabling.
|
|
hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
|
|
}
|
|
if (port_reqs & PORT_REQ_RECOVER) {
|
|
ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port");
|
|
ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl));
|
|
|
|
// In case the port's power was turned off with usb_host_lib_set_root_port_power(false)
|
|
// we will not turn on the power during port recovery
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
const root_port_state_t root_state = p_hub_driver_obj->dynamic.root_port_state;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
if (root_state != ROOT_PORT_STATE_NOT_POWERED) {
|
|
ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON));
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
}
|
|
}
|
|
}
|
|
|
|
static esp_err_t root_port_recycle(void)
|
|
{
|
|
// Device is free, we can now request its port be recycled
|
|
hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
// How the port is recycled will depend on the port's state
|
|
switch (port_state) {
|
|
case HCD_PORT_STATE_ENABLED:
|
|
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE;
|
|
break;
|
|
case HCD_PORT_STATE_RECOVERY:
|
|
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
|
|
break;
|
|
default:
|
|
abort(); // Should never occur
|
|
break;
|
|
}
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t root_port_disable(void)
|
|
{
|
|
hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
|
|
HUB_DRIVER_CHECK(port_state == HCD_PORT_STATE_ENABLED, ESP_ERR_INVALID_STATE);
|
|
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE;
|
|
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_ACTION_ROOT_REQ;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
|
|
return ESP_OK;
|
|
}
|
|
|
|
// ---------------------------------------------- Hub Driver Functions -------------------------------------------------
|
|
|
|
esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
esp_err_t ret;
|
|
|
|
// Allocate Hub driver object
|
|
hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT);
|
|
if (hub_driver_obj == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
#if ENABLE_USB_HUBS
|
|
// Install External Port driver
|
|
ext_port_driver_config_t ext_port_config = {
|
|
.proc_req_cb = ext_port_callback,
|
|
.event_cb = ext_port_event_callback,
|
|
.hub_request_cb = ext_hub_request,
|
|
};
|
|
ret = ext_port_install(&ext_port_config);
|
|
if (ret != ESP_OK) {
|
|
goto err_ext_port;
|
|
}
|
|
|
|
// Install External HUB driver
|
|
ext_hub_config_t ext_hub_config = {
|
|
.proc_req_cb = ext_hub_callback,
|
|
.port_driver = ext_port_get_driver(),
|
|
};
|
|
ret = ext_hub_install(&ext_hub_config);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Ext hub install error: %s", esp_err_to_name(ret));
|
|
goto err_ext_hub;
|
|
}
|
|
*client_ret = ext_hub_get_client();
|
|
#else
|
|
*client_ret = NULL;
|
|
#endif // ENABLE_USB_HUBS
|
|
|
|
// Install HCD port
|
|
hcd_port_config_t port_config = {
|
|
.callback = root_port_callback,
|
|
.callback_arg = NULL,
|
|
.context = NULL,
|
|
};
|
|
hcd_port_handle_t root_port_hdl;
|
|
ret = hcd_port_init(HUB_ROOT_PORT_NUM, &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;
|
|
}
|
|
|
|
// Initialize Hub driver object
|
|
hub_driver_obj->constant.root_port_hdl = root_port_hdl;
|
|
hub_driver_obj->constant.proc_req_cb = hub_config->proc_req_cb;
|
|
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;
|
|
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;
|
|
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
if (p_hub_driver_obj != NULL) {
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
ret = ESP_ERR_INVALID_STATE;
|
|
goto assign_err;
|
|
}
|
|
p_hub_driver_obj = hub_driver_obj;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
return ret;
|
|
|
|
assign_err:
|
|
ESP_ERROR_CHECK(hcd_port_deinit(root_port_hdl));
|
|
err:
|
|
#if ENABLE_USB_HUBS
|
|
ext_hub_uninstall();
|
|
err_ext_hub:
|
|
ext_port_uninstall();
|
|
err_ext_port:
|
|
#endif // ENABLE_USB_HUBS
|
|
heap_caps_free(hub_driver_obj);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_uninstall(void)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE);
|
|
hub_driver_t *hub_driver_obj = p_hub_driver_obj;
|
|
p_hub_driver_obj = NULL;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
#if ENABLE_USB_HUBS
|
|
ESP_ERROR_CHECK(ext_hub_uninstall());
|
|
ESP_ERROR_CHECK(ext_port_uninstall());
|
|
#endif // ENABLE_USB_HUBS
|
|
|
|
ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl));
|
|
// Free Hub driver resources
|
|
heap_caps_free(hub_driver_obj);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t hub_root_start(void)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
// Power ON the root port
|
|
esp_err_t ret;
|
|
ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON);
|
|
if (ret == ESP_OK) {
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_root_stop(void)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_NOT_POWERED) {
|
|
// The HUB was already stopped by usb_host_lib_set_root_port_power(false)
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
return ESP_OK;
|
|
}
|
|
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_NOT_POWERED;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
// HCD_PORT_CMD_POWER_OFF will only fail if the port is already powered_off
|
|
// This should never happen, so we assert ret == ESP_OK
|
|
const esp_err_t ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_OFF);
|
|
assert(ret == ESP_OK);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num, unsigned int dev_uid)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
esp_err_t ret;
|
|
|
|
if (parent_port_num == 0) {
|
|
ret = root_port_recycle();
|
|
} else {
|
|
#if ENABLE_USB_HUBS
|
|
ext_hub_handle_t ext_hub_hdl = NULL;
|
|
ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
|
|
ret = ext_hub_port_recycle(ext_hub_hdl, parent_port_num);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port recycle error: %s", esp_err_to_name(ret));
|
|
}
|
|
#else
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Recycling External Port is not available (External Hub support disabled)");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
#endif // ENABLE_USB_HUBS
|
|
}
|
|
|
|
if (ret == ESP_OK) {
|
|
ESP_ERROR_CHECK(dev_tree_node_remove_by_parent(parent_dev_hdl, parent_port_num));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_port_reset(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
esp_err_t ret;
|
|
|
|
if (parent_port_num == 0) {
|
|
ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue root port reset");
|
|
} else {
|
|
ret = dev_tree_node_reset_completed(NULL, 0);
|
|
}
|
|
} else {
|
|
#if ENABLE_USB_HUBS
|
|
ext_hub_handle_t ext_hub_hdl = NULL;
|
|
ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
|
|
ret = ext_hub_port_reset(ext_hub_hdl, parent_port_num);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port reset error: %s", esp_err_to_name(ret));
|
|
}
|
|
#else
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Resetting External Port is not available (External Hub support disabled)");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
#endif // ENABLE_USB_HUBS
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_port_active(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
esp_err_t ret;
|
|
|
|
if (parent_port_num == 0) {
|
|
// Root port no need to be activated
|
|
ret = ESP_OK;
|
|
} else {
|
|
#if ENABLE_USB_HUBS
|
|
// External Hub port
|
|
ext_hub_handle_t ext_hub_hdl = NULL;
|
|
ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
|
|
ret = ext_hub_port_active(ext_hub_hdl, parent_port_num);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(HUB_DRIVER_TAG, "Ext hub port activation error: %s", esp_err_to_name(ret));
|
|
}
|
|
#else
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
#endif // ENABLE_USB_HUBS
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_port_num)
|
|
{
|
|
esp_err_t ret;
|
|
|
|
if (parent_port_num == 0) {
|
|
ret = root_port_disable();
|
|
} else {
|
|
#if ENABLE_USB_HUBS
|
|
// External Hub port
|
|
ext_hub_handle_t ext_hub_hdl = NULL;
|
|
ext_hub_get_handle(parent_dev_hdl, &ext_hub_hdl);
|
|
ret = ext_hub_port_disable(ext_hub_hdl, parent_port_num);
|
|
#else
|
|
ESP_LOGW(HUB_DRIVER_TAG, "Activating External Port is not available (External Hub support disabled)");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
#endif // ENABLE_USB_HUBS
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_notify_new_dev(uint8_t dev_addr)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
esp_err_t ret;
|
|
#if ENABLE_USB_HUBS
|
|
ret = ext_hub_new_dev(dev_addr);
|
|
#else
|
|
// Verify the device descriptor and if the bDeviceClass is a Hub class,
|
|
// show the warning message, that Hub support feature is not enabled
|
|
usb_device_handle_t dev_hdl = NULL;
|
|
const usb_device_desc_t *device_desc = NULL;
|
|
// Open device
|
|
if (usbh_devs_open(dev_addr, &dev_hdl) == ESP_OK) {
|
|
// Get Device Descriptor
|
|
if (usbh_dev_get_desc(dev_hdl, &device_desc) == ESP_OK) {
|
|
if (device_desc->bDeviceClass == USB_CLASS_HUB) {
|
|
ESP_LOGW(HUB_DRIVER_TAG, "External Hubs support disabled, Hub device was not initialized");
|
|
}
|
|
}
|
|
// Close device
|
|
usbh_dev_close(dev_hdl);
|
|
}
|
|
// Logic should not stop the flow, so no error to return
|
|
ret = ESP_OK;
|
|
#endif // ENABLE_USB_HUBS
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t hub_notify_dev_gone(uint8_t dev_addr)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
esp_err_t ret;
|
|
#if ENABLE_USB_HUBS
|
|
ret = ext_hub_dev_gone(dev_addr);
|
|
#else
|
|
// Nothing to do, while Hubs support is not enabled
|
|
ret = ESP_OK;
|
|
#endif // ENABLE_USB_HUBS
|
|
return ret;
|
|
}
|
|
|
|
#if (ENABLE_USB_HUBS)
|
|
esp_err_t hub_notify_all_free(void)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
return ext_hub_all_free();
|
|
}
|
|
#endif // ENABLE_USB_HUBS
|
|
|
|
esp_err_t hub_process(void)
|
|
{
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
uint32_t action_flags = p_hub_driver_obj->dynamic.flags.actions;
|
|
p_hub_driver_obj->dynamic.flags.actions = 0;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
|
|
while (action_flags) {
|
|
#if ENABLE_USB_HUBS
|
|
if (action_flags & HUB_DRIVER_ACTION_EXT_PORT) {
|
|
ESP_ERROR_CHECK(ext_port_process());
|
|
}
|
|
if (action_flags & HUB_DRIVER_ACTION_EXT_HUB) {
|
|
ESP_ERROR_CHECK(ext_hub_process());
|
|
}
|
|
#endif // ENABLE_USB_HUBS
|
|
if (action_flags & HUB_DRIVER_ACTION_ROOT_EVENT) {
|
|
root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl);
|
|
}
|
|
if (action_flags & HUB_DRIVER_ACTION_ROOT_REQ) {
|
|
root_port_req(p_hub_driver_obj->constant.root_port_hdl);
|
|
}
|
|
|
|
HUB_DRIVER_ENTER_CRITICAL();
|
|
action_flags = p_hub_driver_obj->dynamic.flags.actions;
|
|
p_hub_driver_obj->dynamic.flags.actions = 0;
|
|
HUB_DRIVER_EXIT_CRITICAL();
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|