2024-03-04 15:11:43 -05:00
/*
* SPDX - FileCopyrightText : 2023 - 2024 Espressif Systems ( Shanghai ) CO LTD
*
* SPDX - License - Identifier : Apache - 2.0
*/
# include <string.h>
# include <stdint.h>
# include "esp_err.h"
# include "esp_log.h"
# include "esp_heap_caps.h"
# include "freertos/FreeRTOS.h"
# include "freertos/task.h"
# include "usb_private.h"
# include "usbh.h"
# include "enum.h"
# include "usb/usb_helpers.h"
# 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_INIT_VALUE_DEV_ADDR 1 // Init value for device address
# define ENUM_DEFAULT_CONFIGURATION_VALUE 1 // Default configuration value for SetConfiguration() request
# 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_HS 64 // The worst case MPS of EP0 for a FS/HS device
# define ENUM_LANGID 0x409 // Current enumeration only supports English (United States) string descriptors
# define ENUM_MAX_ADDRESS (127) // Maximal device address value
/**
* @ brief Stages of device enumeration listed in their order of execution
*
2024-06-05 09:15:51 -04:00
* Entry :
2024-03-04 15:11:43 -05:00
* - These stages MUST BE LISTED IN THE ORDER OF THEIR EXECUTION as the enumeration will simply increment the current stage
* - If an error occurs at any stage , ENUM_STAGE_CANCEL acts as a common exit stage on failure
* - Must start with 0 as enum is also used as an index
* - The short descriptor stages are used to fetch the start particular descriptors that don ' t have a fixed length in order to determine the full descriptors length
2024-06-05 09:15:51 -04:00
* - Any state of Get String Descriptor could be STALLed by the device . In that case we just don ' t fetch them and treat enumeration as successful
2024-03-04 15:11:43 -05:00
*/
typedef enum {
ENUM_STAGE_IDLE = 0 , /**< There is no device awaiting enumeration */
2024-06-05 09:15:51 -04:00
// Basic Device enumeration
2024-03-04 15:11:43 -05:00
ENUM_STAGE_GET_SHORT_DEV_DESC , /**< Getting short dev desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */
ENUM_STAGE_CHECK_SHORT_DEV_DESC , /**< Save bMaxPacketSize0 from the short dev desc. Update the MPS of the enum pipe */
ENUM_STAGE_SECOND_RESET , /**< Reset the device again (Workaround for old USB devices that get confused by the previous short dev desc request). */
2024-07-18 04:00:07 -04:00
ENUM_STAGE_SECOND_RESET_COMPLETE , /**< Reset completed, re-trigger the FSM */
2024-03-04 15:11:43 -05:00
ENUM_STAGE_SET_ADDR , /**< Send SET_ADDRESS request */
ENUM_STAGE_CHECK_ADDR , /**< Update the enum pipe's target address */
ENUM_STAGE_SET_ADDR_RECOVERY , /**< Wait SET ADDRESS recovery interval at least for 2ms due to usb_20, chapter 9.2.6.3 */
ENUM_STAGE_GET_FULL_DEV_DESC , /**< Get the full dev desc */
ENUM_STAGE_CHECK_FULL_DEV_DESC , /**< Check the full dev desc, fill it into the device object in USBH. Save the string descriptor indexes*/
ENUM_STAGE_SELECT_CONFIG , /**< Select configuration: select default ENUM_DEFAULT_CONFIGURATION_VALUE value or use callback if ENABLE_ENUM_FILTER_CALLBACK enabled */
ENUM_STAGE_GET_SHORT_CONFIG_DESC , /**< Getting a short config desc (wLength is ENUM_SHORT_DESC_REQ_LEN) */
ENUM_STAGE_CHECK_SHORT_CONFIG_DESC , /**< Save wTotalLength of the short config desc */
ENUM_STAGE_GET_FULL_CONFIG_DESC , /**< Get the full config desc (wLength is the saved wTotalLength) */
ENUM_STAGE_CHECK_FULL_CONFIG_DESC , /**< Check the full config desc, fill it into the device object in USBH */
2024-06-05 09:15:51 -04:00
// Get String Descriptors
2024-03-04 15:11:43 -05:00
ENUM_STAGE_GET_SHORT_LANGID_TABLE , /**< Get the header of the LANGID table string descriptor */
ENUM_STAGE_CHECK_SHORT_LANGID_TABLE , /**< Save the bLength of the LANGID table string descriptor */
ENUM_STAGE_GET_FULL_LANGID_TABLE , /**< Get the full LANGID table string descriptor */
ENUM_STAGE_CHECK_FULL_LANGID_TABLE , /**< Check whether ENUM_LANGID is in the LANGID table */
ENUM_STAGE_GET_SHORT_MANU_STR_DESC , /**< Get the header of the iManufacturer string descriptor */
ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC , /**< Save the bLength of the iManufacturer string descriptor */
ENUM_STAGE_GET_FULL_MANU_STR_DESC , /**< Get the full iManufacturer string descriptor */
ENUM_STAGE_CHECK_FULL_MANU_STR_DESC , /**< Check and fill the full iManufacturer string descriptor */
ENUM_STAGE_GET_SHORT_PROD_STR_DESC , /**< Get the header of the string descriptor at index iProduct */
ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC , /**< Save the bLength of the iProduct string descriptor */
ENUM_STAGE_GET_FULL_PROD_STR_DESC , /**< Get the full iProduct string descriptor */
ENUM_STAGE_CHECK_FULL_PROD_STR_DESC , /**< Check and fill the full iProduct string descriptor */
ENUM_STAGE_GET_SHORT_SER_STR_DESC , /**< Get the header of the string descriptor at index iSerialNumber */
ENUM_STAGE_CHECK_SHORT_SER_STR_DESC , /**< Save the bLength of the iSerialNumber string descriptor */
ENUM_STAGE_GET_FULL_SER_STR_DESC , /**< Get the full iSerialNumber string descriptor */
ENUM_STAGE_CHECK_FULL_SER_STR_DESC , /**< Check and fill the full iSerialNumber string descriptor */
2024-06-05 09:15:51 -04:00
// Set Configuration
2024-03-04 15:11:43 -05:00
ENUM_STAGE_SET_CONFIG , /**< Send SET_CONFIGURATION request */
ENUM_STAGE_CHECK_CONFIG , /**< Check that SET_CONFIGURATION request was successful */
// Terminal stages
ENUM_STAGE_COMPLETE , /**< Successful enumeration complete. */
ENUM_STAGE_CANCEL , /**< Cancel enumeration. Free device resources */
} enum_stage_t ;
const char * const enum_stage_strings [ ] = {
2024-07-18 04:00:07 -04:00
" IDLE " ,
2024-03-04 15:11:43 -05:00
" GET_SHORT_DEV_DESC " ,
" CHECK_SHORT_DEV_DESC " ,
" SECOND_RESET " ,
2024-07-18 04:00:07 -04:00
" SECOND_RESET_COMPLETE " ,
2024-03-04 15:11:43 -05:00
" SET_ADDR " ,
" CHECK_ADDR " ,
" SET_ADDR_RECOVERY " ,
" GET_FULL_DEV_DESC " ,
" CHECK_FULL_DEV_DESC " ,
" SELECT_CONFIG " ,
" GET_SHORT_CONFIG_DESC " ,
" CHECK_SHORT_CONFIG_DESC " ,
" GET_FULL_CONFIG_DESC " ,
" CHECK_FULL_CONFIG_DESC " ,
" GET_SHORT_LANGID_TABLE " ,
" CHECK_SHORT_LANGID_TABLE " ,
" GET_FULL_LANGID_TABLE " ,
" CHECK_FULL_LANGID_TABLE " ,
" GET_SHORT_MANU_STR_DESC " ,
" CHECK_SHORT_MANU_STR_DESC " ,
" GET_FULL_MANU_STR_DESC " ,
" CHECK_FULL_MANU_STR_DESC " ,
" GET_SHORT_PROD_STR_DESC " ,
" CHECK_SHORT_PROD_STR_DESC " ,
" GET_FULL_PROD_STR_DESC " ,
" CHECK_FULL_PROD_STR_DESC " ,
" GET_SHORT_SER_STR_DESC " ,
" CHECK_SHORT_SER_STR_DESC " ,
" GET_FULL_SER_STR_DESC " ,
" CHECK_FULL_SER_STR_DESC " ,
" SET_CONFIG " ,
" CHECK_CONFIG " ,
" COMPLETE " ,
" CANCEL " ,
} ;
typedef struct {
// Constant
uint8_t new_dev_addr ; /**< Device address that should be assigned during enumeration */
uint8_t bMaxPacketSize0 ; /**< Max packet size of the device's EP0. Read from bMaxPacketSize0 field of device descriptor */
uint16_t wTotalLength ; /**< Total length of device's configuration descriptor. Read from wTotalLength field of config descriptor */
uint8_t iManufacturer ; /**< Index of the Manufacturer string descriptor */
uint8_t iProduct ; /**< Index of the Product string descriptor */
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 */
} enum_device_params_t ;
typedef struct {
struct {
// Device related objects, initialized at start of a particular enumeration
unsigned int dev_uid ; /**< Unique device ID being enumerated */
usb_device_handle_t dev_hdl ; /**< Handle of device being enumerated */
// Parent info for optimization and more clean debug output
usb_device_handle_t parent_dev_hdl ; /**< Device's parent handle */
uint8_t parent_dev_addr ; /**< Device's parent address */
uint8_t parent_port_num ; /**< Device's parent port number */
// Parameters, updated during enumeration
2024-07-18 04:00:07 -04:00
enum_stage_t stage ; /**< Current enumeration stage */
2024-03-04 15:11:43 -05:00
enum_device_params_t dev_params ; /**< Parameters of device under enumeration */
int expect_num_bytes ; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */
2024-07-18 04:00:07 -04:00
uint8_t next_dev_addr ; /**< Device address for device under enumeration */
2024-03-04 15:11:43 -05:00
} single_thread ; /**< Single thread members don't require a critical section so long as they are never accessed from multiple threads */
struct {
// Internal objects
urb_t * urb ; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */
// Callbacks
usb_proc_req_cb_t proc_req_cb ; /**< USB Host process request callback. Refer to proc_req_callback() in usb_host.c */
void * proc_req_cb_arg ; /**< USB Host process request callback argument */
enum_event_cb_t enum_event_cb ; /**< Enumeration driver event callback */
void * enum_event_cb_arg ; /**< Enumeration driver event callback argument */
# if ENABLE_ENUM_FILTER_CALLBACK
usb_host_enum_filter_cb_t enum_filter_cb ; /**< Set device configuration callback */
void * enum_filter_cb_arg ; /**< Set device configuration callback argument */
# endif // ENABLE_ENUM_FILTER_CALLBACK
} constant ; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
} enum_driver_t ;
static enum_driver_t * p_enum_driver = NULL ;
const char * ENUM_TAG = " ENUM " ;
// -----------------------------------------------------------------------------
// ---------------------------- Helpers ----------------------------------------
// -----------------------------------------------------------------------------
# define ENUM_CHECK(cond, ret_val) ({ \
if ( ! ( cond ) ) { \
return ( ret_val ) ; \
} \
} )
// -----------------------------------------------------------------------------
// ------------------------ Private functions ----------------------------------
// -----------------------------------------------------------------------------
static inline uint8_t get_next_dev_addr ( void )
{
uint8_t ret = 0 ;
2024-07-18 04:00:07 -04:00
p_enum_driver - > single_thread . next_dev_addr + + ;
if ( p_enum_driver - > single_thread . next_dev_addr > ENUM_MAX_ADDRESS ) {
p_enum_driver - > single_thread . next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR ;
2024-03-04 15:11:43 -05:00
}
2024-07-18 04:00:07 -04:00
ret = p_enum_driver - > single_thread . next_dev_addr ;
2024-03-04 15:11:43 -05:00
return ret ;
}
static uint8_t get_next_free_dev_addr ( void )
{
usb_device_handle_t dev_hdl ;
2024-07-18 04:00:07 -04:00
uint8_t new_dev_addr = p_enum_driver - > single_thread . next_dev_addr ;
2024-03-04 15:11:43 -05:00
while ( 1 ) {
if ( usbh_devs_open ( new_dev_addr , & dev_hdl ) = = ESP_ERR_NOT_FOUND ) {
break ;
}
// We have a device with the same address on a bus, close device and request new addr
usbh_dev_close ( dev_hdl ) ;
new_dev_addr = get_next_dev_addr ( ) ;
}
// Sanity check
assert ( new_dev_addr ! = 0 ) ;
return new_dev_addr ;
}
/**
* @ brief Get Configuration descriptor index
*
* For Configuration descriptor bConfigurationValue and index are not the same and
* should be different for SetConfiguration ( ) and GetDescriptor ( ) requests .
* GetDescriptor ( ) : index from 0 to one less than the bNumConfigurations ( refer to section 9.4 .3 Get Descriptor )
* SetConfiguration ( ) : bConfigurationValue field used as a parameter to the SetConfiguration ( ) request . ( refer to section 9.6 .3 Configuration )
*
* @ return uint8_t
*/
static inline uint8_t get_configuration_descriptor_index ( uint8_t bConfigurationValue )
{
return ( bConfigurationValue = = 0 ) ? bConfigurationValue : ( bConfigurationValue - 1 ) ;
}
/**
* @ brief Select active configuration
*
* During enumeration process , device objects could have several configuration that can be activated
* To be able to select configuration this call should be used
* This will call the enumeration filter callback ( if enabled ) and set the bConfigurationValue for the upcoming SetConfiguration ( ) command
*
* @ return esp_err_t
*/
static esp_err_t select_active_configuration ( void )
{
// This configuration value must be zero or match a configuration value from a configuration descriptor.
// If the configuration value is zero, the device is placed in its Address state.
// But some devices STALLed get configuration descriptor with bConfigurationValue = 1, even they have one configuration with bValue = 1.
uint8_t bConfigurationValue = ENUM_DEFAULT_CONFIGURATION_VALUE ;
# if ENABLE_ENUM_FILTER_CALLBACK
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
const usb_device_desc_t * dev_desc ;
ESP_ERROR_CHECK ( usbh_dev_get_desc ( dev_hdl , & dev_desc ) ) ;
bool enum_proceed = false ;
// Sanity check
assert ( dev_desc ) ;
if ( p_enum_driver - > constant . enum_filter_cb ) {
enum_proceed = p_enum_driver - > constant . enum_filter_cb ( dev_desc , & bConfigurationValue ) ;
}
// User's request NOT to enumerate the USB device
if ( ! enum_proceed ) {
ESP_LOGW ( ENUM_TAG , " [%d:%d] Abort request of enumeration process (%#x:%#x) " ,
p_enum_driver - > single_thread . parent_dev_addr ,
p_enum_driver - > single_thread . parent_port_num ,
dev_desc - > idProduct ,
dev_desc - > idVendor ) ;
enum_cancel ( p_enum_driver - > single_thread . dev_uid ) ;
return ESP_OK ;
}
// Set configuration descriptor
if ( ( bConfigurationValue = = 0 ) | | ( bConfigurationValue > dev_desc - > bNumConfigurations ) ) {
ESP_LOGE ( ENUM_TAG , " Invalid bConfigurationValue (%d) provided by user, using default " , bConfigurationValue ) ;
bConfigurationValue = ENUM_DEFAULT_CONFIGURATION_VALUE ;
}
# endif // ENABLE_ENUM_FILTER_CALLBACK
ESP_LOGD ( ENUM_TAG , " Selected bConfigurationValue=%d " , bConfigurationValue ) ;
p_enum_driver - > single_thread . dev_params . bConfigurationValue = bConfigurationValue ;
return ESP_OK ;
}
2024-07-18 04:00:07 -04:00
static esp_err_t second_reset_request ( void )
2024-03-04 15:11:43 -05:00
{
// Notify USB Host
enum_event_data_t event_data = {
. event = ENUM_EVENT_RESET_REQUIRED ,
. reset_req = {
. parent_dev_hdl = p_enum_driver - > single_thread . parent_dev_hdl ,
. parent_port_num = p_enum_driver - > single_thread . parent_port_num ,
} ,
} ;
p_enum_driver - > constant . enum_event_cb ( & event_data , p_enum_driver - > constant . enum_event_cb_arg ) ;
return ESP_OK ;
}
/**
* @ brief Get index and langid
*
* Returns index and langid , based on enumerator stage .
*
* @ param [ in ] stage Stage
* @ param [ out ] index String index
* @ param [ out ] langid String langid
*/
static inline void get_index_langid_for_stage ( enum_stage_t stage , uint8_t * index , uint16_t * langid )
{
switch ( stage ) {
case ENUM_STAGE_GET_SHORT_LANGID_TABLE :
case ENUM_STAGE_GET_FULL_LANGID_TABLE :
* index = 0 ; // The LANGID table uses an index of 0
* langid = 0 ; // Getting the LANGID table itself should use a LANGID of 0
break ;
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
* index = p_enum_driver - > single_thread . dev_params . iManufacturer ;
* langid = ENUM_LANGID ; // Use the default LANGID
break ;
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
* index = p_enum_driver - > single_thread . dev_params . iProduct ;
* langid = ENUM_LANGID ; // Use the default LANGID
break ;
case ENUM_STAGE_GET_SHORT_SER_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC :
* index = p_enum_driver - > single_thread . dev_params . iSerialNumber ;
* langid = ENUM_LANGID ; // Use the default LANGID
break ;
default :
// Should not occur
abort ( ) ;
break ;
}
}
/**
* @ brief Control request : General
*
* Prepares the Control request byte - data transfer for current stage of the enumerator
2024-07-18 04:00:07 -04:00
*
* @ param [ in ] stage Enumeration stage
2024-03-04 15:11:43 -05:00
*/
2024-07-18 04:00:07 -04:00
static void control_request_general ( enum_stage_t stage )
2024-03-04 15:11:43 -05:00
{
usb_transfer_t * transfer = & p_enum_driver - > constant . urb - > transfer ;
uint8_t ctrl_ep_mps = p_enum_driver - > single_thread . dev_params . bMaxPacketSize0 ;
uint16_t wTotalLength = p_enum_driver - > single_thread . dev_params . wTotalLength ;
uint8_t bConfigurationValue = p_enum_driver - > single_thread . dev_params . bConfigurationValue ;
uint8_t desc_index = get_configuration_descriptor_index ( bConfigurationValue ) ;
2024-07-18 04:00:07 -04:00
switch ( stage ) {
2024-03-04 15:11:43 -05:00
case ENUM_STAGE_GET_SHORT_DEV_DESC : {
// Initialize a short device descriptor request
USB_SETUP_PACKET_INIT_GET_DEVICE_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer ) ;
( ( usb_setup_packet_t * ) transfer - > data_buffer ) - > wLength = ENUM_SHORT_DESC_REQ_LEN ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( ENUM_SHORT_DESC_REQ_LEN , ctrl_ep_mps ) ;
// IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + ENUM_SHORT_DESC_REQ_LEN ;
break ;
}
case ENUM_STAGE_SET_ADDR : {
p_enum_driver - > single_thread . dev_params . new_dev_addr = get_next_free_dev_addr ( ) ;
USB_SETUP_PACKET_INIT_SET_ADDR ( ( usb_setup_packet_t * ) transfer - > data_buffer , p_enum_driver - > single_thread . dev_params . new_dev_addr ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) ; // No data stage
p_enum_driver - > single_thread . expect_num_bytes = 0 ; // OUT transfer. No need to check number of bytes returned
break ;
}
case ENUM_STAGE_GET_FULL_DEV_DESC : {
USB_SETUP_PACKET_INIT_GET_DEVICE_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( sizeof ( usb_device_desc_t ) , ctrl_ep_mps ) ;
// IN data stage should return exactly sizeof(usb_device_desc_t) bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_device_desc_t ) ;
break ;
}
case ENUM_STAGE_GET_SHORT_CONFIG_DESC : {
// Get a short config descriptor at descriptor index
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer , desc_index , ENUM_SHORT_DESC_REQ_LEN ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( ENUM_SHORT_DESC_REQ_LEN , ctrl_ep_mps ) ;
// IN data stage should return exactly ENUM_SHORT_DESC_REQ_LEN bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + ENUM_SHORT_DESC_REQ_LEN ;
break ;
}
case ENUM_STAGE_GET_FULL_CONFIG_DESC : {
// Get the full configuration descriptor at descriptor index, requesting its exact length.
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer , desc_index , wTotalLength ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( wTotalLength , ctrl_ep_mps ) ;
// IN data stage should return exactly wTotalLength bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + wTotalLength ;
break ;
}
case ENUM_STAGE_SET_CONFIG : {
USB_SETUP_PACKET_INIT_SET_CONFIG ( ( usb_setup_packet_t * ) transfer - > data_buffer , bConfigurationValue ) ;
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) ; // No data stage
p_enum_driver - > single_thread . expect_num_bytes = 0 ; // OUT transfer. No need to check number of bytes returned
break ;
}
default :
// Should never occur
p_enum_driver - > single_thread . expect_num_bytes = 0 ;
abort ( ) ;
break ;
}
}
/**
* @ brief Control request : String
*
* Prepares the Control request string - data transfer for current stage of the enumerator
2024-07-18 04:00:07 -04:00
*
* @ param [ in ] stage Enumeration stage
2024-03-04 15:11:43 -05:00
*/
2024-07-18 04:00:07 -04:00
static void control_request_string ( enum_stage_t stage )
2024-03-04 15:11:43 -05:00
{
usb_transfer_t * transfer = & p_enum_driver - > constant . urb - > transfer ;
2024-06-12 15:29:41 -04:00
uint8_t ctrl_ep_mps = p_enum_driver - > single_thread . dev_params . bMaxPacketSize0 ;
2024-03-04 15:11:43 -05:00
uint8_t bLength = p_enum_driver - > single_thread . dev_params . str_desc_bLength ;
uint8_t index = 0 ;
uint16_t langid = 0 ;
get_index_langid_for_stage ( stage , & index , & langid ) ;
switch ( stage ) {
case ENUM_STAGE_GET_SHORT_LANGID_TABLE :
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_SHORT_SER_STR_DESC : {
// Get only the header of the string descriptor
USB_SETUP_PACKET_INIT_GET_STR_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer , index , langid , sizeof ( usb_str_desc_t ) ) ;
2024-06-12 15:29:41 -04:00
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( sizeof ( usb_str_desc_t ) , ctrl_ep_mps ) ;
2024-03-04 15:11:43 -05:00
// IN data stage should return exactly sizeof(usb_str_desc_t) bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + sizeof ( usb_str_desc_t ) ;
break ;
}
case ENUM_STAGE_GET_FULL_LANGID_TABLE :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC : {
// Get the full string descriptor at a particular index, requesting the descriptors exact length
USB_SETUP_PACKET_INIT_GET_STR_DESC ( ( usb_setup_packet_t * ) transfer - > data_buffer , index , langid , bLength ) ;
2024-06-12 15:29:41 -04:00
transfer - > num_bytes = sizeof ( usb_setup_packet_t ) + usb_round_up_to_mps ( bLength , ctrl_ep_mps ) ;
2024-03-04 15:11:43 -05:00
// IN data stage should return exactly str_desc_bLength bytes
p_enum_driver - > single_thread . expect_num_bytes = sizeof ( usb_setup_packet_t ) + bLength ;
break ;
}
default :
// Should never occur
p_enum_driver - > single_thread . expect_num_bytes = 0 ;
abort ( ) ;
break ;
}
}
/**
* @ brief Parse short Device descriptor
*
* Parses short device descriptor response
* Configures the EP0 MPS for device object under enumeration
*/
static esp_err_t parse_short_dev_desc ( void )
{
esp_err_t ret = ESP_OK ;
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
usb_transfer_t * ctrl_xfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_device_desc_t * dev_desc = ( usb_device_desc_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
// Check if the returned descriptor has correct type
if ( dev_desc - > bDescriptorType ! = USB_B_DESCRIPTOR_TYPE_DEVICE ) {
ESP_LOGE ( ENUM_TAG , " Short dev desc has wrong bDescriptorType " ) ;
ret = ESP_ERR_INVALID_RESPONSE ;
goto exit ;
}
// Update and save actual MPS of the default pipe
ret = usbh_dev_set_ep0_mps ( dev_hdl , dev_desc - > bMaxPacketSize0 ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGE ( ENUM_TAG , " Failed to update MPS " ) ;
goto exit ;
}
// Save the actual MPS of EP0 in enum driver context
p_enum_driver - > single_thread . dev_params . bMaxPacketSize0 = dev_desc - > bMaxPacketSize0 ;
exit :
return ret ;
}
static esp_err_t check_addr ( void )
{
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
uint8_t assign_addr = p_enum_driver - > single_thread . dev_params . new_dev_addr ;
ESP_LOGD ( ENUM_TAG , " Assign address (dev_addr=%d) " , assign_addr ) ;
esp_err_t ret = usbh_dev_set_addr ( dev_hdl , assign_addr ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGE ( ENUM_TAG , " Error during assign device address " ) ;
}
return ret ;
}
/**
* @ brief Parse full Device descriptor response
*
* Parses full device descriptor response
* Set device descriptor for device object under enumeration
*/
static esp_err_t parse_full_dev_desc ( void )
{
esp_err_t ret = ESP_OK ;
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
usb_transfer_t * ctrl_xfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_device_desc_t * dev_desc = ( usb_device_desc_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
// Check if the returned descriptor has correct type
if ( dev_desc - > bDescriptorType ! = USB_B_DESCRIPTOR_TYPE_DEVICE ) {
ESP_LOGE ( ENUM_TAG , " Full dev desc has wrong bDescriptorType " ) ;
ret = ESP_ERR_INVALID_RESPONSE ;
goto exit ;
}
// Save string parameters
p_enum_driver - > single_thread . dev_params . iManufacturer = dev_desc - > iManufacturer ;
p_enum_driver - > single_thread . dev_params . iProduct = dev_desc - > iProduct ;
p_enum_driver - > single_thread . dev_params . iSerialNumber = dev_desc - > iSerialNumber ;
// Device has more than one configuration
if ( dev_desc - > bNumConfigurations > 1 ) {
ESP_LOGW ( ENUM_TAG , " Device has more than 1 configuration " ) ;
}
// Allocate Device descriptor and set it's value to device object
ret = usbh_dev_set_desc ( dev_hdl , dev_desc ) ;
exit :
return ret ;
}
/**
* @ brief Parse short Configuration descriptor
*
* Parses short Configuration descriptor response
* Set the length to request full Configuration descriptor
*/
static esp_err_t parse_short_config_desc ( void )
{
esp_err_t ret ;
usb_transfer_t * ctrl_xfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_config_desc_t * config_desc = ( usb_config_desc_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
// Check if the returned descriptor is corrupted
if ( config_desc - > bDescriptorType ! = USB_B_DESCRIPTOR_TYPE_CONFIGURATION ) {
ESP_LOGE ( ENUM_TAG , " Short config desc has wrong bDescriptorType " ) ;
ret = ESP_ERR_INVALID_RESPONSE ;
goto exit ;
}
# if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT16_MAX) // Suppress -Wtype-limits warning due to uint16_t wTotalLength
// Check if the descriptor is too long to be supported
if ( config_desc - > wTotalLength > ENUM_CTRL_TRANSFER_MAX_DATA_LEN ) {
ESP_LOGE ( ENUM_TAG , " Configuration descriptor larger than control transfer max length " ) ;
ret = ESP_ERR_INVALID_SIZE ;
goto exit ;
}
# endif
// Set the configuration descriptor's full length
p_enum_driver - > single_thread . dev_params . wTotalLength = config_desc - > wTotalLength ;
ret = ESP_OK ;
exit :
return ret ;
}
/**
* @ brief Parse full Configuration descriptor
*
* Parses full Configuration descriptor response
* Set the Configuration descriptor to device object under enumeration
*/
static esp_err_t parse_full_config_desc ( void )
{
esp_err_t ret ;
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
usb_transfer_t * ctrl_xfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_config_desc_t * config_desc = ( usb_config_desc_t * ) ( ctrl_xfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
// Check if the returned descriptor is corrupted
if ( config_desc - > bDescriptorType ! = USB_B_DESCRIPTOR_TYPE_CONFIGURATION ) {
ESP_LOGE ( ENUM_TAG , " Full config desc has wrong bDescriptorType " ) ;
ret = ESP_ERR_INVALID_RESPONSE ;
goto exit ;
}
// Allocate Configuration descriptor and set it's value to device object
ret = usbh_dev_set_config_desc ( dev_hdl , config_desc ) ;
exit :
return ret ;
}
/**
* @ brief Parse short String descriptor
*
* Parses short String descriptor response
* Set the length to request full String descriptor
*/
static esp_err_t parse_short_str_desc ( void )
{
esp_err_t ret ;
usb_transfer_t * transfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_str_desc_t * str_desc = ( usb_str_desc_t * ) ( transfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
//Check if the returned descriptor is supported or corrupted
if ( str_desc - > bDescriptorType = = 0 ) {
ESP_LOGE ( ENUM_TAG , " String desc not supported " ) ;
ret = ESP_ERR_NOT_SUPPORTED ;
goto exit ;
} else if ( str_desc - > bDescriptorType ! = USB_B_DESCRIPTOR_TYPE_STRING ) {
ESP_LOGE ( ENUM_TAG , " Short string desc corrupt " ) ;
ret = ESP_ERR_INVALID_RESPONSE ;
goto exit ;
}
# if (ENUM_CTRL_TRANSFER_MAX_DATA_LEN < UINT8_MAX) //Suppress -Wtype-limits warning due to uint8_t bLength
//Check if the descriptor is too long to be supported
if ( str_desc - > bLength > ( uint32_t ) ENUM_CTRL_TRANSFER_MAX_DATA_LEN ) {
ESP_LOGE ( ENUM_TAG , " String descriptor larger than control transfer max length " ) ;
ret = ESP_ERR_INVALID_SIZE ;
goto exit ;
}
# endif
// Set the descriptor's full length
p_enum_driver - > single_thread . dev_params . str_desc_bLength = str_desc - > bLength ;
ret = ESP_OK ;
exit :
return ret ;
}
/**
* @ brief Parse Language ID table
*
* Parses Language ID table response
* Searches Language ID table for LangID = 0x0409
*/
static esp_err_t parse_langid_table ( void )
{
esp_err_t ret ;
usb_transfer_t * transfer = & p_enum_driver - > constant . urb - > transfer ;
const usb_str_desc_t * str_desc = ( usb_str_desc_t * ) ( transfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
//Scan the LANGID table for our target LANGID
ret = ESP_ERR_NOT_FOUND ;
int langid_table_num_entries = ( str_desc - > bLength - sizeof ( usb_str_desc_t ) ) / 2 ; // Each LANGID is 2 bytes
for ( int i = 0 ; i < langid_table_num_entries ; i + + ) { // Each LANGID is 2 bytes
if ( str_desc - > wData [ i ] = = ENUM_LANGID ) {
ret = ESP_OK ;
break ;
}
}
if ( ret ! = ESP_OK ) {
ESP_LOGE ( ENUM_TAG , " LANGID %#x not found " , ENUM_LANGID ) ;
}
return ret ;
}
/**
* @ brief Get String index number
*
* Returns string number index ( 0 , 1 or 2 ) based on stage
*/
static inline int get_str_index ( enum_stage_t stage )
{
switch ( stage ) {
case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC :
return 0 ;
case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC :
return 1 ;
case ENUM_STAGE_CHECK_FULL_SER_STR_DESC :
return 2 ;
default :
break ;
}
// Should never occurred
abort ( ) ;
return - 1 ;
}
/**
* @ brief Parse full String descriptor
*
* Set String descriptor to the device object under enumeration
*/
static esp_err_t parse_full_str_desc ( void )
{
usb_transfer_t * transfer = & p_enum_driver - > constant . urb - > transfer ;
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
const usb_str_desc_t * str_desc = ( usb_str_desc_t * ) ( transfer - > data_buffer + sizeof ( usb_setup_packet_t ) ) ;
2024-07-18 04:00:07 -04:00
return usbh_dev_set_str_desc ( dev_hdl , str_desc , get_str_index ( p_enum_driver - > single_thread . stage ) ) ;
2024-03-04 15:11:43 -05:00
}
static esp_err_t check_config ( void )
{
// Nothing to parse after a SET_CONFIG request
return ESP_OK ;
}
// -----------------------------------------------------------------------------
// ---------------------- Stage handle functions -------------------------------
// -----------------------------------------------------------------------------
/**
* @ brief Control request stage
*
* Based on the stage , does prepare General or String Control request
2024-07-18 04:00:07 -04:00
*
* @ param [ in ] stage Enumeration stage
2024-03-04 15:11:43 -05:00
*/
2024-07-18 04:00:07 -04:00
static esp_err_t control_request ( enum_stage_t stage )
2024-03-04 15:11:43 -05:00
{
esp_err_t ret ;
2024-07-18 04:00:07 -04:00
switch ( stage ) {
2024-03-04 15:11:43 -05:00
case ENUM_STAGE_GET_SHORT_DEV_DESC :
case ENUM_STAGE_SET_ADDR :
case ENUM_STAGE_GET_FULL_DEV_DESC :
case ENUM_STAGE_GET_SHORT_CONFIG_DESC :
case ENUM_STAGE_GET_FULL_CONFIG_DESC :
case ENUM_STAGE_SET_CONFIG :
2024-07-18 04:00:07 -04:00
control_request_general ( stage ) ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_GET_SHORT_LANGID_TABLE :
case ENUM_STAGE_GET_FULL_LANGID_TABLE :
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
case ENUM_STAGE_GET_SHORT_SER_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC :
2024-07-18 04:00:07 -04:00
control_request_string ( stage ) ;
2024-03-04 15:11:43 -05:00
break ;
default : // Should never occur
ret = ESP_ERR_INVALID_STATE ;
abort ( ) ;
break ;
}
ret = usbh_dev_submit_ctrl_urb ( p_enum_driver - > single_thread . dev_hdl , p_enum_driver - > constant . urb ) ;
if ( ret ! = ESP_OK ) {
ESP_LOGE ( ENUM_TAG , " [%d:%d] Control transfer submit error (%#x), stage '%s' " ,
p_enum_driver - > single_thread . parent_dev_addr ,
p_enum_driver - > single_thread . parent_port_num ,
ret ,
2024-07-18 04:00:07 -04:00
enum_stage_strings [ stage ] ) ;
2024-03-04 15:11:43 -05:00
}
return ret ;
}
/**
* @ brief Control request response handling stage
*
* Based on the stage , does parse the response data
2024-07-18 04:00:07 -04:00
*
* @ param [ in ] stage Enumeration stage
2024-03-04 15:11:43 -05:00
*/
2024-07-18 04:00:07 -04:00
static esp_err_t control_response_handling ( enum_stage_t stage )
2024-03-04 15:11:43 -05:00
{
esp_err_t ret = ESP_FAIL ;
// Check transfer status
int expected_num_bytes = p_enum_driver - > single_thread . expect_num_bytes ;
usb_transfer_t * ctrl_xfer = & p_enum_driver - > constant . urb - > transfer ;
2024-06-05 09:15:51 -04:00
if ( ctrl_xfer - > status ! = USB_TRANSFER_STATUS_COMPLETED ) {
ESP_LOGE ( ENUM_TAG , " Bad transfer status %d: %s " ,
ctrl_xfer - > status ,
2024-07-18 04:00:07 -04:00
enum_stage_strings [ stage ] ) ;
2024-06-05 09:15:51 -04:00
return ret ;
}
2024-03-04 15:11:43 -05:00
2024-07-18 04:00:07 -04:00
// Transfer completed, verbose data in ESP_LOG_VERBOSE is set
ESP_LOG_BUFFER_HEXDUMP ( ENUM_TAG , ctrl_xfer - > data_buffer , ctrl_xfer - > actual_num_bytes , ESP_LOG_VERBOSE ) ;
2024-03-04 15:11:43 -05:00
// Check Control IN transfer returned the expected correct number of bytes
if ( expected_num_bytes ! = 0 & & expected_num_bytes ! = ctrl_xfer - > actual_num_bytes ) {
ESP_LOGW ( ENUM_TAG , " [%d:%d] Unexpected (%d) device response length (expected %d) " ,
p_enum_driver - > single_thread . parent_dev_addr ,
p_enum_driver - > single_thread . parent_port_num ,
ctrl_xfer - > actual_num_bytes ,
expected_num_bytes ) ;
if ( ctrl_xfer - > actual_num_bytes < expected_num_bytes ) {
// The device returned less bytes than requested. We cannot continue.
ESP_LOGE ( ENUM_TAG , " Device returned less bytes than requested " ) ;
ret = ESP_ERR_INVALID_SIZE ;
goto exit ;
}
// The device returned more bytes than requested.
// This violates the USB specs chapter 9.3.5, but we can continue
}
2024-07-18 04:00:07 -04:00
switch ( stage ) {
2024-03-04 15:11:43 -05:00
case ENUM_STAGE_CHECK_SHORT_DEV_DESC :
ret = parse_short_dev_desc ( ) ;
break ;
case ENUM_STAGE_CHECK_ADDR :
ret = check_addr ( ) ;
break ;
case ENUM_STAGE_CHECK_FULL_DEV_DESC :
ret = parse_full_dev_desc ( ) ;
break ;
case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC :
ret = parse_short_config_desc ( ) ;
break ;
case ENUM_STAGE_CHECK_FULL_CONFIG_DESC :
ret = parse_full_config_desc ( ) ;
break ;
case ENUM_STAGE_CHECK_CONFIG :
ret = check_config ( ) ;
break ;
case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE :
case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC :
ret = parse_short_str_desc ( ) ;
break ;
case ENUM_STAGE_CHECK_FULL_LANGID_TABLE :
ret = parse_langid_table ( ) ;
break ;
case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC :
case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC :
case ENUM_STAGE_CHECK_FULL_SER_STR_DESC :
ret = parse_full_str_desc ( ) ;
break ;
default :
// Should never occurred
ret = ESP_ERR_INVALID_STATE ;
abort ( ) ;
break ;
}
exit :
return ret ;
}
/**
* @ brief Cancel stage
*
* Force shutdown device object under enumeration
*/
static esp_err_t stage_cancel ( void )
{
// There should be device under enumeration
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
usb_device_handle_t parent_dev_hdl = p_enum_driver - > single_thread . parent_dev_hdl ;
uint8_t parent_port_num = p_enum_driver - > single_thread . parent_port_num ;
2024-07-18 04:00:07 -04:00
if ( dev_hdl ) {
ESP_ERROR_CHECK ( usbh_dev_enum_unlock ( dev_hdl ) ) ;
ESP_ERROR_CHECK ( usbh_dev_close ( dev_hdl ) ) ;
}
2024-03-04 15:11:43 -05:00
2024-07-18 04:00:07 -04:00
// Clean up variables device from enumerator
2024-03-04 15:11:43 -05:00
p_enum_driver - > single_thread . dev_uid = 0 ;
p_enum_driver - > single_thread . dev_hdl = NULL ;
p_enum_driver - > single_thread . parent_dev_hdl = NULL ;
p_enum_driver - > single_thread . parent_dev_addr = 0 ;
p_enum_driver - > single_thread . parent_port_num = 0 ;
p_enum_driver - > constant . urb - > transfer . context = NULL ;
2024-07-18 04:00:07 -04:00
// Propagate the event
2024-03-04 15:11:43 -05:00
enum_event_data_t event_data = {
. event = ENUM_EVENT_CANCELED ,
. canceled = {
. parent_dev_hdl = parent_dev_hdl ,
. parent_port_num = parent_port_num ,
} ,
} ;
p_enum_driver - > constant . enum_event_cb ( & event_data , p_enum_driver - > constant . enum_event_cb_arg ) ;
return ESP_OK ;
}
/**
* @ brief Complete stage
*
* Closes device object under enumeration
*/
static esp_err_t stage_complete ( void )
{
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
usb_device_handle_t parent_dev_hdl = p_enum_driver - > single_thread . parent_dev_hdl ;
uint8_t parent_dev_addr = p_enum_driver - > single_thread . parent_dev_addr ;
uint8_t parent_port_num = p_enum_driver - > single_thread . parent_port_num ;
uint8_t dev_addr = 0 ;
ESP_ERROR_CHECK ( usbh_dev_get_addr ( dev_hdl , & dev_addr ) ) ;
// Close device
ESP_ERROR_CHECK ( usbh_dev_enum_unlock ( dev_hdl ) ) ;
ESP_ERROR_CHECK ( usbh_dev_close ( dev_hdl ) ) ;
// Release device from enumerator
p_enum_driver - > single_thread . dev_uid = 0 ;
p_enum_driver - > single_thread . dev_hdl = NULL ;
p_enum_driver - > single_thread . parent_dev_hdl = NULL ;
p_enum_driver - > single_thread . parent_dev_addr = 0 ;
p_enum_driver - > single_thread . parent_port_num = 0 ;
// Release device from enumerator
p_enum_driver - > constant . urb - > transfer . context = NULL ;
// Flush device params
memset ( & p_enum_driver - > single_thread . dev_params , 0 , sizeof ( enum_device_params_t ) ) ;
p_enum_driver - > single_thread . expect_num_bytes = 0 ;
// Increase device address to use new value during the next enumeration process
get_next_dev_addr ( ) ;
ESP_LOGD ( ENUM_TAG , " [%d:%d] Processing complete, new device address %d " ,
parent_dev_addr ,
parent_port_num ,
dev_addr ) ;
enum_event_data_t event_data = {
. event = ENUM_EVENT_COMPLETED ,
. complete = {
. dev_hdl = dev_hdl ,
. dev_addr = dev_addr ,
. parent_dev_hdl = parent_dev_hdl ,
. parent_port_num = parent_port_num ,
} ,
} ;
p_enum_driver - > constant . enum_event_cb ( & event_data , p_enum_driver - > constant . enum_event_cb_arg ) ;
return ESP_OK ;
}
// -----------------------------------------------------------------------------
// -------------------------- State Machine ------------------------------------
// -----------------------------------------------------------------------------
2024-07-18 04:00:07 -04:00
/**
* @ brief Returns the process requirement flag for the stage
*
* When stage doesn ' t have a control transfer callback which request processing , request process should be triggered immediately
*
* @ param [ in ] stage Enumeration process stage
* @ return Processing for the stage is :
* @ retval true Required
* @ retval false Not required
*/
static bool stage_need_process ( enum_stage_t stage )
{
bool need_process_cb = false ;
switch ( stage ) {
// Transfer submission stages
// Stages have transfer completion callback which will re-trigger the processing
case ENUM_STAGE_GET_SHORT_DEV_DESC :
case ENUM_STAGE_SET_ADDR :
case ENUM_STAGE_GET_FULL_DEV_DESC :
case ENUM_STAGE_GET_SHORT_CONFIG_DESC :
case ENUM_STAGE_GET_FULL_CONFIG_DESC :
case ENUM_STAGE_SET_CONFIG :
case ENUM_STAGE_GET_SHORT_LANGID_TABLE :
case ENUM_STAGE_GET_FULL_LANGID_TABLE :
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
case ENUM_STAGE_GET_SHORT_SER_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC :
// Other stages
// Stages, require the re-triggering the processing
case ENUM_STAGE_SECOND_RESET :
case ENUM_STAGE_SET_ADDR_RECOVERY :
case ENUM_STAGE_SELECT_CONFIG :
case ENUM_STAGE_COMPLETE :
case ENUM_STAGE_CANCEL :
need_process_cb = true ;
break ;
default :
break ;
}
return need_process_cb ;
}
2024-03-04 15:11:43 -05:00
/**
* @ brief Set next stage
*
* Does set next stage , based on the successful completion of last stage
* Some stages ( i . e . , string descriptors ) are skipped if the device doesn ' t support them
* Some stages ( i . e . string descriptors ) are allowed to fail
*
* @ param [ in ] last_stage_pass Flag of successful completion last stage
2024-07-18 04:00:07 -04:00
*
* @ return Processing for the next stage is :
* @ retval true Required
* @ retval false Not required
2024-03-04 15:11:43 -05:00
*/
2024-07-18 04:00:07 -04:00
static bool set_next_stage ( bool last_stage_pass )
2024-03-04 15:11:43 -05:00
{
2024-07-18 04:00:07 -04:00
enum_stage_t last_stage = p_enum_driver - > single_thread . stage ;
2024-03-04 15:11:43 -05:00
enum_stage_t next_stage ;
while ( 1 ) {
bool stage_skip = false ;
// Find the next stage
if ( last_stage_pass ) {
// Last stage was successful
ESP_LOGD ( ENUM_TAG , " [%d:%d] %s OK " ,
p_enum_driver - > single_thread . parent_dev_addr ,
p_enum_driver - > single_thread . parent_port_num ,
enum_stage_strings [ last_stage ] ) ;
// Get next stage
2024-07-18 04:00:07 -04:00
if ( last_stage = = ENUM_STAGE_COMPLETE | |
last_stage = = ENUM_STAGE_CANCEL ) {
// Stages are terminal, move state machine to IDLE
2024-03-04 15:11:43 -05:00
next_stage = ENUM_STAGE_IDLE ;
} else {
// Simply increment to get the next stage
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 & &
2024-07-18 04:00:07 -04:00
last_stage ! = ENUM_STAGE_SECOND_RESET & &
last_stage ! = ENUM_STAGE_SECOND_RESET_COMPLETE & &
2024-03-04 15:11:43 -05:00
last_stage ! = ENUM_STAGE_COMPLETE & &
last_stage ! = ENUM_STAGE_CANCEL ) ;
// Last stage failed
switch ( last_stage ) {
// Stages that are allowed to fail skip to the next appropriate stage
case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE :
case ENUM_STAGE_CHECK_FULL_LANGID_TABLE :
2024-06-05 09:15:51 -04:00
// Couldn't get LANGID, skip the rest of the string descriptors
2024-03-04 15:11:43 -05:00
case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC :
case ENUM_STAGE_CHECK_FULL_SER_STR_DESC :
2024-06-05 09:15:51 -04:00
// iSerialNumber string failed. Jump to Set Configuration and complete enumeration process.
next_stage = ENUM_STAGE_SET_CONFIG ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC :
case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC :
// iManufacturer string failed. Get iProduct string next
next_stage = ENUM_STAGE_GET_SHORT_PROD_STR_DESC ;
break ;
case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC :
case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC :
// iProduct string failed. Get iSerialNumber string next
next_stage = ENUM_STAGE_GET_SHORT_SER_STR_DESC ;
break ;
case ENUM_STAGE_COMPLETE :
case ENUM_STAGE_CANCEL :
// These stages should never fail
abort ( ) ;
break ;
default :
// Stage is not allowed to failed. Cancel enumeration.
next_stage = ENUM_STAGE_CANCEL ;
break ;
}
}
// Check if the next stage should be skipped
switch ( next_stage ) {
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC :
// Device doesn't support iManufacturer string
if ( p_enum_driver - > single_thread . dev_params . iManufacturer = = 0 ) {
ESP_LOGD ( ENUM_TAG , " String iManufacturer not set, skip " ) ;
stage_skip = true ;
}
break ;
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC :
// Device doesn't support iProduct string
if ( p_enum_driver - > single_thread . dev_params . iProduct = = 0 ) {
ESP_LOGD ( ENUM_TAG , " String iProduct not set, skip " ) ;
stage_skip = true ;
}
break ;
case ENUM_STAGE_GET_SHORT_SER_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC :
case ENUM_STAGE_CHECK_FULL_SER_STR_DESC :
// Device doesn't support iSerialNumber string
if ( p_enum_driver - > single_thread . dev_params . iSerialNumber = = 0 ) {
ESP_LOGD ( ENUM_TAG , " String iSerialNumber not set, skip " ) ;
stage_skip = true ;
}
break ;
default :
break ;
}
if ( stage_skip ) {
// Loop back around to get the next stage again
last_stage = next_stage ;
} else {
break ;
}
}
2024-07-18 04:00:07 -04:00
p_enum_driver - > single_thread . stage = next_stage ;
return stage_need_process ( next_stage ) ;
2024-03-04 15:11:43 -05:00
}
/**
* @ brief Control transfer completion callback
*
* Is called by lower logic when transfer is completed with or without error
*
* @ param [ in ] ctrl_xfer Pointer to a transfer buffer
*/
static void enum_control_transfer_complete ( usb_transfer_t * ctrl_xfer )
{
// Sanity checks
assert ( ctrl_xfer ) ;
assert ( ctrl_xfer - > context ) ;
assert ( p_enum_driver - > single_thread . dev_hdl = = ctrl_xfer - > context ) ;
// Request processing
p_enum_driver - > constant . proc_req_cb ( USB_PROC_REQ_SOURCE_ENUM , false , p_enum_driver - > constant . proc_req_cb_arg ) ;
}
// -----------------------------------------------------------------------------
// -------------------------- Public API ---------------------------------------
// -----------------------------------------------------------------------------
esp_err_t enum_install ( enum_config_t * config , void * * client_ret )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver = = NULL , ESP_ERR_INVALID_STATE ) ;
2024-03-04 15:11:43 -05:00
ENUM_CHECK ( config ! = NULL , ESP_ERR_INVALID_ARG ) ;
esp_err_t ret ;
enum_driver_t * enum_drv = heap_caps_calloc ( 1 , sizeof ( enum_driver_t ) , MALLOC_CAP_DEFAULT ) ;
ENUM_CHECK ( enum_drv , ESP_ERR_NO_MEM ) ;
// Initialize ENUM objects
urb_t * urb = urb_alloc ( sizeof ( usb_setup_packet_t ) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN , 0 ) ;
if ( urb = = NULL ) {
ret = ESP_ERR_NOT_FINISHED ;
goto alloc_err ;
}
// Setup urb
urb - > usb_host_client = ( void * ) enum_drv ; // Client is an address of the enum driver object
urb - > transfer . callback = enum_control_transfer_complete ;
enum_drv - > constant . urb = urb ;
// Save callbacks
enum_drv - > constant . proc_req_cb = config - > proc_req_cb ;
enum_drv - > constant . proc_req_cb_arg = config - > proc_req_cb_arg ;
enum_drv - > constant . enum_event_cb = config - > enum_event_cb ;
enum_drv - > constant . enum_event_cb_arg = config - > enum_event_cb_arg ;
# if ENABLE_ENUM_FILTER_CALLBACK
enum_drv - > constant . enum_filter_cb = config - > enum_filter_cb ;
enum_drv - > constant . enum_filter_cb_arg = config - > enum_filter_cb_arg ;
# endif // ENABLE_ENUM_FILTER_CALLBACK
2024-07-18 04:00:07 -04:00
enum_drv - > single_thread . next_dev_addr = ENUM_INIT_VALUE_DEV_ADDR ;
enum_drv - > single_thread . stage = ENUM_STAGE_IDLE ;
2024-03-04 15:11:43 -05:00
2024-07-18 04:00:07 -04:00
// Enumeration driver is single_threaded
2024-03-04 15:11:43 -05:00
if ( p_enum_driver ! = NULL ) {
ret = ESP_ERR_NOT_FINISHED ;
goto err ;
}
p_enum_driver = enum_drv ;
// Write-back client_ret pointer
* client_ret = ( void * ) enum_drv ;
return ESP_OK ;
err :
urb_free ( urb ) ;
alloc_err :
heap_caps_free ( enum_drv ) ;
return ret ;
}
esp_err_t enum_uninstall ( void )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
// Enumeration driver is single_threaded
2024-03-04 15:11:43 -05:00
enum_driver_t * enum_drv = p_enum_driver ;
p_enum_driver = NULL ;
// Free resources
urb_free ( enum_drv - > constant . urb ) ;
heap_caps_free ( enum_drv ) ;
return ESP_OK ;
}
esp_err_t enum_start ( unsigned int uid )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
2024-03-04 15:11:43 -05:00
esp_err_t ret = ESP_FAIL ;
// Open device and lock it for enumeration process
usb_device_handle_t dev_hdl ;
ret = usbh_devs_open ( 0 , & dev_hdl ) ;
if ( ret ! = ESP_OK ) {
return ret ;
}
ESP_ERROR_CHECK ( usbh_dev_enum_lock ( dev_hdl ) ) ;
// Get device info
usb_device_info_t dev_info ;
uint8_t parent_dev_addr = 0 ;
ESP_ERROR_CHECK ( usbh_dev_get_info ( dev_hdl , & dev_info ) ) ;
if ( dev_info . parent . dev_hdl ) {
ESP_ERROR_CHECK ( usbh_dev_get_addr ( dev_info . parent . dev_hdl , & parent_dev_addr ) ) ;
}
// Stage ENUM_STAGE_GET_SHORT_DEV_DESC
ESP_LOGD ( ENUM_TAG , " [%d:%d] Start processing, device address %d " ,
parent_dev_addr ,
dev_info . parent . port_num ,
0 ) ;
2024-07-18 04:00:07 -04:00
p_enum_driver - > single_thread . stage = ENUM_STAGE_GET_SHORT_DEV_DESC ;
2024-03-04 15:11:43 -05:00
p_enum_driver - > single_thread . dev_uid = uid ;
p_enum_driver - > single_thread . dev_hdl = dev_hdl ;
p_enum_driver - > single_thread . parent_dev_hdl = dev_info . parent . dev_hdl ;
p_enum_driver - > single_thread . parent_dev_addr = parent_dev_addr ;
p_enum_driver - > single_thread . parent_port_num = dev_info . parent . port_num ;
// Save device handle to the URB transfer context
p_enum_driver - > constant . urb - > transfer . context = ( void * ) dev_hdl ;
// Device params
memset ( & p_enum_driver - > single_thread . dev_params , 0 , sizeof ( enum_device_params_t ) ) ;
p_enum_driver - > single_thread . dev_params . bMaxPacketSize0 = ( dev_info . speed = = USB_SPEED_LOW )
? ENUM_WORST_CASE_MPS_LS
: ENUM_WORST_CASE_MPS_FS_HS ;
// Notify USB Host about starting enumeration process
enum_event_data_t event_data = {
. event = ENUM_EVENT_STARTED ,
. started = {
. uid = uid ,
. parent_dev_hdl = dev_info . parent . dev_hdl ,
. parent_port_num = dev_info . parent . port_num ,
} ,
} ;
p_enum_driver - > constant . enum_event_cb ( & event_data , p_enum_driver - > constant . enum_event_cb_arg ) ;
// Request processing
p_enum_driver - > constant . proc_req_cb ( USB_PROC_REQ_SOURCE_ENUM , false , p_enum_driver - > constant . proc_req_cb_arg ) ;
return ret ;
}
esp_err_t enum_proceed ( unsigned int uid )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
2024-03-04 15:11:43 -05:00
// Request processing
p_enum_driver - > constant . proc_req_cb ( USB_PROC_REQ_SOURCE_ENUM , false , p_enum_driver - > constant . proc_req_cb_arg ) ;
return ESP_OK ;
}
esp_err_t enum_cancel ( unsigned int uid )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
enum_stage_t old_stage = p_enum_driver - > single_thread . stage ;
if ( old_stage = = ENUM_STAGE_IDLE | |
old_stage = = ENUM_STAGE_CANCEL ) {
// Nothing to cancel
return ESP_OK ;
}
2024-03-04 15:11:43 -05:00
2024-07-18 04:00:07 -04:00
p_enum_driver - > single_thread . stage = ENUM_STAGE_CANCEL ;
2024-03-04 15:11:43 -05:00
ESP_LOGV ( ENUM_TAG , " [%d:%d] Cancel at %s " ,
p_enum_driver - > single_thread . parent_dev_addr ,
p_enum_driver - > single_thread . parent_port_num ,
2024-07-18 04:00:07 -04:00
enum_stage_strings [ old_stage ] ) ;
if ( stage_need_process ( old_stage ) ) {
// These stages are required to trigger processing in the enum_process()
// This means, that there is no ongoing transfer and we can release the
// device from enumeration immediately
usb_device_handle_t dev_hdl = p_enum_driver - > single_thread . dev_hdl ;
if ( dev_hdl ) {
// Close the device
ESP_ERROR_CHECK ( usbh_dev_enum_unlock ( dev_hdl ) ) ;
ESP_ERROR_CHECK ( usbh_dev_close ( dev_hdl ) ) ;
p_enum_driver - > single_thread . dev_hdl = NULL ;
}
}
// SECOND_RESET_COMPLETE is the exceptional stage, as it awaits the notification after port reset completion via the enum_proceed() call.
// Meanwhile, the device could be detached during the reset, thus the device disconnect comes instead of reset completion.
if ( old_stage = = ENUM_STAGE_SECOND_RESET_COMPLETE ) {
p_enum_driver - > constant . proc_req_cb ( USB_PROC_REQ_SOURCE_ENUM , false , p_enum_driver - > constant . proc_req_cb_arg ) ;
}
2024-03-04 15:11:43 -05:00
return ESP_OK ;
}
esp_err_t enum_process ( void )
{
2024-07-18 04:00:07 -04:00
ENUM_CHECK ( p_enum_driver ! = NULL , ESP_ERR_INVALID_STATE ) ;
2024-03-04 15:11:43 -05:00
2024-06-05 09:15:51 -04:00
esp_err_t res = ESP_FAIL ;
2024-07-18 04:00:07 -04:00
enum_stage_t stage = p_enum_driver - > single_thread . stage ;
2024-03-04 15:11:43 -05:00
switch ( stage ) {
// Transfer submission stages
case ENUM_STAGE_GET_SHORT_DEV_DESC :
case ENUM_STAGE_SET_ADDR :
case ENUM_STAGE_GET_FULL_DEV_DESC :
case ENUM_STAGE_GET_SHORT_CONFIG_DESC :
case ENUM_STAGE_GET_FULL_CONFIG_DESC :
case ENUM_STAGE_SET_CONFIG :
case ENUM_STAGE_GET_SHORT_LANGID_TABLE :
case ENUM_STAGE_GET_FULL_LANGID_TABLE :
case ENUM_STAGE_GET_SHORT_MANU_STR_DESC :
case ENUM_STAGE_GET_FULL_MANU_STR_DESC :
case ENUM_STAGE_GET_SHORT_PROD_STR_DESC :
case ENUM_STAGE_GET_FULL_PROD_STR_DESC :
case ENUM_STAGE_GET_SHORT_SER_STR_DESC :
case ENUM_STAGE_GET_FULL_SER_STR_DESC :
2024-07-18 04:00:07 -04:00
res = control_request ( stage ) ;
2024-03-04 15:11:43 -05:00
break ;
// Recovery interval
case ENUM_STAGE_SET_ADDR_RECOVERY :
// Need a short delay before device is ready. Todo: IDF-7007
vTaskDelay ( pdMS_TO_TICKS ( SET_ADDR_RECOVERY_INTERVAL_MS ) ) ;
2024-06-05 09:15:51 -04:00
res = ESP_OK ;
2024-03-04 15:11:43 -05:00
break ;
// Transfer check stages
case ENUM_STAGE_CHECK_SHORT_DEV_DESC :
case ENUM_STAGE_CHECK_ADDR :
case ENUM_STAGE_CHECK_FULL_DEV_DESC :
case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC :
case ENUM_STAGE_CHECK_FULL_CONFIG_DESC :
case ENUM_STAGE_CHECK_CONFIG :
case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE :
case ENUM_STAGE_CHECK_FULL_LANGID_TABLE :
case ENUM_STAGE_CHECK_SHORT_MANU_STR_DESC :
case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_PROD_STR_DESC :
case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC :
case ENUM_STAGE_CHECK_SHORT_SER_STR_DESC :
case ENUM_STAGE_CHECK_FULL_SER_STR_DESC :
2024-07-18 04:00:07 -04:00
res = control_response_handling ( stage ) ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_SELECT_CONFIG :
2024-06-05 09:15:51 -04:00
res = select_active_configuration ( ) ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_SECOND_RESET :
2024-07-18 04:00:07 -04:00
// We need to wait Hub driver to finish port reset
res = second_reset_request ( ) ;
break ;
case ENUM_STAGE_SECOND_RESET_COMPLETE :
// Second reset complete
res = ESP_OK ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_CANCEL :
2024-06-05 09:15:51 -04:00
res = stage_cancel ( ) ;
2024-03-04 15:11:43 -05:00
break ;
case ENUM_STAGE_COMPLETE :
2024-06-05 09:15:51 -04:00
res = stage_complete ( ) ;
2024-03-04 15:11:43 -05:00
break ;
default :
// Should never occur
abort ( ) ;
break ;
}
2024-07-18 04:00:07 -04:00
// Set nest stage of enumeration process, based on the stage pass result
if ( set_next_stage ( res = = ESP_OK ) ) {
2024-03-04 15:11:43 -05:00
p_enum_driver - > constant . proc_req_cb ( USB_PROC_REQ_SOURCE_ENUM , false , p_enum_driver - > constant . proc_req_cb_arg ) ;
}
2024-06-05 09:15:51 -04:00
return ESP_OK ;
2024-03-04 15:11:43 -05:00
}