Merge branch 'feature/usb_host_hub_support_collective_backport_p3_v5.2' into 'release/v5.2'

feat(usb_host): Hub Support Collective backport part 3/3 (v5.2)

See merge request espressif/esp-idf!33283
This commit is contained in:
morris 2024-11-20 10:40:09 +08:00
commit 1a49691899
25 changed files with 2184 additions and 219 deletions

View File

@ -620,3 +620,5 @@ 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

View File

@ -26,7 +26,8 @@ if(CONFIG_SOC_USB_OTG_SUPPORTED)
endif()
if(CONFIG_USB_HOST_HUBS_SUPPORTED)
list(APPEND srcs "ext_hub.c")
list(APPEND srcs "ext_hub.c"
"ext_port.c")
endif()
idf_component_register(SRCS ${srcs}

View File

@ -112,6 +112,63 @@ menu "USB-OTG"
help
Enables support for connecting multiple Hubs simultaneously.
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
# Todo: IDF-11283
int
default 1
help
Amount of attempts to reset the device.
The default value is 1.
config USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS
int "Reset recovery delay in ms"
default 30
help
After a port stops driving the reset signal, the USB 2.0 specification requires that
the "USB System Software guarantees a minimum of 10 ms for reset recovery" before the
attached device is expected to respond to data transfers (see USB 2.0 chapter 7.1.7.3 for
more details).
The device may ignore any data transfers during the recovery interval.
The default value is set to 30 ms to be safe.
config USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE
bool "Custom bPwrOn2PwrGood value"
default n
help
Enables the possibility to configure custom time for the power-on sequence on a port
until power is good on that port.
When enabled, applies the custom PwrOn2PwrGood delay.
When disabled, applies the PwrOn2PwrGood value from the Hub Descriptor.
config USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_MS
depends on USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE
int "PwrOn2PwrGood delay in ms"
default 100
range 0 5000
help
Custom value of delay from the time the power-on sequence begins on a port
until power is good on that port.
Value 0 is used for a hub with no power switches.
The default value is 100 ms.
endmenu #Downstream Port configuration
endmenu #Hub Driver Configuration
# Hidden or compatibility options

View File

@ -765,9 +765,15 @@ static esp_err_t control_response_handling(enum_stage_t stage)
usb_transfer_t *ctrl_xfer = &p_enum_driver->constant.urb->transfer;
if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(ENUM_TAG, "Bad transfer status %d: %s",
ctrl_xfer->status,
enum_stage_strings[stage]);
if (ctrl_xfer->status == USB_TRANSFER_STATUS_STALL &&
stage >= ENUM_STAGE_CHECK_SHORT_LANGID_TABLE &&
stage <= ENUM_STAGE_CHECK_FULL_SER_STR_DESC) {
// String Descriptor request could be STALLed, if the device doesn't have them
} else {
ESP_LOGE(ENUM_TAG, "Bad transfer status %d: %s",
ctrl_xfer->status,
enum_stage_strings[stage]);
}
return ret;
}
@ -1015,10 +1021,6 @@ static bool set_next_stage(bool last_stage_pass)
next_stage = last_stage + 1;
}
} else {
ESP_LOGE(ENUM_TAG, "[%d:%d] %s FAILED",
p_enum_driver->single_thread.parent_dev_addr,
p_enum_driver->single_thread.parent_port_num,
enum_stage_strings[last_stage]);
// These stages cannot fail
assert(last_stage != ENUM_STAGE_SET_ADDR_RECOVERY &&
last_stage != ENUM_STAGE_SELECT_CONFIG &&
@ -1055,6 +1057,10 @@ static bool set_next_stage(bool last_stage_pass)
break;
default:
// Stage is not allowed to failed. Cancel enumeration.
ESP_LOGE(ENUM_TAG, "[%d:%d] %s FAILED",
p_enum_driver->single_thread.parent_dev_addr,
p_enum_driver->single_thread.parent_port_num,
enum_stage_strings[last_stage]);
next_stage = ENUM_STAGE_CANCEL;
break;
}

View File

@ -5,18 +5,17 @@
*/
#include <string.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb_private.h"
#include "ext_hub.h"
#include "ext_port.h"
#include "usb/usb_helpers.h"
typedef struct ext_port_s *ext_port_hdl_t; /* This will be implemented during ext_port driver implementation */
#define EXT_HUB_MAX_STATUS_BYTES_SIZE (sizeof(uint32_t))
#define EXT_HUB_STATUS_CHANGE_FLAG (1 << 0)
#define EXT_HUB_STATUS_PORT1_CHANGE_FLAG (1 << 1)
@ -396,8 +395,15 @@ 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,
.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,
};
assert(p_ext_hub_driver->constant.port_driver);
esp_err_t ret = p_ext_hub_driver->constant.port_driver->new (NULL, (void**) &ext_hub_dev->constant.ports[port_idx]);
esp_err_t ret = p_ext_hub_driver->constant.port_driver->new (&port_config, (void**) &ext_hub_dev->constant.ports[port_idx]);
if (ret != ESP_OK) {
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port allocation error: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret));
goto fail;
@ -418,7 +424,7 @@ static esp_err_t device_port_free(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx)
assert(ext_hub_dev->single_thread.maxchild != 0);
assert(p_ext_hub_driver->constant.port_driver);
esp_err_t ret = p_ext_hub_driver->constant.port_driver->free(ext_hub_dev->constant.ports[port_idx]);
esp_err_t ret = p_ext_hub_driver->constant.port_driver->del(ext_hub_dev->constant.ports[port_idx]);
if (ret != ESP_OK) {
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to free port: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret));
@ -1038,6 +1044,7 @@ static void handle_device(ext_hub_dev_t *ext_hub_dev)
// 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:
@ -1118,8 +1125,7 @@ esp_err_t ext_hub_install(const ext_hub_config_t *config)
{
esp_err_t ret;
ext_hub_driver_t *ext_hub_drv = heap_caps_calloc(1, sizeof(ext_hub_driver_t), MALLOC_CAP_DEFAULT);
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex();
if (ext_hub_drv == NULL || mux_lock == NULL) {
if (ext_hub_drv == NULL) {
ret = ESP_ERR_NO_MEM;
goto err;
}
@ -1151,9 +1157,6 @@ esp_err_t ext_hub_install(const ext_hub_config_t *config)
return ESP_OK;
err:
if (mux_lock != NULL) {
vSemaphoreDelete(mux_lock);
}
heap_caps_free(ext_hub_drv);
return ret;
}
@ -1672,7 +1675,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num,
esp_err_t ext_hub_set_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_INVALID_STATE);
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_HUB_EXIT_CRITICAL();
esp_err_t ret;
@ -1701,7 +1704,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu
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_INVALID_STATE);
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_HUB_EXIT_CRITICAL();
esp_err_t ret;
@ -1730,7 +1733,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_
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_INVALID_STATE);
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_NOT_ALLOWED);
EXT_HUB_EXIT_CRITICAL();
esp_err_t ret;

