diff --git a/components/usb/Kconfig b/components/usb/Kconfig index 5e02abe0d82..1b9da3889a6 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -96,6 +96,17 @@ menu "USB-OTG" endmenu #Root Hub configuration + 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()'. + # Hidden or compatibility options config USB_OTG_SUPPORTED diff --git a/components/usb/hub.c b/components/usb/hub.c index 7f24a60a6d9..daf591a39a2 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 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(); diff --git a/components/usb/include/usb/usb_host.h b/components/usb/include/usb/usb_host.h index 89cc88cbc9e..ba44c584d17 100644 --- a/components/usb/include/usb/usb_host.h +++ b/components/usb/include/usb/usb_host.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 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; /** diff --git a/components/usb/include/usb/usb_types_stack.h b/components/usb/include/usb/usb_types_stack.h index 00af897182c..8b351063f4b 100644 --- a/components/usb/include/usb/usb_types_stack.h +++ b/components/usb/include/usb/usb_types_stack.h @@ -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 #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 */ diff --git a/components/usb/private_include/hub.h b/components/usb/private_include/hub.h index 1a8807bf51e..dac89b7ea73 100644 --- a/components/usb/private_include/hub.h +++ b/components/usb/private_include/hub.h @@ -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 #include +#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 ------------------------------------------------- diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index cfea392a432..8328927952c 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -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 #include +#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) { diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt index e52dfe2ba87..477ba42e45e 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt @@ -3,3 +3,5 @@ idf_component_register( INCLUDE_DIRS "." PRIV_REQUIRES usb ) + +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-missing-field-initializers") diff --git a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c index db41bd32657..e6c2a8df92d 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c @@ -17,6 +17,10 @@ #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); @@ -66,6 +70,35 @@ static void gpio_cb(void *arg) } } +/** + * @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 + /** * @brief Start USB Host install and handle common USB host library events while app pin not low * @@ -77,6 +110,9 @@ static void usb_host_lib_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));