Merge branch 'feat/usb_host_set_device_config_v5.2' into 'release/v5.2'

USB Host: Add enumeration callback filter (backport v5.2)

See merge request espressif/esp-idf!28550
This commit is contained in:
morris
2024-02-22 11:07:43 +08:00
11 changed files with 260 additions and 12 deletions

View File

@ -104,4 +104,15 @@ menu "USB-OTG"
bool
default y
config USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
bool "Enable enumeration filter callback"
default n
help
The enumeration filter callback is called before enumeration of each newly attached device. This callback
allows users to control whether a device should be enumerated, and what configuration number to use when
enumerating a device.
If enabled, the enumeration filter callback can be set via 'usb_host_config_t' when calling
'usb_host_install()'.
endmenu #USB-OTG

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -32,11 +32,15 @@ implement the bare minimum to control the root HCD port.
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED
#endif
#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define ENABLE_ENUM_FILTER_CALLBACK
#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define SET_ADDR_RECOVERY_INTERVAL_MS CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS
#define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
#define ENUM_DEV_ADDR 1 // Device address used in enumeration
#define ENUM_CONFIG_INDEX 0 // Index used to get the first configuration descriptor of the device
#define ENUM_CONFIG_INDEX_DEFAULT 0 // Index used to get the first configuration descriptor of the device
#define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength)
#define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device
#define ENUM_WORST_CASE_MPS_FS 64 // The worst case MPS of EP0 for a FS device
@ -165,6 +169,11 @@ typedef struct {
uint8_t iSerialNumber; /**< Index of the Serial Number string descriptor */
uint8_t str_desc_bLength; /**< Saved bLength from getting a short string descriptor */
uint8_t bConfigurationValue; /**< Device's current configuration number */
uint8_t enum_config_index; /**< Configuration index used during enumeration */
#ifdef ENABLE_ENUM_FILTER_CALLBACK
usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */
bool graceful_exit; /**< Exit enumeration by user's request from the callback function */
#endif // ENABLE_ENUM_FILTER_CALLBACK
} enum_ctrl_t;
typedef struct {
@ -280,6 +289,11 @@ static bool enum_stage_start(enum_ctrl_t *enum_ctrl)
ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL));
enum_ctrl->dev_hdl = enum_dev_hdl;
enum_ctrl->pipe = enum_dflt_pipe_hdl;
// Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb
#ifdef ENABLE_ENUM_FILTER_CALLBACK
enum_ctrl->graceful_exit = false;
#endif // ENABLE_ENUM_FILTER_CALLBACK
return true;
}
@ -323,6 +337,39 @@ static void get_string_desc_index_and_langid(enum_ctrl_t *enum_ctrl, uint8_t *in
}
}
static bool set_config_index(enum_ctrl_t *enum_ctrl, const usb_device_desc_t *device_desc)
{
#ifdef ENABLE_ENUM_FILTER_CALLBACK
// Callback enabled in the menuncofig, but the callback function was not defined
if (enum_ctrl->enum_filter_cb == NULL) {
enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT;
return true;
}
uint8_t enum_config_index;
const bool enum_continue = enum_ctrl->enum_filter_cb(device_desc, &enum_config_index);
// User's request NOT to enumerate the USB device
if (!enum_continue) {
ESP_LOGW(HUB_DRIVER_TAG, "USB device (PID = 0x%x, VID = 0x%x) will not be enumerated", device_desc->idProduct, device_desc->idVendor);
enum_ctrl->graceful_exit = true;
return false;
}
// Set configuration descriptor
if ((enum_config_index == 0) || (enum_config_index > device_desc->bNumConfigurations)) {
ESP_LOGW(HUB_DRIVER_TAG, "bConfigurationValue %d provided by user, device will be configured with configuration descriptor 1", enum_config_index);
enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT;
} else {
enum_ctrl->enum_config_index = enum_config_index - 1;
}
#else // ENABLE_ENUM_FILTER_CALLBACK
enum_ctrl->enum_config_index = ENUM_CONFIG_INDEX_DEFAULT;
#endif // ENABLE_ENUM_FILTER_CALLBACK
return true;
}
static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl)
{
usb_transfer_t *transfer = &enum_ctrl->urb->transfer;
@ -351,7 +398,7 @@ static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl)
}
case ENUM_STAGE_GET_SHORT_CONFIG_DESC: {
// Get a short config descriptor at index 0
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, ENUM_CONFIG_INDEX, ENUM_SHORT_DESC_REQ_LEN);
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, enum_ctrl->enum_config_index, ENUM_SHORT_DESC_REQ_LEN);
transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(ENUM_SHORT_DESC_REQ_LEN, enum_ctrl->bMaxPacketSize0);
// IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes
enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + ENUM_SHORT_DESC_REQ_LEN;
@ -359,7 +406,7 @@ static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl)
}
case ENUM_STAGE_GET_FULL_CONFIG_DESC: {
// Get the full configuration descriptor at index 0, requesting its exact length.
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, ENUM_CONFIG_INDEX, enum_ctrl->wTotalLength);
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, enum_ctrl->enum_config_index, enum_ctrl->wTotalLength);
transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(enum_ctrl->wTotalLength, enum_ctrl->bMaxPacketSize0);
// IN data stage should return exactly wTotalLength bytes
enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + enum_ctrl->wTotalLength;
@ -497,7 +544,7 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
enum_ctrl->iManufacturer = device_desc->iManufacturer;
enum_ctrl->iProduct = device_desc->iProduct;
enum_ctrl->iSerialNumber = device_desc->iSerialNumber;
ret = true;
ret = set_config_index(enum_ctrl, device_desc);
break;
}
case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: {
@ -924,7 +971,15 @@ static void enum_handle_events(void)
if (stage_pass) {
ESP_LOGD(HUB_DRIVER_TAG, "Stage done: %s", enum_stage_strings[enum_ctrl->stage]);
} else {
#ifdef ENABLE_ENUM_FILTER_CALLBACK
if (!enum_ctrl->graceful_exit) {
ESP_LOGE(HUB_DRIVER_TAG, "Stage failed: %s", enum_stage_strings[enum_ctrl->stage]);
} else {
ESP_LOGD(HUB_DRIVER_TAG, "Stage done: %s", enum_stage_strings[enum_ctrl->stage]);
}
#else // ENABLE_ENUM_FILTER_CALLBACK
ESP_LOGE(HUB_DRIVER_TAG, "Stage failed: %s", enum_stage_strings[enum_ctrl->stage]);
#endif // ENABLE_ENUM_FILTER_CALLBACK
}
enum_set_next_stage(enum_ctrl, stage_pass);
}
@ -959,9 +1014,13 @@ esp_err_t hub_install(hub_config_t *hub_config)
hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED;
hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE;
hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb;
#ifdef ENABLE_ENUM_FILTER_CALLBACK
hub_driver_obj->single_thread.enum_ctrl.enum_filter_cb = hub_config->enum_filter_cb;
#endif // ENABLE_ENUM_FILTER_CALLBACK
hub_driver_obj->constant.root_port_hdl = 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_ENTER_CRITICAL();
if (p_hub_driver_obj != NULL) {
HUB_DRIVER_EXIT_CRITICAL();

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -106,6 +106,8 @@ typedef struct {
set this if they want to use an external USB PHY. Otherwise, the USB Host Library
will automatically configure the internal USB PHY */
int intr_flags; /**< Interrupt flags for the underlying ISR used by the USB Host stack */
usb_host_enum_filter_cb_t enum_filter_cb; /**< Enumeration filter callback. Enable CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
to use this feature. Set to NULL otherwise. */
} usb_host_config_t;
/**

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -10,6 +10,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to
#pragma once
#include <stdbool.h>
#include "usb/usb_types_ch9.h"
#ifdef __cplusplus
@ -46,6 +47,29 @@ typedef enum {
*/
typedef struct usb_device_handle_s *usb_device_handle_t;
/**
* @brief Enumeration filter callback
*
* This callback is called at the beginning of the enumeration process for a newly attached device.
* Through this callback, users are able to:
*
* - filter which devices should be enumerated
* - select the configuration number to use when enumerating the device
*
* The device descriptor is passed to this callback to allow users to filter devices based on
* Vendor ID, Product ID, and class code.
*
* @attention This callback must be non-blocking
* @attention This callback must not submit any USB transfers
* @param[in] dev_desc Device descriptor of the device to enumerate
* @param[out] bConfigurationValue Configuration number to use when enumerating the device (starts with 1)
*
* @return bool
* - true: USB device will be enumerated
* - false: USB device will not be enumerated
*/
typedef bool (*usb_host_enum_filter_cb_t)(const usb_device_desc_t *dev_desc, uint8_t *bConfigurationValue);
/**
* @brief Basic information of an enumerated device
*/

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -8,6 +8,7 @@
#include <stdlib.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "usb_private.h"
#include "usbh.h"
@ -22,8 +23,11 @@ extern "C" {
* @brief Hub driver configuration
*/
typedef struct {
usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */
void *proc_req_cb_arg; /**< Processing request callback argument */
usb_proc_req_cb_t proc_req_cb; /**< Processing request callback */
void *proc_req_cb_arg; /**< Processing request callback argument */
#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
usb_host_enum_filter_cb_t enum_filter_cb; /**< Set device configuration callback */
#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
} hub_config_t;
// ---------------------------------------------- Hub Driver Functions -------------------------------------------------

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -10,6 +10,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to
#include <stdlib.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
@ -47,6 +48,10 @@ static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED;
#define PROCESS_REQUEST_PENDING_FLAG_USBH 0x01
#define PROCESS_REQUEST_PENDING_FLAG_HUB 0x02
#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
#define ENABLE_ENUM_FILTER_CALLBACK
#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK
typedef struct ep_wrapper_s ep_wrapper_t;
typedef struct interface_s interface_t;
typedef struct client_s client_t;
@ -404,10 +409,18 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
goto usbh_err;
}
#ifdef ENABLE_ENUM_FILTER_CALLBACK
if (config->enum_filter_cb == NULL) {
ESP_LOGW(USB_HOST_TAG, "User callback to set USB device configuration is enabled, but not used");
}
#endif // ENABLE_ENUM_FILTER_CALLBACK
// Install Hub
hub_config_t hub_config = {
.proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL,
#ifdef ENABLE_ENUM_FILTER_CALLBACK
.enum_filter_cb = config->enum_filter_cb,
#endif // ENABLE_ENUM_FILTER_CALLBACK
};
ret = hub_install(&hub_config);
if (ret != ESP_OK) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -390,6 +390,62 @@ UVC
* A host class driver for the USB Video Device Class is distributed as a managed component via the `ESP-IDF Component Registry <https://components.espressif.com/component/espressif/usb_host_uvc>`__.
* The :example:`peripherals/usb/host/uvc` example demonstrates the usage of the UVC host driver to receive a video stream from a USB camera and optionally forward that stream over Wi-Fi.
.. ---------------------------------------------- USB Host Menuconfig --------------------------------------------------
Host Stack Configuration
------------------------
Non-Compliant Device Support
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To support USB devices that are non-compliant in various scenarios or exhibit specific behaviors, it is possible to configure the USB Host stack.
As a USB device may be hot-plugged, it is essential to have the configurable delays between power switching and device attachment, and when the device's internal power has stabilized.
Enumeration Configuration
"""""""""""""""""""""""""
During the process of enumerating connected USB devices, several timeout values ensure the proper functioning of the device.
.. figure:: ../../../_static/usb_host/poweron-timings.png
:align: center
:alt: USB Root Hub Power-on and Connection Events Timing
:figclass: align-center
USB Root Hub Power-on and Connection Events Timing
The figure above shows all the timeouts associated with both turning on port power with a device connected and hot-plugging a device.
* After a port is reset or resumed, the USB system software is expected to provide a "recovery" interval of 10 ms before the device attached to the port is expected to respond to data transfers.
* After the reset/resume recovery interval, if a device receives a ``SetAddress()`` request, the device must be able to complete processing of the request and be able to successfully complete the Status stage of the request within 50 ms.
* After successful completion of the Status stage, the device is allowed a ``SetAddress()`` recovery interval of 2 ms.
.. note::
For more details regarding connection event timings, please refer to *Universal Serial Bus 2.0 Specification* > Chapter 7.1.7.3 *Connect and Disconnect Signaling*.
Configurable parameters of the USB host stack can be configured with multiple options via Menuconfig.
* For debounce delay, refer to :ref:`CONFIG_USB_HOST_DEBOUNCE_DELAY_MS`.
* For reset hold interval, refer to :ref:`CONFIG_USB_HOST_RESET_HOLD_MS`.
* For reset recovery interval, refer to :ref:`CONFIG_USB_HOST_RESET_RECOVERY_MS`.
* For ``SetAddress()`` recovery interval, refer to :ref:`CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS`.
Multiple Configuration Support
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To support USB devices that have more than one configuration, it is possible to specify the desired configuration number during a device's enumeration process.
Enumeration Filter
""""""""""""""""""
The enumeration filter is a callback function of type :cpp:type:`usb_host_enum_filter_cb_t` called at the beginning of the enumeration process once a device descriptor is read from a newly attached USB device. Consequently, the user is provided with the obtained device descriptor. Through this callback, the user can:
* Select the configuration of the USB device.
* Filter which USB devices should be enumerated.
To use the enumeration filter, users should enable the :ref:`CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK` option using menuconfig. Users can specify the callback by setting :cpp:member:`usb_host_config_t::enum_filter_cb` which is then passed to the Host Library when calling :cpp:func:`usb_host_install`.
.. -------------------------------------------------- API Reference ----------------------------------------------------
API Reference

View File

@ -390,6 +390,47 @@ UVC
* USB 视频设备 Class 的主机 Class 驱动程序作为托管组件通过 `ESP-IDF 组件注册器 <https://components.espressif.com/component/espressif/usb_host_uvc>`__ 分发。
* 示例 :example:`peripherals/usb/host/uvc` 展示了如何使用 UVC 主机驱动程序接收来自 USB 摄像头的视频流,并可选择将该流通过 Wi-Fi 转发。
.. ---------------------------------------------- USB Host Menuconfig --------------------------------------------------
主机栈配置
----------
非兼容设备支持
^^^^^^^^^^^^^^
为了支持在某些情况下非兼容或会表现出特定行为的 USB 设备,可以对 USB 主机栈进行配置。
USB 设备可能是热插拔的,因此必须配置电源开关和设备连接之间的延迟,以及设备内部电源稳定后的延迟。
枚举配置
""""""""
在枚举已连接 USB 设备的过程中,一些超时值可确保设备正常运行。
.. figure:: ../../../_static/usb_host/poweron-timings.png
:align: center
:alt: USB 根集线器上电和连接事件时序
:figclass: align-center
USB 根集线器上电和连接事件时序
上图展示了与连接设备时开启端口电源和热插拔设备相关的所有超时值。
* 端口复位或恢复运行后USB 系统软件应提供 10 毫秒的恢复时间,此后连接到端口的设备才会响应数据传输。
* 恢复时间结束后,如果设备收到 ``SetAddress()`` 请求,设备必须能够完成对该请求的处理,并能在 50 毫秒内成功完成请求的状态 (Status) 阶段。
* 状态阶段结束后,设备允许有 2 毫秒的 ``SetAddress()`` 恢复时间。
.. note::
有关连接事件时序的更多信息,请参阅 *通用串行总线 2.0 规范* > 第 7.1.7.3 章 *连接和断开信令*
可通过 Menuconfig 选项设置 USB 主机栈的可配置参数。
* :ref:`CONFIG_USB_HOST_DEBOUNCE_DELAY_MS` 用于配置防抖延迟。
* :ref:`CONFIG_USB_HOST_RESET_HOLD_MS` 用于配置重置保持时间。
* :ref:`CONFIG_USB_HOST_RESET_RECOVERY_MS` 用于配置重置恢复时间。
* :ref:`CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS` 用于配置 ``SetAddress()`` 恢复时间。
.. -------------------------------------------------- API Reference ----------------------------------------------------
API 参考

View File

@ -3,3 +3,5 @@ idf_component_register(
INCLUDE_DIRS "."
PRIV_REQUIRES usb
)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-missing-field-initializers")

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -14,10 +14,43 @@
#define DAEMON_TASK_PRIORITY 2
#define CLASS_TASK_PRIORITY 3
#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);
static const char *TAG = "DAEMON";
/**
* @brief Set configuration callback
*
* Set the USB device configuration during the enumeration process, must be enabled in the menuconfig
* @note bConfigurationValue starts at index 1
*
* @param[in] dev_desc device descriptor of the USB device currently being enumerated
* @param[out] bConfigurationValue configuration descriptor index, that will be user for enumeration
*
* @return bool
* - true: USB device will be enumerated
* - false: USB device will not be enumerated
*/
#ifdef ENABLE_ENUM_FILTER_CALLBACK
static bool set_config_cb(const usb_device_desc_t *dev_desc, uint8_t *bConfigurationValue)
{
// If the USB device has more than one configuration, set the second configuration
if (dev_desc->bNumConfigurations > 1) {
*bConfigurationValue = 2;
} else {
*bConfigurationValue = 1;
}
// Return true to enumerate the USB device
return true;
}
#endif // ENABLE_ENUM_FILTER_CALLBACK
static void host_lib_daemon_task(void *arg)
{
SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg;
@ -26,6 +59,9 @@ static void host_lib_daemon_task(void *arg)
usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
#ifdef ENABLE_ENUM_FILTER_CALLBACK
.enum_filter_cb = set_config_cb,
#endif // ENABLE_ENUM_FILTER_CALLBACK
};
ESP_ERROR_CHECK(usb_host_install(&host_config));