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

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

See merge request espressif/esp-idf!28548
This commit is contained in:
morris
2024-03-05 09:47:56 +08:00
10 changed files with 220 additions and 13 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 {
ESP_LOGE(HUB_DRIVER_TAG, "Stage failed: %s", enum_stage_strings[enum_ctrl->stage]);
#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

@@ -372,6 +372,62 @@ MSC
* A host class driver for the Mass Storage Class (Bulk-Only Transport) is deployed to `IDF component registry <https://components.espressif.com/component/espressif/usb_host_msc>`__. You can find its example in :example:`peripherals/usb/host/msc`.
.. ---------------------------------------------- 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

@@ -2,3 +2,5 @@ idf_component_register(
SRCS "cdc_acm_vcp_example_main.cpp"
INCLUDE_DIRS "."
)
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));