1404
components/usb/ext_port.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1731,6 +1731,8 @@ esp_err_t hcd_pipe_alloc(hcd_port_handle_t port_hdl, const hcd_pipe_config_t *pi
bool chan_allocated = usb_dwc_hal_chan_alloc(port->hal, pipe->chan_obj, (void *) pipe);
if (!chan_allocated) {
HCD_EXIT_CRITICAL();
// The only reason why alloc channel could return false is no more free channels
ESP_LOGE(HCD_DWC_TAG, "No more HCD channels available");
ret = ESP_ERR_NOT_SUPPORTED;
goto err;
}

View File

@ -20,7 +20,7 @@
#include "usb/usb_helpers.h"
#if ENABLE_USB_HUBS
#include "ext_hub.h"
#include "ext_port.h"
#endif // ENABLE_USB_HUBS
/*
@ -320,6 +320,72 @@ static bool ext_hub_callback(bool in_isr, void *user_arg)
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_event_data_t *event_data, void *arg)
{
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,
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 (!CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS)
if (port_speed == USB_SPEED_LOW) {
ESP_LOGE(HUB_DRIVER_TAG, "Connected %s-speed device, not supported",
(char *[]) {
"Low", "Full", "High"
}[port_speed]);
goto new_ds_dev_err;
}
#endif // CONFIG_USB_HOST_EXT_PORT_SUPPORT_LS
if (new_dev_tree_node(event_data->connected.parent_dev_hdl, event_data->connected.parent_port_num, port_speed) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to add new downstream device");
goto new_ds_dev_err;
}
break;
new_ds_dev_err:
ext_hub_port_disable(event_data->connected.ext_hub_hdl, event_data->connected.parent_port_num);
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 -------------------------
@ -479,10 +545,20 @@ esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
}
#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,
};
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 = NULL,
.port_driver = ext_port_get_driver(),
};
ret = ext_hub_install(&ext_hub_config);
if (ret != ESP_OK) {
@ -536,6 +612,8 @@ 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;
@ -552,6 +630,7 @@ esp_err_t hub_uninstall(void)
#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));
@ -739,10 +818,7 @@ esp_err_t hub_process(void)
while (action_flags) {
#if ENABLE_USB_HUBS
if (action_flags & HUB_DRIVER_ACTION_EXT_PORT) {
ESP_LOGW(HUB_DRIVER_TAG, "ext_port_process() has not been implemented yet");
/*
ESP_ERROR_CHECK(ext_port_process());
*/
}
if (action_flags & HUB_DRIVER_ACTION_EXT_HUB) {
ESP_ERROR_CHECK(ext_hub_process());

View File

@ -43,6 +43,28 @@ typedef enum {
USB_B_REQUEST_HUB_STOP_TT = 0x0B, /**< Stop TT. */
} usb_hub_class_request_t ;
/**
* @brief USB Hub Port state
*
* See USB 2.0 spec, 11.5.1 Downstream Facing Port State Descriptions
*/
typedef enum {
USB_PORT_STATE_NOT_CONFIGURED = 0x00, /**< The hub is not configured. A port transitions to and remains in this state whenever the value of the hub configuration is zero. */
USB_PORT_STATE_POWERED_OFF, /**< Powered_off: Port requires explicit request to transition. */
USB_PORT_STATE_DISCONNECTED, /**< In the Disconnected state, the ports differential transmitter and receiver are disabled and only connection detection is possible.*/
USB_PORT_STATE_DISABLED, /**< A port in the Disabled state will not propagate signaling in either the upstream or the downstream direction */
USB_PORT_STATE_RESETTING, /**< The duration of the Resetting state is nominally 10 ms to 20 ms (10 ms is preferred). */
USB_PORT_STATE_ENABLED, /**< While in this state, the output of the ports differential receiver is available to the Hub Repeater so that appropriate signaling transitions can establish upstream connectivity*/
USB_PORT_STATE_TRANSMIT, /**< This state is entered from the Enabled state on the transition of the Hub Repeater to the WFEOPFU state */
USB_PORT_STATE_TRANSMIT_R, /**< When in this state, the port repeats the resume K at the upstream facing port to the downstream facing port. */
USB_PORT_STATE_SUSPENDED, /**< While a port is in the Suspended state, the port's differential transmitter is disabled. */
USB_PORT_STATE_RESUMING, /**< While in this state, the hub drives a 'K' on the port. */
USB_PORT_STATE_SEND_EOR, /**< This state is entered from the Resuming state if the 20 ms timer expires. */
USB_PORT_STATE_RESTART_S, /**< A port enters the Restart_S state from the Suspended state when an SE0 or K is seen at the port and the Receiver is in the Suspended state */
USB_PORT_STATE_RESTART_E, /**< A port enters the Restart_E state from the Enabled state when an SE0 or K is seen at the port and the Receiver is in the Suspended state. */
USB_PORT_STATE_TESTING, /**< A port transitions to this state from any state when the port sees SetTest. */
} usb_hub_port_state_t;
/**
* @brief USB Hub Port feature selector codes
*

View File

@ -46,7 +46,7 @@ typedef struct {
esp_err_t (*active)(void *port_hdl);
esp_err_t (*disable)(void *port_hdl);
esp_err_t (*gone)(void *port_hdl);
esp_err_t (*free)(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);
@ -278,7 +278,7 @@ esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num,
*
* @return
* - ESP_OK: Port's feature set 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
*/
@ -293,7 +293,7 @@ esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_nu
*
* @return
* - ESP_OK: Port's feature cleared 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
*/
@ -310,7 +310,7 @@ esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_
*
* @return
* - ESP_OK: Port's status 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
*/

View File

@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "ext_hub.h"
#include "usb/usb_types_stack.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ext_port_s *ext_port_hdl_t;
// ------------------------------ Events ---------------------------------------
typedef enum {
EXT_PORT_CONNECTED = 0x01, /**< Port has a device connection event */
EXT_PORT_RESET_COMPLETED, /**< Port has completed the reset routine */
EXT_PORT_DISCONNECTED, /**< Port has a device disconnection event */
} ext_port_event_t;
/**
* @brief Event data object for External Port driver events
*/
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 */
struct {
usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */
uint8_t parent_port_num; /**< Ports' parent port number */
} reset_completed; /**< EXT_PORT_RESET_COMPLETED event specific data */
struct {
usb_device_handle_t parent_dev_hdl; /**< Ports' parent device handle */
uint8_t parent_port_num; /**< Ports' parent port number */
} disconnected; /**< EXT_PORT_DISCONNECTED event specific data */
};
} ext_port_event_data_t;
// ------------------------------ Callbacks ------------------------------------
/**
* @brief Callback used to indicate that the External Port Driver requires process callback
*
* @note For the Hub Driver only
*/
typedef void (*ext_port_cb_t)(void *user_arg);
/**
* @brief Callback used to indicate that the External Port driver has an event
*
* @note For the Hub Driver only
*/
typedef void (*ext_port_event_cb_t)(ext_port_event_data_t *event_data, void *arg);
// ----------------- External Port Driver configuration ------------------------
/**
* @brief External Port driver configuration
*/
typedef struct {
ext_port_cb_t proc_req_cb; /**< External Port process callback */
void *proc_req_cb_arg; /**< External Port process callback argument */
ext_port_event_cb_t event_cb; /**< External Port event callback */
void *event_cb_arg; /**< External Port event callback argument */
} ext_port_driver_config_t;
/**
* @brief External Port configuration
*
* Structure is used to create new port
*/
typedef 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 */
uint16_t port_power_delay_ms; /**< Ports' Power on time to Power Good, ms */
} ext_port_config_t;
// -------------------- External Port Processing Functions ---------------------
/**
* @brief Install the External Port Driver
*
* @note This function should only be called from the Hub Driver
*
* @param[in] config External Port Driver configuration
* @return
* - ESP_ERR_NOT_ALLOWED: The Driver was already installed
* - ESP_ERR_NO_MEM: Unable to install the Driver, no memory
* - ESP_OK: The Driver has been installed successfully
*/
esp_err_t ext_port_install(const ext_port_driver_config_t *config);
/**
* @brief Uninstall the External Port Driver
*
* @note This function should only be called from the Hub Driver
*
* @return
* - ESP_ERR_NOT_ALLOWED: The Driver was not installed
* - ESP_ERR_INVALID_STATE: The Driver has ports in the pending list and can't be uninstalled
* - ESP_OK: The Driver has been uninstall successfully
*/
esp_err_t ext_port_uninstall(void);
/**
* @brief External Port Driver's process function
*
* @note This function should only be called from the Hub Driver
*
* External Port Driver process function that must be called repeatedly to process the driver's actions and events.
* If blocking, the caller can block on the notification callback of source USB_PROC_REQ_SOURCE_HUB
* to run this function.
*
* @return
* - ESP_ERR_NOT_ALLOWED: The Driver was not installed
* - ESP_OK: The Driver processed completed
*/
esp_err_t ext_port_process(void);
/**
* @brief Returns External Port Driver's API
*
* @note This is a specific API for the External Hub Driver to handle the ports.
* @return
* - 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);
#ifdef __cplusplus
}
#endif

View File

@ -134,8 +134,8 @@ esp_err_t hub_root_stop(void);
*
* @note This function should only be called from the Host Library task
*
* @param[in] parent_dev_hdl
* @param[in] parent_port_num
* @param[in] parent_dev_hdl Parent device handle (is used to get the External Hub handle)
* @param[in] parent_port_num Parent number (is used to specify the External Port)
* @param[in] dev_uid Device's unique ID
*
* @return
@ -151,9 +151,8 @@ esp_err_t hub_port_recycle(usb_device_handle_t parent_dev_hdl, uint8_t parent_po
*
* @note This function should only be called from the Host Library task
*
* @param[in] parent_dev_hdl
* @param[in] parent_port_num
*
* @param[in] parent_dev_hdl Parent device handle (is used to get the External Hub handle)
* @param[in] parent_port_num Parent number (is used to specify the External Port)
* @return
* - ESP_OK: Port reset successful
* - ESP_ERR_INVALID_STATE: Hub driver is not installed
@ -198,6 +197,8 @@ esp_err_t hub_port_disable(usb_device_handle_t parent_dev_hdl, uint8_t parent_po
*
* If device is has a HUB class, then it will be added as External Hub to Hub Driver.
*
* @note This function should only be called from the Host Library task
*
* @param[in] dev_addr Device bus address
*
* @return
@ -211,6 +212,8 @@ esp_err_t hub_notify_new_dev(uint8_t dev_addr);
*
* If the device was an External Hub, then it will be removed from the Hub Driver.
*
* @note This function should only be called from the Host Library task
*
* @param[in] dev_addr Device bus address
*
* @return
@ -222,6 +225,8 @@ esp_err_t hub_notify_dev_gone(uint8_t dev_addr);
/**
* @brief Notify Hub driver that all devices should be freed
*
* @note This function should only be called from the Host Library task
*
* @return
* - ESP_OK: All the devices can be freed
* - ESP_ERR_INVALID_STATE: Hub driver is not in a correct state

View File

@ -6,3 +6,4 @@ CONFIG_HEAP_POISONING_COMPREHENSIVE=y
# CONFIG_UNITY_ENABLE_FLOAT is not set
# CONFIG_UNITY_ENABLE_DOUBLE is not set
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_USB_HOST_HUBS_SUPPORTED=y

View File

@ -6,3 +6,4 @@ CONFIG_HEAP_POISONING_COMPREHENSIVE=y
# CONFIG_UNITY_ENABLE_FLOAT is not set
# CONFIG_UNITY_ENABLE_DOUBLE is not set
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_USB_HOST_HUBS_SUPPORTED=y

View File

@ -1050,7 +1050,6 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl)
if (dev_obj->dynamic.open_count == 0) {
// Sanity check.
assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); // There cannot be any control transfer in-flight
assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when open_count reaches 0
if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_free) {
// Device is already gone or is awaiting to be freed. Trigger the USBH process to free the device
call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE);

View File

@ -2,3 +2,4 @@
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_USB_HOST_HUBS_SUPPORTED=y

View File

@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_USB_HOST_HUBS_SUPPORTED=y

View File

@ -28,6 +28,11 @@ static const char *TAG = "example";
#define APP_QUIT_PIN GPIO_NUM_0 // BOOT button on most boards
#define BUFFER_SIZE 4096 // The read/write performance can be improved with larger buffer for the cost of RAM, 4kB is enough for most usecases
// IMPORTANT NOTE
// MSC Class Driver is not fully support connecting devices through external Hub.
// TODO: Remove this line after MSC Class Driver will support it
static bool dev_present = false;
/**
* @brief Application Queue and its messages ID
*/
@ -77,7 +82,7 @@ static void gpio_cb(void *arg)
static void msc_event_cb(const msc_host_event_t *event, void *arg)
{
if (event->event == MSC_DEVICE_CONNECTED) {
ESP_LOGI(TAG, "MSC device connected");
ESP_LOGI(TAG, "MSC device connected (usb_addr=%d)", event->device.address);
app_message_t message = {
.id = APP_DEVICE_CONNECTED,
.data.new_dev_address = event->device.address,
@ -271,47 +276,56 @@ void app_main(void)
xQueueReceive(app_queue, &msg, portMAX_DELAY);
if (msg.id == APP_DEVICE_CONNECTED) {
// 1. MSC flash drive connected. Open it and map it to Virtual File System
ESP_ERROR_CHECK(msc_host_install_device(msg.data.new_dev_address, &msc_device));
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle));
if (dev_present) {
ESP_LOGW(TAG, "MSC Example handles only one device at a time");
} else {
// 0. Change flag
dev_present = true;
// 1. MSC flash drive connected. Open it and map it to Virtual File System
ESP_ERROR_CHECK(msc_host_install_device(msg.data.new_dev_address, &msc_device));
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
ESP_ERROR_CHECK(msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle));
// 2. Print information about the connected disk
msc_host_device_info_t info;
ESP_ERROR_CHECK(msc_host_get_device_info(msc_device, &info));
msc_host_print_descriptors(msc_device);
print_device_info(&info);
// 2. Print information about the connected disk
msc_host_device_info_t info;
ESP_ERROR_CHECK(msc_host_get_device_info(msc_device, &info));
msc_host_print_descriptors(msc_device);
print_device_info(&info);
// 3. List all the files in root directory
ESP_LOGI(TAG, "ls command output:");
struct dirent *d;
DIR *dh = opendir(MNT_PATH);
assert(dh);
while ((d = readdir(dh)) != NULL) {
printf("%s\n", d->d_name);
// 3. List all the files in root directory
ESP_LOGI(TAG, "ls command output:");
struct dirent *d;
DIR *dh = opendir(MNT_PATH);
assert(dh);
while ((d = readdir(dh)) != NULL) {
printf("%s\n", d->d_name);
}
closedir(dh);
// 4. The disk is mounted to Virtual File System, perform some basic demo file operation
file_operations();
// 5. Perform speed test
speed_test();
ESP_LOGI(TAG, "Example finished, you can disconnect the USB flash drive");
}
closedir(dh);
// 4. The disk is mounted to Virtual File System, perform some basic demo file operation
file_operations();
// 5. Perform speed test
speed_test();
ESP_LOGI(TAG, "Example finished, you can disconnect the USB flash drive");
}
if ((msg.id == APP_DEVICE_DISCONNECTED) || (msg.id == APP_QUIT)) {
if (vfs_handle) {
ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
vfs_handle = NULL;
}
if (msc_device) {
ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
msc_device = NULL;
if (dev_present) {
dev_present = false;
if (vfs_handle) {
ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
vfs_handle = NULL;
}
if (msc_device) {
ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
msc_device = NULL;
}
}
if (msg.id == APP_QUIT) {
// This will cause the usb_task to exit

View File

@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_USB_HOST_HUBS_SUPPORTED=y

View File

@ -5,19 +5,21 @@
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates the basic usage of the [USB Host Library API](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html) by implementing a pseudo class driver and a Host Library daemon task. The example does the following:
This example demonstrates the basic usage of the [USB Host Library API](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html) by implementing a pseudo class driver and a Host Library task. The example does the following:
1. Install Host Library and register a client
2. Waits for a device connection
3. Prints the device's information (such as device/configuration/string descriptors)
4. Waits for the device to disconnect
5. Deregister the client and uninstall the Host Library
5. Repeats steps 2 to 4 until a user pressess a button, which quits the `app`
6. If the button has been pressed, while a USB device is still connected, the user will be prompted to remove the device and push the button again to quit the `app`
7. Deregister the client, uninstall the Host Library and quit the `app`
The example demonstrates the following aspects of the USB Host Library API:
- How to use the Library API to:
- Install and uninstall the USB Host Library
- Run the library event handler function a daemon task
- Run the library event handler function and usb host library task
- How to handle library events
- How to use the Client API from a client task to:
- Register and deregister a client of the USB Host Library
@ -30,7 +32,7 @@ The example demonstrates the following aspects of the USB Host Library API:
### Hardware Required
An ESP board that supports USB-OTG. The example uses the ESP's internal USB PHY, however the internal USB PHY's pins will need to be connected to a USB port (i.e., a USB breakout board) as follows:
An ESP board that has a push button and supports USB-OTG. The example uses the ESP's internal USB PHY, however the internal USB PHY's pins will need to be connected to a USB port (i.e., a USB breakout board) as follows:
- GND and 5V signals of the ESP board to the GND and 5V lines of the USB port
- GPIO 19 to D-
@ -43,6 +45,7 @@ idf.py menuconfig
```
* The USB Host Stack has a maximum supported transfer size for control transfer during device enumeration. This size is specified via the USB_HOST_CONTROL_TRANSFER_MAX_SIZE configuration option and has a default value of 256 bytes. Therefore, if devices with length config/string descriptors are used, users may want to increase the size of this configuration.
* Push button GPIO selection
### Build and Flash
@ -61,14 +64,17 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output
```
I (261) cpu_start: Starting scheduler on PRO CPU.
I (267) DAEMON: Installing USB Host Library
I (297) CLASS: Registering Client
I (5067) CLASS: Opening device at address 1
I (5067) CLASS: Getting device information
I (5067) CLASS: Full speed
I (5067) CLASS: bConfigurationValue 1
I (5067) CLASS: Getting device descriptor
I (305) main_task: Started on CPU0
I (315) main_task: Calling app_main()
I (315) USB host lib: USB host library example
I (315) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:2
I (325) USB host lib: Installing USB Host Library
I (365) CLASS: Registering Client
I (745) CLASS: Opening device at address 1
I (745) CLASS: Getting device information
I (745) CLASS: Full speed
I (745) CLASS: bConfigurationValue 1
I (745) CLASS: Getting device descriptor
*** Device descriptor ***
bLength 18
bDescriptorType 1
@ -84,7 +90,7 @@ iManufacturer 1
iProduct 2
iSerialNumber 3
bNumConfigurations 1
I (5097) CLASS: Getting config descriptor
I (775) CLASS: Getting config descriptor
*** Configuration descriptor ***
bLength 9
bDescriptorType 2
@ -153,12 +159,20 @@ bMaxPower 500mA
bmAttributes 0x2 BULK
wMaxPacketSize 64
bInterval 1
I (5227) CLASS: Getting Manufacturer string descriptor
I (855) CLASS: Getting Manufacturer string descriptor
Espressif
I (5237) CLASS: Getting Product string descriptor
I (855) CLASS: Getting Product string descriptor
USB JTAG/serial debug unit
I (5247) CLASS: Getting Serial Number string descriptor
I (865) CLASS: Getting Serial Number string descriptor
7C:DF:A1:E0:10:50
W (2855) USB host lib: To shutdown example, remove all USB devices and press button again.
E (6135) USBH: Device 1 gone
I (9545) CLASS: Deregistering Client
I (9545) USB host lib: No more clients
I (9545) USB host lib: All devices marked as free
I (9545) USB host lib: No more clients and devices
I (9645) USB host lib: End of the example
I (9645) main_task: Returned from app_main()
```
## Troubleshooting

View File

@ -1,4 +1,4 @@
idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c"
INCLUDE_DIRS "."
PRIV_REQUIRES usb
PRIV_REQUIRES usb driver
)

View File

@ -0,0 +1,12 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config APP_QUIT_PIN
int "APP Quit button GPIO pin"
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
default 0
help
GPIO pin number to be used as APP_QUIT button.
endmenu

View File

@ -12,99 +12,139 @@
#define CLIENT_NUM_EVENT_MSG 5
#define ACTION_OPEN_DEV 0x01
#define ACTION_GET_DEV_INFO 0x02
#define ACTION_GET_DEV_DESC 0x04
#define ACTION_GET_CONFIG_DESC 0x08
#define ACTION_GET_STR_DESC 0x10
#define ACTION_CLOSE_DEV 0x20
#define ACTION_EXIT 0x40
typedef enum {
ACTION_OPEN_DEV = (1 << 0),
ACTION_GET_DEV_INFO = (1 << 1),
ACTION_GET_DEV_DESC = (1 << 2),
ACTION_GET_CONFIG_DESC = (1 << 3),
ACTION_GET_STR_DESC = (1 << 4),
ACTION_CLOSE_DEV = (1 << 5),
} action_t;
#define DEV_MAX_COUNT 128
typedef struct {
usb_host_client_handle_t client_hdl;
uint8_t dev_addr;
usb_device_handle_t dev_hdl;
uint32_t actions;
action_t actions;
} usb_device_t;
typedef struct {
struct {
union {
struct {
uint8_t unhandled_devices: 1; /**< Device has unhandled devices */
uint8_t shutdown: 1; /**< */
uint8_t reserved6: 6; /**< Reserved */
};
uint8_t val; /**< Class drivers' flags value */
} flags; /**< Class drivers' flags */
usb_device_t device[DEV_MAX_COUNT]; /**< Class drivers' static array of devices */
} mux_protected; /**< Mutex protected members. Must be protected by the Class mux_lock when accessed */
struct {
usb_host_client_handle_t client_hdl;
SemaphoreHandle_t mux_lock; /**< Mutex for protected members */
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} class_driver_t;
static const char *TAG = "CLASS";
static class_driver_t *s_driver_obj;
static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg)
{
class_driver_t *driver_obj = (class_driver_t *)arg;
switch (event_msg->event) {
case USB_HOST_CLIENT_EVENT_NEW_DEV:
if (driver_obj->dev_addr == 0) {
driver_obj->dev_addr = event_msg->new_dev.address;
//Open the device next
driver_obj->actions |= ACTION_OPEN_DEV;
}
// Save the device address
xSemaphoreTake(driver_obj->constant.mux_lock, portMAX_DELAY);
driver_obj->mux_protected.device[event_msg->new_dev.address].dev_addr = event_msg->new_dev.address;
driver_obj->mux_protected.device[event_msg->new_dev.address].dev_hdl = NULL;
// Open the device next
driver_obj->mux_protected.device[event_msg->new_dev.address].actions |= ACTION_OPEN_DEV;
// Set flag
driver_obj->mux_protected.flags.unhandled_devices = 1;
xSemaphoreGive(driver_obj->constant.mux_lock);
break;
case USB_HOST_CLIENT_EVENT_DEV_GONE:
if (driver_obj->dev_hdl != NULL) {
//Cancel any other actions and close the device next
driver_obj->actions = ACTION_CLOSE_DEV;
// Cancel any other actions and close the device next
xSemaphoreTake(driver_obj->constant.mux_lock, portMAX_DELAY);
for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) {
if (driver_obj->mux_protected.device[i].dev_hdl == event_msg->dev_gone.dev_hdl) {
driver_obj->mux_protected.device[i].actions = ACTION_CLOSE_DEV;
// Set flag
driver_obj->mux_protected.flags.unhandled_devices = 1;
}
}
xSemaphoreGive(driver_obj->constant.mux_lock);
break;
default:
//Should never occur
// Should never occur
abort();
}
}
static void action_open_dev(class_driver_t *driver_obj)
static void action_open_dev(usb_device_t *device_obj)
{
assert(driver_obj->dev_addr != 0);
ESP_LOGI(TAG, "Opening device at address %d", driver_obj->dev_addr);
ESP_ERROR_CHECK(usb_host_device_open(driver_obj->client_hdl, driver_obj->dev_addr, &driver_obj->dev_hdl));
//Get the device's information next
driver_obj->actions &= ~ACTION_OPEN_DEV;
driver_obj->actions |= ACTION_GET_DEV_INFO;
assert(device_obj->dev_addr != 0);
ESP_LOGI(TAG, "Opening device at address %d", device_obj->dev_addr);
ESP_ERROR_CHECK(usb_host_device_open(device_obj->client_hdl, device_obj->dev_addr, &device_obj->dev_hdl));
// Get the device's information next
device_obj->actions |= ACTION_GET_DEV_INFO;
}
static void action_get_info(class_driver_t *driver_obj)
static void action_get_info(usb_device_t *device_obj)
{
assert(driver_obj->dev_hdl != NULL);
assert(device_obj->dev_hdl != NULL);
ESP_LOGI(TAG, "Getting device information");
usb_device_info_t dev_info;
ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info));
ESP_LOGI(TAG, "\t%s speed", (dev_info.speed == USB_SPEED_LOW) ? "Low" : "Full");
ESP_ERROR_CHECK(usb_host_device_info(device_obj->dev_hdl, &dev_info));
ESP_LOGI(TAG, "\t%s speed", (char *[]) {
"Low", "Full", "High"
}[dev_info.speed]);
ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue);
//Get the device descriptor next
driver_obj->actions &= ~ACTION_GET_DEV_INFO;
driver_obj->actions |= ACTION_GET_DEV_DESC;
ESP_LOGI(TAG, "\tParent info:");
if (dev_info.parent.dev_hdl) {
usb_device_info_t parent_dev_info;
ESP_ERROR_CHECK(usb_host_device_info(dev_info.parent.dev_hdl, &parent_dev_info));
ESP_LOGI(TAG, "\t\tBus addr: %d", parent_dev_info.dev_addr);
ESP_LOGI(TAG, "\t\tPort: %d", dev_info.parent.port_num);
} else {
ESP_LOGI(TAG, "\t\tPort: ROOT");
}
ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue);
// Get the device descriptor next
device_obj->actions |= ACTION_GET_DEV_DESC;
}
static void action_get_dev_desc(class_driver_t *driver_obj)
static void action_get_dev_desc(usb_device_t *device_obj)
{
assert(driver_obj->dev_hdl != NULL);
assert(device_obj->dev_hdl != NULL);
ESP_LOGI(TAG, "Getting device descriptor");
const usb_device_desc_t *dev_desc;
ESP_ERROR_CHECK(usb_host_get_device_descriptor(driver_obj->dev_hdl, &dev_desc));
ESP_ERROR_CHECK(usb_host_get_device_descriptor(device_obj->dev_hdl, &dev_desc));
usb_print_device_descriptor(dev_desc);
//Get the device's config descriptor next
driver_obj->actions &= ~ACTION_GET_DEV_DESC;
driver_obj->actions |= ACTION_GET_CONFIG_DESC;
// Get the device's config descriptor next
device_obj->actions |= ACTION_GET_CONFIG_DESC;
}
static void action_get_config_desc(class_driver_t *driver_obj)
static void action_get_config_desc(usb_device_t *device_obj)
{
assert(driver_obj->dev_hdl != NULL);
assert(device_obj->dev_hdl != NULL);
ESP_LOGI(TAG, "Getting config descriptor");
const usb_config_desc_t *config_desc;
ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(driver_obj->dev_hdl, &config_desc));
ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(device_obj->dev_hdl, &config_desc));
usb_print_config_descriptor(config_desc, NULL);
//Get the device's string descriptors next
driver_obj->actions &= ~ACTION_GET_CONFIG_DESC;
driver_obj->actions |= ACTION_GET_STR_DESC;
// Get the device's string descriptors next
device_obj->actions |= ACTION_GET_STR_DESC;
}
static void action_get_str_desc(class_driver_t *driver_obj)
static void action_get_str_desc(usb_device_t *device_obj)
{
assert(driver_obj->dev_hdl != NULL);
assert(device_obj->dev_hdl != NULL);
usb_device_info_t dev_info;
ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info));
ESP_ERROR_CHECK(usb_host_device_info(device_obj->dev_hdl, &dev_info));
if (dev_info.str_desc_manufacturer) {
ESP_LOGI(TAG, "Getting Manufacturer string descriptor");
usb_print_string_descriptor(dev_info.str_desc_manufacturer);
@ -117,29 +157,59 @@ static void action_get_str_desc(class_driver_t *driver_obj)
ESP_LOGI(TAG, "Getting Serial Number string descriptor");
usb_print_string_descriptor(dev_info.str_desc_serial_num);
}
//Nothing to do until the device disconnects
driver_obj->actions &= ~ACTION_GET_STR_DESC;
}
static void aciton_close_dev(class_driver_t *driver_obj)
static void action_close_dev(usb_device_t *device_obj)
{
ESP_ERROR_CHECK(usb_host_device_close(driver_obj->client_hdl, driver_obj->dev_hdl));
driver_obj->dev_hdl = NULL;
driver_obj->dev_addr = 0;
//We need to exit the event handler loop
driver_obj->actions &= ~ACTION_CLOSE_DEV;
driver_obj->actions |= ACTION_EXIT;
ESP_ERROR_CHECK(usb_host_device_close(device_obj->client_hdl, device_obj->dev_hdl));
device_obj->dev_hdl = NULL;
device_obj->dev_addr = 0;
}
static void class_driver_device_handle(usb_device_t *device_obj)
{
uint8_t actions = device_obj->actions;
device_obj->actions = 0;
while (actions) {
if (actions & ACTION_OPEN_DEV) {
action_open_dev(device_obj);
}
if (actions & ACTION_GET_DEV_INFO) {
action_get_info(device_obj);
}
if (actions & ACTION_GET_DEV_DESC) {
action_get_dev_desc(device_obj);
}
if (actions & ACTION_GET_CONFIG_DESC) {
action_get_config_desc(device_obj);
}
if (actions & ACTION_GET_STR_DESC) {
action_get_str_desc(device_obj);
}
if (actions & ACTION_CLOSE_DEV) {
action_close_dev(device_obj);
}
actions = device_obj->actions;
device_obj->actions = 0;
}
}
void class_driver_task(void *arg)
{
SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg;
class_driver_t driver_obj = {0};
//Wait until daemon task has installed USB Host Library
xSemaphoreTake(signaling_sem, portMAX_DELAY);
usb_host_client_handle_t class_driver_client_hdl = NULL;
ESP_LOGI(TAG, "Registering Client");
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex();
if (mux_lock == NULL) {
ESP_LOGE(TAG, "Unable to create class driver mutex");
vTaskSuspend(NULL);
return;
}
usb_host_client_config_t client_config = {
.is_synchronous = false, //Synchronous clients currently not supported. Set this to false
.max_num_event_msg = CLIENT_NUM_EVENT_MSG,
@ -148,40 +218,62 @@ void class_driver_task(void *arg)
.callback_arg = (void *) &driver_obj,
},
};
ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl));
ESP_ERROR_CHECK(usb_host_client_register(&client_config, &class_driver_client_hdl));
driver_obj.constant.mux_lock = mux_lock;
driver_obj.constant.client_hdl = class_driver_client_hdl;
for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) {
driver_obj.mux_protected.device[i].client_hdl = class_driver_client_hdl;
}
s_driver_obj = &driver_obj;
while (1) {
if (driver_obj.actions == 0) {
usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY);
// Driver has unhandled devices, handle all devices first
if (driver_obj.mux_protected.flags.unhandled_devices) {
xSemaphoreTake(driver_obj.constant.mux_lock, portMAX_DELAY);
for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) {
if (driver_obj.mux_protected.device[i].actions) {
class_driver_device_handle(&driver_obj.mux_protected.device[i]);
}
}
driver_obj.mux_protected.flags.unhandled_devices = 0;
xSemaphoreGive(driver_obj.constant.mux_lock);
} else {
if (driver_obj.actions & ACTION_OPEN_DEV) {
action_open_dev(&driver_obj);
}
if (driver_obj.actions & ACTION_GET_DEV_INFO) {
action_get_info(&driver_obj);
}
if (driver_obj.actions & ACTION_GET_DEV_DESC) {
action_get_dev_desc(&driver_obj);
}
if (driver_obj.actions & ACTION_GET_CONFIG_DESC) {
action_get_config_desc(&driver_obj);
}
if (driver_obj.actions & ACTION_GET_STR_DESC) {
action_get_str_desc(&driver_obj);
}
if (driver_obj.actions & ACTION_CLOSE_DEV) {
aciton_close_dev(&driver_obj);
}
if (driver_obj.actions & ACTION_EXIT) {
// Driver is active, handle client events
if (driver_obj.mux_protected.flags.shutdown == 0) {
usb_host_client_handle_events(class_driver_client_hdl, portMAX_DELAY);
} else {
// Shutdown the driver
break;
}
}
}
ESP_LOGI(TAG, "Deregistering Client");
ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl));
//Wait to be deleted
xSemaphoreGive(signaling_sem);
ESP_LOGI(TAG, "Deregistering Class Client");
ESP_ERROR_CHECK(usb_host_client_deregister(class_driver_client_hdl));
if (mux_lock != NULL) {
vSemaphoreDelete(mux_lock);
}
vTaskSuspend(NULL);
}
void class_driver_client_deregister(void)
{
// Mark all opened devices
xSemaphoreTake(s_driver_obj->constant.mux_lock, portMAX_DELAY);
for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) {
if (s_driver_obj->mux_protected.device[i].dev_hdl != NULL) {
// Mark device to close
s_driver_obj->mux_protected.device[i].actions |= ACTION_CLOSE_DEV;
// Set flag
s_driver_obj->mux_protected.flags.unhandled_devices = 1;
}
}
s_driver_obj->mux_protected.flags.shutdown = 1;
xSemaphoreGive(s_driver_obj->constant.mux_lock);
// Unblock, exit the loop and proceed to deregister client
ESP_ERROR_CHECK(usb_host_client_unblock(s_driver_obj->constant.client_hdl));
}

View File

@ -6,21 +6,69 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_intr_alloc.h"
#include "usb/usb_host.h"
#include "driver/gpio.h"
#define DAEMON_TASK_PRIORITY 2
#define HOST_LIB_TASK_PRIORITY 2
#define CLASS_TASK_PRIORITY 3
#define APP_QUIT_PIN CONFIG_APP_QUIT_PIN
#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define ENABLE_ENUM_FILTER_CALLBACK
#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
extern void class_driver_task(void *arg);
extern void class_driver_client_deregister(void);
static const char *TAG = "DAEMON";
static const char *TAG = "USB host lib";
QueueHandle_t app_event_queue = NULL;
/**
* @brief APP event group
*
* APP_EVENT - General event, which is APP_QUIT_PIN press event in this example.
*/
typedef enum {
APP_EVENT = 0,
} app_event_group_t;
/**
* @brief APP event queue
*
* This event is used for delivering events from callback to a task.
*/
typedef struct {
app_event_group_t event_group;
} app_event_queue_t;
/**
* @brief BOOT button pressed callback
*
* Signal application to exit the Host lib task
*
* @param[in] arg Unused
*/
static void gpio_cb(void *arg)
{
const app_event_queue_t evt_queue = {
.event_group = APP_EVENT,
};
BaseType_t xTaskWoken = pdFALSE;
if (app_event_queue) {
xQueueSendFromISR(app_event_queue, &evt_queue, &xTaskWoken);
}
if (xTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
/**
* @brief Set configuration callback
@ -51,10 +99,13 @@ static bool set_config_cb(const usb_device_desc_t *dev_desc, uint8_t *bConfigura
}
#endif // ENABLE_ENUM_FILTER_CALLBACK
static void host_lib_daemon_task(void *arg)
/**
* @brief Start USB Host install and handle common USB host library events while app pin not low
*
* @param[in] arg Not used
*/
static void usb_host_lib_task(void *arg)
{
SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg;
ESP_LOGI(TAG, "Installing USB Host Library");
usb_host_config_t host_config = {
.skip_phy_setup = false,
@ -65,62 +116,109 @@ static void host_lib_daemon_task(void *arg)
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
//Signal to the class driver task that the host library is installed
xSemaphoreGive(signaling_sem);
vTaskDelay(10); //Short delay to let client task spin up
//Signalize the app_main, the USB host library has been installed
xTaskNotifyGive(arg);
bool has_clients = true;
bool has_devices = true;
while (has_clients || has_devices) {
bool has_devices = false;
while (has_clients) {
uint32_t event_flags;
ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags));
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
ESP_LOGI(TAG, "Get FLAGS_NO_CLIENTS");
if (ESP_OK == usb_host_device_free_all()) {
ESP_LOGI(TAG, "All devices marked as free, no need to wait FLAGS_ALL_FREE event");
has_clients = false;
} else {
ESP_LOGI(TAG, "Wait for the FLAGS_ALL_FREE");
has_devices = true;
}
}
if (has_devices && event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
ESP_LOGI(TAG, "Get FLAGS_ALL_FREE");
has_clients = false;
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
has_devices = false;
}
}
ESP_LOGI(TAG, "No more clients and devices");
ESP_LOGI(TAG, "No more clients and devices, uninstall USB Host library");
//Uninstall the USB Host Library
ESP_ERROR_CHECK(usb_host_uninstall());
//Wait to be deleted
xSemaphoreGive(signaling_sem);
vTaskSuspend(NULL);
}
void app_main(void)
{
SemaphoreHandle_t signaling_sem = xSemaphoreCreateBinary();
ESP_LOGI(TAG, "USB host library example");
TaskHandle_t daemon_task_hdl;
TaskHandle_t class_driver_task_hdl;
//Create daemon task
xTaskCreatePinnedToCore(host_lib_daemon_task,
"daemon",
4096,
(void *)signaling_sem,
DAEMON_TASK_PRIORITY,
&daemon_task_hdl,
0);
//Create the class driver task
xTaskCreatePinnedToCore(class_driver_task,
"class",
4096,
(void *)signaling_sem,
CLASS_TASK_PRIORITY,
&class_driver_task_hdl,
0);
// Init BOOT button: Pressing the button simulates app request to exit
// It will uninstall the class driver and USB Host Lib
const gpio_config_t input_pin = {
.pin_bit_mask = BIT64(APP_QUIT_PIN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.intr_type = GPIO_INTR_NEGEDGE,
};
ESP_ERROR_CHECK(gpio_config(&input_pin));
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1));
ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_cb, NULL));
vTaskDelay(10); //Add a short delay to let the tasks run
app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t));
app_event_queue_t evt_queue;
//Wait for the tasks to complete
for (int i = 0; i < 2; i++) {
xSemaphoreTake(signaling_sem, portMAX_DELAY);
TaskHandle_t host_lib_task_hdl, class_driver_task_hdl;
// Create usb host lib task
BaseType_t task_created;
task_created = xTaskCreatePinnedToCore(usb_host_lib_task,
"usb_host",
4096,
xTaskGetCurrentTaskHandle(),
HOST_LIB_TASK_PRIORITY,
&host_lib_task_hdl,
0);
assert(task_created == pdTRUE);
// Wait unit the USB host library is installed
ulTaskNotifyTake(false, 1000);
// Create class driver task
task_created = xTaskCreatePinnedToCore(class_driver_task,
"class",
5 * 1024,
NULL,
CLASS_TASK_PRIORITY,
&class_driver_task_hdl,
0);
assert(task_created == pdTRUE);
// Add a short delay to let the tasks run
vTaskDelay(10);
while (1) {
if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY)) {
if (APP_EVENT == evt_queue.event_group) {
// User pressed button
usb_host_lib_info_t lib_info;
ESP_ERROR_CHECK(usb_host_lib_info(&lib_info));
if (lib_info.num_devices != 0) {
ESP_LOGW(TAG, "Shutdown with attached devices.");
}
// End while cycle
break;
}
}
}
//Delete the tasks
// Deregister client
class_driver_client_deregister();
vTaskDelay(10);
// Delete the tasks
vTaskDelete(class_driver_task_hdl);
vTaskDelete(daemon_task_hdl);
vTaskDelete(host_lib_task_hdl);
// Delete interrupt and queue
gpio_isr_handler_remove(APP_QUIT_PIN);
xQueueReset(app_event_queue);
vQueueDelete(app_event_queue);
ESP_LOGI(TAG, "End of the example");
}

View File

@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_USB_HOST_HUBS_SUPPORTED=y