feat(us/host): set device cfg during enumeration

- user callback funciton to set device configuration
      as a part of usb_host_install
    - callback provides device descriptor of a device being enumerated
    - user can set which cfg descriptor the USB device will be set with
    - user can filter device enumeration
    - Kconfig menu to enable callback function
    - usb_host_lib example demonstration
This commit is contained in:
Peter Marcisovsky 2023-11-23 16:00:13 +01:00
parent e8c901b144
commit e87bb08216
8 changed files with 164 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) {

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));