diff --git a/components/hal/include/hal/usbh_hal.h b/components/hal/include/hal/usbh_hal.h index 6360f7b3f3..5326deb2dc 100644 --- a/components/hal/include/hal/usbh_hal.h +++ b/components/hal/include/hal/usbh_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,7 +18,6 @@ NOTE: Thread safety is the responsibility fo the HAL user. All USB Host HAL #include #include #include "soc/usbh_struct.h" -#include "soc/usb_wrap_struct.h" #include "hal/usbh_ll.h" #include "hal/usb_types_private.h" #include "hal/assert.h" @@ -152,7 +151,6 @@ typedef struct { typedef struct { //Context usbh_dev_t *dev; /**< Pointer to base address of DWC_OTG registers */ - usb_wrap_dev_t *wrap_dev; /**< Pointer to base address of USB Wrapper registers */ //Host Port related uint32_t *periodic_frame_list; /**< Pointer to scheduling frame list */ usb_hal_frame_list_len_t frame_list_len; /**< Length of the periodic scheduling frame list */ @@ -181,6 +179,7 @@ typedef struct { * * Entry: * - The peripheral must have been reset and clock un-gated + * - The USB PHY (internal or external) and associated GPIOs must already be configured * - GPIO pins configured * - Interrupt allocated but DISABLED (in case of an unknown interupt state) * Exit: @@ -495,7 +494,7 @@ static inline void usbh_hal_disable_debounce_lock(usbh_hal_context_t *hal) hal->flags.dbnc_lock_enabled = 0; //Clear Conenction and disconenction interrupt in case it triggered again usb_ll_intr_clear(hal->dev, USB_LL_INTR_CORE_DISCONNINT); - usbh_ll_hprt_intr_clear(hal->dev, USBH_LL_INTR_HPRT_PRTENCHNG); + usbh_ll_hprt_intr_clear(hal->dev, USBH_LL_INTR_HPRT_PRTCONNDET); //Reenable the hprt (connection) and disconnection interrupts usb_ll_en_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_DISCONNINT); } diff --git a/components/hal/include/hal/usbh_ll.h b/components/hal/include/hal/usbh_ll.h index 63f7b8219d..4320ead0a3 100644 --- a/components/hal/include/hal/usbh_ll.h +++ b/components/hal/include/hal/usbh_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,6 @@ extern "C" { #include #include #include "soc/usbh_struct.h" -#include "soc/usb_wrap_struct.h" #include "hal/usb_types_private.h" #include "hal/misc.h" @@ -153,25 +152,6 @@ typedef struct { uint8_t *buffer; } usbh_ll_dma_qtd_t; -/* ----------------------------------------------------------------------------- ------------------------------- USB Wrap Registers ------------------------------ ------------------------------------------------------------------------------ */ - -/** - * @brief Configures the internal PHY to operate as HOST - * - * @param hw Start address of the USB Wrap registers - */ -static inline void usbh_ll_internal_phy_conf(usb_wrap_dev_t *hw) -{ - //Enable internal PHY - hw->otg_conf.pad_enable = 1; - hw->otg_conf.phy_sel = 0; - //Set pulldowns on D+ and D- - hw->otg_conf.pad_pull_override = 1; - hw->otg_conf.dp_pulldown = 1; - hw->otg_conf.dm_pulldown = 1; -} /* ----------------------------------------------------------------------------- ------------------------------- Global Registers ------------------------------- @@ -431,7 +411,7 @@ static inline void usbh_ll_hcfg_set_fsls_pclk_sel(usbh_dev_t *hw) /** * @brief Sets some default values to HCFG to operate in Host mode with scatter/gather DMA * - * @param hw Start address of the USB Wrap registers + * @param hw Start address of the DWC_OTG registers * @param speed Speed to initialize the host port at */ static inline void usbh_ll_hcfg_set_defaults(usbh_dev_t *hw, usb_priv_speed_t speed) diff --git a/components/hal/usbh_hal.c b/components/hal/usbh_hal.c index a0e038b0e5..b4a10ca5c5 100644 --- a/components/hal/usbh_hal.c +++ b/components/hal/usbh_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -84,7 +84,6 @@ static void set_defaults(usbh_hal_context_t *hal) { - usbh_ll_internal_phy_conf(hal->wrap_dev); //Enable and configure internal PHY //GAHBCFG register usb_ll_en_dma_mode(hal->dev); #ifdef CONFIG_IDF_TARGET_ESP32S2 @@ -114,7 +113,6 @@ void usbh_hal_init(usbh_hal_context_t *hal) //Initialize HAL context memset(hal, 0, sizeof(usbh_hal_context_t)); hal->dev = dev; - hal->wrap_dev = &USB_WRAP; set_defaults(hal); } @@ -125,7 +123,6 @@ void usbh_hal_deinit(usbh_hal_context_t *hal) usb_ll_intr_read_and_clear(hal->dev); //Clear interrupts usb_ll_dis_global_intr(hal->dev); //Disable interrupt signal hal->dev = NULL; - hal->wrap_dev = NULL; } void usbh_hal_core_soft_reset(usbh_hal_context_t *hal) diff --git a/components/usb/hcd.c b/components/usb/hcd.c index d28b943e42..7aa13c7936 100644 --- a/components/usb/hcd.c +++ b/components/usb/hcd.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -194,12 +194,13 @@ typedef struct { uint32_t reserved28: 28; } ctrl; //Control transfer related struct { - uint32_t zero_len_packet: 1; //Bulk transfer should add a zero length packet at the end regardless + uint32_t zero_len_packet: 1; //Added a zero length packet, so transfer consists of 2 QTDs uint32_t reserved31: 31; } bulk; //Bulk transfer related struct { - uint32_t num_qtds: 8; //Number of transfer descriptors filled - uint32_t reserved24: 24; + uint32_t num_qtds: 8; //Number of transfer descriptors filled (excluding zero length packet) + uint32_t zero_len_packet: 1; //Added a zero length packet, so true number descriptors is num_qtds + 1 + uint32_t reserved23: 23; } intr; //Interrupt transfer related struct { uint32_t num_qtds: 8; //Number of transfer descriptors filled (including NULL descriptors) @@ -1025,20 +1026,6 @@ esp_err_t hcd_install(const hcd_config_t *config) goto err; } s_hcd_obj = p_hcd_obj_dmy; - //Set HW prerequisites for each port (there's only one) - periph_module_enable(PERIPH_USB_MODULE); - periph_module_reset(PERIPH_USB_MODULE); - /* - Configure GPIOS for Host mode operation using internal PHY - - Forces ID to GND for A side - - Forces B Valid to GND as we are A side host - - Forces VBUS Valid to HIGH - - Forces A Valid to HIGH - */ - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_IDDIG_IN_IDX, false); - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, false); - esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_AVALID_IN_IDX, false); HCD_EXIT_CRITICAL(); return ESP_OK; @@ -1059,7 +1046,6 @@ esp_err_t hcd_uninstall(void) HCD_EXIT_CRITICAL(); return ESP_ERR_INVALID_STATE; } - periph_module_disable(PERIPH_USB_MODULE); hcd_obj_t *p_hcd_obj_dmy = s_hcd_obj; s_hcd_obj = NULL; HCD_EXIT_CRITICAL(); @@ -2082,8 +2068,8 @@ static inline void _buffer_fill_ctrl(dma_buffer_block_t *buffer, usb_transfer_t //Not data stage. Fill with an empty descriptor usbh_hal_xfer_desc_clear(buffer->xfer_desc_list, 1); } else { - //Fill data stage - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, transfer->data_buffer + sizeof(usb_setup_packet_t), setup_pkt->wLength, + //Fill data stage. Note that we still fill with transfer->num_bytes instead of setup_pkt->wLength as it's possible to require more bytes than wLength + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, transfer->data_buffer + sizeof(usb_setup_packet_t), transfer->num_bytes - sizeof(usb_setup_packet_t), ((data_stg_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0) | USBH_HAL_XFER_DESC_FLAG_HOC); } //Fill status stage (i.e., a zero length packet). If data stage is skipped, the status stage is always IN. @@ -2095,46 +2081,68 @@ static inline void _buffer_fill_ctrl(dma_buffer_block_t *buffer, usb_transfer_t buffer->flags.ctrl.cur_stg = 0; } -static inline void _buffer_fill_bulk(dma_buffer_block_t *buffer, usb_transfer_t *transfer, bool is_in) +static inline void _buffer_fill_bulk(dma_buffer_block_t *buffer, usb_transfer_t *transfer, bool is_in, int mps) { + //Only add a zero length packet if OUT, flag is set, and transfer length is multiple of EP's MPS + //Minor optimization: Do the mod operation last + bool zero_len_packet = !is_in && (transfer->flags & USB_TRANSFER_FLAG_ZERO_PACK) && (transfer->num_bytes % mps == 0); if (is_in) { usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, transfer->num_bytes, USBH_HAL_XFER_DESC_FLAG_IN | USBH_HAL_XFER_DESC_FLAG_HOC); - } else if (transfer->flags & USB_TRANSFER_FLAG_ZERO_PACK) { - //We need to add an extra zero length packet, so two descriptors are used - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, transfer->num_bytes, 0); - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, NULL, 0, USBH_HAL_XFER_DESC_FLAG_HOC); - } else { - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, transfer->num_bytes, USBH_HAL_XFER_DESC_FLAG_HOC); + } else { //OUT + if (zero_len_packet) { + //Adding a zero length packet, so two descriptors are used. + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, transfer->num_bytes, 0); + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 1, NULL, 0, USBH_HAL_XFER_DESC_FLAG_HOC); + } else { + //Zero length packet not required. One descriptor is enough + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, 0, transfer->data_buffer, transfer->num_bytes, USBH_HAL_XFER_DESC_FLAG_HOC); + } } //Update buffer flags - buffer->flags.bulk.zero_len_packet = (is_in && (transfer->flags & USB_TRANSFER_FLAG_ZERO_PACK)) ? 1 : 0; + buffer->flags.bulk.zero_len_packet = zero_len_packet; } static inline void _buffer_fill_intr(dma_buffer_block_t *buffer, usb_transfer_t *transfer, bool is_in, int mps) { int num_qtds; + int mod_mps = transfer->num_bytes % mps; + //Only add a zero length packet if OUT, flag is set, and transfer length is multiple of EP's MPS + bool zero_len_packet = !is_in && (transfer->flags & USB_TRANSFER_FLAG_ZERO_PACK) && (mod_mps == 0); if (is_in) { - assert(transfer->num_bytes % mps == 0); //IN transfers MUST be integer multiple of MPS - num_qtds = transfer->num_bytes / mps; + assert(mod_mps == 0); //IN transfers MUST be integer multiple of MPS + num_qtds = transfer->num_bytes / mps; //Can just floor divide as it's already multiple of MPS } else { - num_qtds = transfer->num_bytes / mps; //Floor division for number of MPS packets - if (transfer->num_bytes % transfer->num_bytes > 0) { - num_qtds++; //For the last shot packet + num_qtds = transfer->num_bytes / mps; //Floor division to get the number of MPS sized packets + if (mod_mps > 0) { + num_qtds++; //Add a short packet for the remainder } } - assert(num_qtds <= XFER_LIST_LEN_INTR); - //Fill all but last descriptor + assert((zero_len_packet) ? num_qtds + 1 : num_qtds <= XFER_LIST_LEN_INTR); //Check that the number of QTDs doesn't exceed the QTD list's length + + uint32_t xfer_desc_flags = (is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0; int bytes_filled = 0; + //Fill all but last QTD for (int i = 0; i < num_qtds - 1; i++) { - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, i, &transfer->data_buffer[bytes_filled], mps, (is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0); + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, i, &transfer->data_buffer[bytes_filled], mps, xfer_desc_flags); bytes_filled += mps; } - //Fill in the last descriptor with HOC flag - usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, num_qtds - 1, &transfer->data_buffer[bytes_filled], transfer->num_bytes - bytes_filled, - ((is_in) ? USBH_HAL_XFER_DESC_FLAG_IN : 0) | USBH_HAL_XFER_DESC_FLAG_HOC); + //Fill last QTD and zero length packet + if (zero_len_packet) { + //Fill in last data packet without HOC flag + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, num_qtds - 1, &transfer->data_buffer[bytes_filled], transfer->num_bytes - bytes_filled, + xfer_desc_flags); + //HOC flag goes to zero length packet instead + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, num_qtds, NULL, 0, USBH_HAL_XFER_DESC_FLAG_HOC); + } else { + //Zero length packet not required. Fill in last QTD with HOC flag + usbh_hal_xfer_desc_fill(buffer->xfer_desc_list, num_qtds - 1, &transfer->data_buffer[bytes_filled], transfer->num_bytes - bytes_filled, + xfer_desc_flags | USBH_HAL_XFER_DESC_FLAG_HOC); + } + //Update buffer members and flags buffer->flags.intr.num_qtds = num_qtds; + buffer->flags.intr.zero_len_packet = zero_len_packet; } static inline void _buffer_fill_isoc(dma_buffer_block_t *buffer, usb_transfer_t *transfer, bool is_in, int mps, int interval, int start_idx) @@ -2220,7 +2228,7 @@ static void _buffer_fill(pipe_t *pipe) break; } case USB_PRIV_XFER_TYPE_BULK: { - _buffer_fill_bulk(buffer_to_fill, transfer, is_in); + _buffer_fill_bulk(buffer_to_fill, transfer, is_in, mps); break; } case USB_PRIV_XFER_TYPE_INTR: { @@ -2269,7 +2277,7 @@ static void _buffer_exec(pipe_t *pipe) } case USB_PRIV_XFER_TYPE_INTR: { start_idx = 0; - desc_list_len = buffer_to_exec->flags.intr.num_qtds; + desc_list_len = (buffer_to_exec->flags.intr.zero_len_packet) ? buffer_to_exec->flags.intr.num_qtds + 1 : buffer_to_exec->flags.intr.num_qtds; break; } default: { @@ -2389,7 +2397,7 @@ static inline void _buffer_parse_intr(dma_buffer_block_t *buffer, bool is_in, in transfer->actual_num_bytes = transfer->num_bytes - last_packet_rem_len; } } else { - //OUT INTR transfers can only complete successfully if all MPS packets have been transmitted. Double check + //OUT INTR transfers can only complete successfully if all packets have been transmitted. Double check for (int i = 0 ; i < buffer->flags.intr.num_qtds; i++) { int rem_len; int desc_status; diff --git a/components/usb/hub.c b/components/usb/hub.c index f970c41b3f..e27dc836d0 100644 --- a/components/usb/hub.c +++ b/components/usb/hub.c @@ -1,11 +1,13 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include +#include +#include #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "esp_err.h" @@ -14,9 +16,10 @@ #include "usb_private.h" #include "hcd.h" #include "hub.h" +#include "usb/usb_helpers.h" /* -Implementation of the HUB driver that only supports the root hub with a single port. Therefore, we currently don't +Implementation of the HUB driver that only supports the Root Hub with a single port. Therefore, we currently don't implement the bare minimum to control the root HCD port. */ @@ -29,43 +32,136 @@ implement the bare minimum to control the root HCD port. #define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED #endif -#define ENUM_CTRL_TRANSFER_MAX_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE +#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 of the first configuration of the device -#define ENUM_DEV_DESC_REQ_SIZE 64 //Worst case size for device descriptor 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 64 //The worst case MPS of EP0 for a FS device #define ENUM_LOW_SPEED_MPS 8 //Worst case MPS for the default endpoint of a low-speed device #define ENUM_FULL_SPEED_MPS 64 //Worst case MPS for the default endpoint of a full-speed device +#define ENUM_LANGID 0x409 //Current enumeration only supports English (United States) string descriptors +//Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive #define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01 -#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x02 +#define HUB_DRIVER_FLAG_ACTION_PORT_DISABLE 0x02 #define HUB_DRIVER_FLAG_ACTION_PORT_RECOVER 0x04 +#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x08 +/** + * @brief Hub driver states + * + * These states represent a Hub driver that only has a single port (the root port) + */ typedef enum { - HUB_DRIVER_STATE_INSTALLED, - HUB_DRIVER_STATE_ROOT_POWERD, - HUB_DRIVER_STATE_ROOT_ENUMERATING, - HUB_DRIVER_STATE_ROOT_ACTIVE, - HUB_DRIVER_STATE_ROOT_RECOVERY, + HUB_DRIVER_STATE_INSTALLED, /**< Hub driver is installed. Root port is not powered */ + HUB_DRIVER_STATE_ROOT_POWERED, /**< Root port is powered, is not connected */ + HUB_DRIVER_STATE_ROOT_ENUM, /**< A device has connected to the root port and is undergoing enumeration */ + HUB_DRIVER_STATE_ROOT_ENUM_FAILED, /**< Enumeration of a connect device has failed. Waiting for that device to disconnect */ + HUB_DRIVER_STATE_ROOT_ACTIVE, /**< The connected device is enumerated */ + HUB_DRIVER_STATE_ROOT_RECOVERY, /**< Root port encountered an error and needs to be recovered */ } hub_driver_state_t; +/** + * @brief Stages of device enumeration listed in their order of execution + * + * - 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_CLEANUP_FAILED 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 + */ typedef enum { - ENUM_STAGE_NONE, /**< There is no device awaiting enumeration */ - ENUM_STAGE_START, /**< Start of enumeration. Allocates device object */ - ENUM_STAGE_GET_SHORT_DEV_DESC, /**< Getting short device descriptor */ - ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK, /**< Check that short device descriptor was obtained */ - ENUM_STAGE_SECOND_RESET, /**< Doing second reset */ - ENUM_STAGE_SET_ADDR, /**< Setting address */ - ENUM_STAGE_SET_ADDR_CHECK, /**< Check that address was set successful */ - ENUM_STAGE_GET_FULL_DEV_DESC, /**< Getting full device descriptor */ - ENUM_STAGE_GET_FULL_DEV_DESC_CHECK, /**< Check that full device descriptor was obtained */ - ENUM_STAGE_GET_CONFIG_DESC, /**< Getting full configuration descriptor */ - ENUM_STAGE_GET_CONFIG_DESC_CHECK, /**< Check that full configuration descriptor was obtained */ - ENUM_STAGE_SET_CONFIG, /**< Set configuration number */ - ENUM_STAGE_SET_CONFIG_CHECK, /**< Check that configuration number was set */ - ENUM_STAGE_CLEANUP, /**< Clean up successful enumeration. Adds enumerated device to USBH */ - ENUM_STAGE_CLEANUP_FAILED, /**< Cleanup failed enumeration. Free device resources */ + ENUM_STAGE_NONE = 0, /**< There is no device awaiting enumeration. Start requires device connection and first reset. */ + ENUM_STAGE_START, /**< A device has connected and has already been reset once. Allocate a device object in USBH */ + //Basic device enumeration + 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). */ + ENUM_STAGE_SET_ADDR, /**< Send SET_ADDRESS request */ + ENUM_STAGE_CHECK_ADDR, /**< Update the enum pipe's target address */ + 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_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 */ + ENUM_STAGE_SET_CONFIG, /**< Send SET_CONFIGURATION request */ + ENUM_STAGE_CHECK_CONFIG, /**< Check that SET_CONFIGURATION request was successful */ + //Get string descriptors + 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 */ + //Cleanup + ENUM_STAGE_CLEANUP, /**< Clean up after successful enumeration. Adds enumerated device to USBH */ + ENUM_STAGE_CLEANUP_FAILED, /**< Cleanup failed enumeration. Free device resources */ } enum_stage_t; +const char *const enum_stage_strings[] = { + "NONE", + "START", + "GET_SHORT_DEV_DESC", + "CHECK_SHORT_DEV_DESC", + "SECOND_RESET", + "SET_ADDR", + "CHECK_ADDR", + "GET_FULL_DEV_DESC", + "CHECK_FULL_DEV_DESC", + "GET_SHORT_CONFIG_DESC", + "CHECK_SHORT_CONFIG_DESC", + "GET_FULL_CONFIG_DESC", + "CHECK_FULL_CONFIG_DESC", + "SET_CONFIG", + "CHECK_CONFIG", + "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", + "CLEANUP", + "CLEANUP_FAILED", +}; + +typedef struct { + //Constant + urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */ + //Initialized at start of a particular enumeration + usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */ + hcd_pipe_handle_t pipe; /**< Default pipe handle of the device being enumerated */ + //Updated during enumeration + enum_stage_t stage; /**< Current enumeration stage */ + int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */ + 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 */ +} enum_ctrl_t; + typedef struct { //Dynamic members require a critical section struct { @@ -77,14 +173,11 @@ typedef struct { uint32_t val; } flags; hub_driver_state_t driver_state; - usb_device_handle_t root_dev_hdl; //Indicates if an enumerated device is connected to the root port } dynamic; //Single thread members don't require a critical section so long as they are never accessed from multiple threads struct { - enum_stage_t enum_stage; - urb_t *enum_urb; - usb_device_handle_t enum_dev_hdl; //Handle of the device being enumerated. Moves to root_dev_hdl on enumeration completion - hcd_pipe_handle_t enum_dflt_pipe_hdl; + usb_device_handle_t root_dev_hdl; //Indicates if an enumerated device is connected to the root port + enum_ctrl_t enum_ctrl; } single_thread; //Constant members do no change after installation thus do not require a critical section struct { @@ -118,6 +211,488 @@ const char *HUB_DRIVER_TAG = "HUB"; } \ }) +// ------------------------------------------------- Forward Declare --------------------------------------------------- + +/** + * @brief HCD port callback for the root port + * + * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process() + * - Under the current HCD implementation, this callback should only be ever be called in an ISR + * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * + * @param port_hdl HCD port handle + * @param port_event HCD port event + * @param user_arg Callback argument + * @param in_isr Whether callback is in an ISR context + * @return Whether a yield is required + */ +static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr); + +/** + * @brief HCD pipe callback for the default pipe of the device under enumeration + * + * - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process() + * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * + * @param pipe_hdl HCD pipe handle + * @param pipe_event Pipe event + * @param user_arg Callback argument + * @param in_isr Whether callback is in an ISR context + * @return Whether a yield is required + */ +static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr); + +/** + * @brief USBH Hub driver request callback + * + * - This callback is called from the context of the USBH, so so any event handling should be deferred to hub_process() + * - This callback needs to call the notification to ensure hub_process() gets a chance to run + * + * @param port_hdl HCD port handle + * @param hub_req Hub driver request + * @param arg Callback argument + */ +static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); + +// ------------------------------------------------- Enum Functions ---------------------------------------------------- + +static bool enum_stage_start(enum_ctrl_t *enum_ctrl) +{ + //Get the speed of the device, and set the enum MPS to the worst case size for now + usb_speed_t speed; + if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { + return false; + } + enum_ctrl->bMaxPacketSize0 = (speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS; + //Try to add the device to USBH + usb_device_handle_t enum_dev_hdl; + hcd_pipe_handle_t enum_dflt_pipe_hdl; + //We use NULL as the parent device to indicate the Root Hub port 1. We currently only support a single device + if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) { + return false; + } + //Set our own default pipe callback + 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; + return true; +} + +static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl) +{ + ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(enum_ctrl->pipe)); //Persist the default pipe through the reset + if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); + return false; + } + return true; +} + +static uint8_t get_string_desc_index(enum_ctrl_t *enum_ctrl) +{ + uint8_t index; + switch (enum_ctrl->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 + break; + case ENUM_STAGE_GET_SHORT_MANU_STR_DESC: + case ENUM_STAGE_GET_FULL_MANU_STR_DESC: + index = enum_ctrl->iManufacturer; + break; + case ENUM_STAGE_GET_SHORT_PROD_STR_DESC: + case ENUM_STAGE_GET_FULL_PROD_STR_DESC: + index = enum_ctrl->iProduct; + break; + case ENUM_STAGE_GET_SHORT_SER_STR_DESC: + case ENUM_STAGE_GET_FULL_SER_STR_DESC: + index = enum_ctrl->iSerialNumber; + break; + default: + //Should not occur + index = 0; + abort(); + break; + } + return index; +} + +static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl) +{ + usb_transfer_t *transfer = &enum_ctrl->urb->transfer; + switch (enum_ctrl->stage) { + 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, 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; + break; + } + case ENUM_STAGE_SET_ADDR: { + USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)transfer->data_buffer, ENUM_DEV_ADDR); + transfer->num_bytes = sizeof(usb_setup_packet_t); //No data stage + enum_ctrl->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), enum_ctrl->bMaxPacketSize0); + //IN data stage should return exactly sizeof(usb_device_desc_t) bytes + enum_ctrl->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 index 0 + USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, 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; + break; + } + 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); + 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; + break; + } + case ENUM_STAGE_SET_CONFIG: { + USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)transfer->data_buffer, ENUM_CONFIG_INDEX + 1); + transfer->num_bytes = sizeof(usb_setup_packet_t); //No data stage + enum_ctrl->expect_num_bytes = 0; //OUT transfer. No need to check number of bytes returned + break; + } + 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: { + uint8_t index = get_string_desc_index(enum_ctrl); + //Get only the header of the string descriptor + USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)transfer->data_buffer, + index, + ENUM_LANGID, + sizeof(usb_str_desc_t)); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(sizeof(usb_str_desc_t), enum_ctrl->bMaxPacketSize0); + //IN data stage should return exactly sizeof(usb_str_desc_t) bytes + enum_ctrl->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: { + uint8_t index = get_string_desc_index(enum_ctrl); + //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, + ENUM_LANGID, + enum_ctrl->str_desc_bLength); + transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(enum_ctrl->str_desc_bLength, enum_ctrl->bMaxPacketSize0); + //IN data stage should return exactly str_desc_bLength bytes + enum_ctrl->expect_num_bytes = sizeof(usb_setup_packet_t) + enum_ctrl->str_desc_bLength; + break; + } + default: //Should never occur + abort(); + break; + } + if (hcd_urb_enqueue(enum_ctrl->pipe, enum_ctrl->urb) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to submit: %s", enum_stage_strings[enum_ctrl->stage]); + return false; + } + return true; +} + +static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl) +{ + //Dequeue the URB + urb_t *dequeued_enum_urb = hcd_urb_dequeue(enum_ctrl->pipe); + assert(dequeued_enum_urb == enum_ctrl->urb); + + //Check transfer status + usb_transfer_t *transfer = &dequeued_enum_urb->transfer; + if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE(HUB_DRIVER_TAG, "Bad transfer status: %s", enum_stage_strings[enum_ctrl->stage]); + return false; + } + //Check IN transfer returned the expected correct number of bytes + if (enum_ctrl->expect_num_bytes != 0 && enum_ctrl->expect_num_bytes != transfer->actual_num_bytes) { + ESP_LOGE(HUB_DRIVER_TAG, "Incorrect number of bytes returned: %s", enum_stage_strings[enum_ctrl->stage]); + return false; + } + + //Stage specific checks and updates + bool ret; + switch (enum_ctrl->stage) { + case ENUM_STAGE_CHECK_SHORT_DEV_DESC: { + const usb_device_desc_t *device_desc = (usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + //Check if the returned descriptor is corrupted + if (device_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_DEVICE) { + ESP_LOGE(HUB_DRIVER_TAG, "Short dev desc corrupt"); + ret = false; + break; + } + //Update and save the MPS of the default pipe + if (hcd_pipe_update_mps(enum_ctrl->pipe, device_desc->bMaxPacketSize0) != ESP_OK) { + ESP_LOGE(HUB_DRIVER_TAG, "Failed to update MPS"); + ret = false; + break; + } + //Save the actual MPS of EP0 + enum_ctrl->bMaxPacketSize0 = device_desc->bMaxPacketSize0; + ret = true; + break; + } + case ENUM_STAGE_CHECK_ADDR: { + //Update the pipe and device's address, and fill the address into the device object + ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(enum_ctrl->pipe, ENUM_DEV_ADDR)); + ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR)); + ret = true; + break; + } + case ENUM_STAGE_CHECK_FULL_DEV_DESC: { + //Fill device descriptor into the device object + const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(enum_ctrl->dev_hdl, device_desc)); + enum_ctrl->iManufacturer = device_desc->iManufacturer; + enum_ctrl->iProduct = device_desc->iProduct; + enum_ctrl->iSerialNumber = device_desc->iSerialNumber; + ret = true; + break; + } + case ENUM_STAGE_CHECK_SHORT_CONFIG_DESC: { + const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->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(HUB_DRIVER_TAG, "Short config desc corrupt"); + ret = false; + break; + } +#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(HUB_DRIVER_TAG, "Configuration descriptor larger than control transfer max length"); + ret = false; + break; + } +#endif + //Save the configuration descriptors full length + enum_ctrl->wTotalLength = config_desc->wTotalLength; + ret = true; + break; + } + case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: { + //Fill configuration descriptor into the device object + const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t)); + ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(enum_ctrl->dev_hdl, config_desc)); + ret = true; + break; + } + case ENUM_STAGE_CHECK_CONFIG: { + ret = true; + //Nothing to do + 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: { + 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_LOGW(HUB_DRIVER_TAG, "String desc not supported"); + ret = false; + break; + } else if (str_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_STRING) { + ESP_LOGE(HUB_DRIVER_TAG, "Full string desc corrupt"); + ret = false; + break; + } +#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(HUB_DRIVER_TAG, "String descriptor larger than control transfer max length"); + ret = false; + break; + } +#endif + //Save the descriptors full length + enum_ctrl->str_desc_bLength = str_desc->bLength; + ret = true; + break; + } + case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: + case ENUM_STAGE_CHECK_FULL_MANU_STR_DESC: + case ENUM_STAGE_CHECK_FULL_PROD_STR_DESC: + case ENUM_STAGE_CHECK_FULL_SER_STR_DESC: { + 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_LOGW(HUB_DRIVER_TAG, "String desc not supported"); + ret = false; + break; + } else if (str_desc->bDescriptorType != USB_B_DESCRIPTOR_TYPE_STRING) { + ESP_LOGE(HUB_DRIVER_TAG, "Full string desc corrupt"); + ret = false; + break; + } + if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_LANGID_TABLE) { + //Scan the LANGID table for our target LANGID + bool target_langid_found = false; + 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) { + target_langid_found = true; + break; + } + } + if (!target_langid_found) { + ESP_LOGE(HUB_DRIVER_TAG, "LANGID 0x%x not found", ENUM_LANGID); + } + ret = target_langid_found; + break; + } else { + //Fill the string descriptor into the device object + int select; + if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_MANU_STR_DESC) { + select = 0; + } else if (enum_ctrl->stage == ENUM_STAGE_CHECK_FULL_PROD_STR_DESC) { + select = 1; + } else { //ENUM_STAGE_CHECK_FULL_PROD_STR_DESC + select = 2; + } + ESP_ERROR_CHECK(usbh_hub_enum_fill_str_desc(enum_ctrl->dev_hdl, str_desc, select)); + ret = true; + break; + } + } + default: //Should never occur + ret = false; + abort(); + break; + } + return ret; +} + +static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl) +{ + //We currently only support a single device connected to the root port. Move the device handle from enum to root + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ACTIVE; + HUB_DRIVER_EXIT_CRITICAL(); + p_hub_driver_obj->single_thread.root_dev_hdl = enum_ctrl->dev_hdl; + usb_device_handle_t dev_hdl = enum_ctrl->dev_hdl; + //Clear values in enum_ctrl + enum_ctrl->dev_hdl = NULL; + enum_ctrl->pipe = NULL; + //Update device object after enumeration is done + ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl)); +} + +static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl) +{ + //Enumeration failed. Clear the enum device handle and pipe + if (enum_ctrl->dev_hdl) { + //If enumeration failed due to a port event, we need to Halt, flush, and dequeue enum default pipe in case there + //was an in-flight URB. + ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_HALT)); + ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_FLUSH)); + hcd_urb_dequeue(enum_ctrl->pipe); //This could return NULL if there + ESP_ERROR_CHECK(usbh_hub_enum_failed(enum_ctrl->dev_hdl)); //Free the underlying device object first before recovering the port + } + //Clear values in enum_ctrl + enum_ctrl->dev_hdl = NULL; + enum_ctrl->pipe = NULL; + HUB_DRIVER_ENTER_CRITICAL(); + //Enum could have failed due to a port error. If so, we need to trigger a port recovery + if (p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_ROOT_RECOVERY) { + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; + } else { + //Otherwise, we move to the enum failed state and wait for the device to disconnect + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUM_FAILED; + } + HUB_DRIVER_EXIT_CRITICAL(); +} + +static void enum_set_next_stage(enum_ctrl_t *enum_ctrl, bool last_stage_pass) +{ + //Set next stage + if (last_stage_pass) { + if (enum_ctrl->stage != ENUM_STAGE_NONE && + enum_ctrl->stage != ENUM_STAGE_CLEANUP && + enum_ctrl->stage != ENUM_STAGE_CLEANUP_FAILED) { + enum_ctrl->stage++; //Go to next stage + } else { + enum_ctrl->stage = ENUM_STAGE_NONE; + } + } else { + switch (enum_ctrl->stage) { + case ENUM_STAGE_START: + //Stage failed but clean up not required + enum_ctrl->stage = ENUM_STAGE_NONE; + break; + case ENUM_STAGE_GET_SHORT_LANGID_TABLE: + case ENUM_STAGE_CHECK_SHORT_LANGID_TABLE: + case ENUM_STAGE_GET_FULL_LANGID_TABLE: + case ENUM_STAGE_CHECK_FULL_LANGID_TABLE: + 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: + 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: + 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: + //String descriptor stages are allow to fail. We just don't fetch them and treat enumeration as successful + enum_ctrl->stage = ENUM_STAGE_CLEANUP; + break; + default: + //Enumeration failed. Go to failure clean up + enum_ctrl->stage = ENUM_STAGE_CLEANUP_FAILED; + break; + } + } + + //These stages are not waiting for a callback, so we need to re-trigger the enum event + bool re_trigger; + switch (enum_ctrl->stage) { + case ENUM_STAGE_GET_SHORT_DEV_DESC: + case ENUM_STAGE_SECOND_RESET: + 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: + case ENUM_STAGE_CLEANUP: + case ENUM_STAGE_CLEANUP_FAILED: + re_trigger = true; + break; + default: + re_trigger = false; + break; + } + if (re_trigger) { + HUB_DRIVER_ENTER_CRITICAL(); + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + HUB_DRIVER_EXIT_CRITICAL(); + } +} + // ------------------------------------------------- Event Handling ---------------------------------------------------- // ---------------------- Callbacks ------------------------ @@ -127,13 +702,8 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port HUB_DRIVER_ENTER_CRITICAL_SAFE(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT; HUB_DRIVER_EXIT_CRITICAL_SAFE(); - bool yield; - if (in_isr) { - p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg); - } else { - yield = false; - } - return yield; + assert(in_isr); //Currently, this callback should only ever be called from an ISR context + return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg);; } static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr) @@ -145,35 +715,27 @@ static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg); } -static void usbh_hub_callback(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg) +static void usbh_hub_req_callback(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg) { - //We currently only support the root port + //We currently only support the root port, so the port_hdl should match the root port assert(port_hdl == p_hub_driver_obj->constant.root_port_hdl); + HUB_DRIVER_ENTER_CRITICAL(); - //Any hub_event results in whatever device connected to the root port to no longer be valid. We clear root_dev_hdl here. - usb_device_handle_t dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl; - p_hub_driver_obj->dynamic.root_dev_hdl = NULL; - assert(dev_hdl); - bool call_port_disable = false; - switch (hub_event) { - case USBH_HUB_EVENT_CLEANUP_PORT: - //After USBH has cleaned up gone device. The port can be recovered. + switch (hub_req) { + case USBH_HUB_REQ_PORT_DISABLE: + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_DISABLE; + break; + case USBH_HUB_REQ_PORT_RECOVER: p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; break; - case USBH_HUB_EVENT_DISABLE_PORT: - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; - call_port_disable = true; - break; default: - abort(); //Should never occur + //Should never occur + abort(); break; } HUB_DRIVER_EXIT_CRITICAL(); - if (call_port_disable) { - ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); - hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE); - ESP_ERROR_CHECK(usbh_hub_dev_port_disabled(dev_hdl)); - } + + p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, false, p_hub_driver_obj->constant.notif_cb_arg); } // ---------------------- Handlers ------------------------- @@ -191,9 +753,9 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) //Start enumeration HUB_DRIVER_ENTER_CRITICAL(); p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUMERATING; + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUM; HUB_DRIVER_EXIT_CRITICAL(); - p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_START; + p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START; } else { ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed"); } @@ -202,21 +764,22 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) case HCD_PORT_EVENT_DISCONNECTION: case HCD_PORT_EVENT_ERROR: case HCD_PORT_EVENT_OVERCURRENT: { - usb_device_handle_t dev_hdl = NULL; + bool pass_event_to_usbh = false; HUB_DRIVER_ENTER_CRITICAL(); switch (p_hub_driver_obj->dynamic.driver_state) { - case HUB_DRIVER_STATE_ROOT_POWERD: - //This occurred before enumeration. Therefore, there's no device and we can go straight to port recovery - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + case HUB_DRIVER_STATE_ROOT_POWERED: //This occurred before enumeration + case HUB_DRIVER_STATE_ROOT_ENUM_FAILED: //This occurred after a failed enumeration. + //Therefore, there's no device and we can go straight to port recovery + p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; break; - case HUB_DRIVER_STATE_ROOT_ENUMERATING: + case HUB_DRIVER_STATE_ROOT_ENUM: //This occurred during enumeration. Therefore, we need to recover the failed enumeration p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; - p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_CLEANUP_FAILED; + p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED; break; case HUB_DRIVER_STATE_ROOT_ACTIVE: //There was an enumerated device. We need to indicate to USBH that the device is gone - dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl; + pass_event_to_usbh = true; break; default: abort(); //Should never occur @@ -224,8 +787,9 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) } p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_RECOVERY; HUB_DRIVER_EXIT_CRITICAL(); - if (dev_hdl) { - ESP_ERROR_CHECK(usbh_hub_mark_dev_gone(dev_hdl)); + if (pass_event_to_usbh) { + assert(p_hub_driver_obj->single_thread.root_dev_hdl); + ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_ERROR)); } break; } @@ -235,303 +799,70 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl) } } -// ------------------------------------------------- Enum Functions ---------------------------------------------------- - -static enum_stage_t enum_stage_start(void) -{ - usb_speed_t speed; - if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) { - return ENUM_STAGE_NONE; - } - usb_device_handle_t enum_dev_hdl; - hcd_pipe_handle_t enum_dflt_pipe_hdl; - //We use NULL as the parent device to indicate the root hub port 1. We currently only support a single device - if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) { - return ENUM_STAGE_NONE; - } - //Set our own default pipe callback - ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL)); - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->single_thread.enum_dev_hdl = enum_dev_hdl; - p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = enum_dflt_pipe_hdl; - HUB_DRIVER_EXIT_CRITICAL(); - ESP_LOGD(HUB_DRIVER_TAG, "Enumeration starting"); - return ENUM_STAGE_GET_SHORT_DEV_DESC; -} - -static enum_stage_t enum_stage_get_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer); - enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE; - if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to get short device descriptor"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Getting short device descriptor"); - return ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK; -} - -static enum_stage_t enum_stage_check_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - //Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); - assert(dequeued_enum_urb == enum_urb); - if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor transfer failed"); - return ENUM_STAGE_CLEANUP_FAILED; - } - usb_device_desc_t *device_desc = (usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); - if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + 8) { //Device must return at least 8 bytes in its data stage - ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor too short"); - return ENUM_STAGE_CLEANUP_FAILED; - } - //Update the MPS of the default pipe - if (hcd_pipe_update_mps(pipe_hdl, device_desc->bMaxPacketSize0) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to update default pipe MPS"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Short device descriptor obtained"); - return ENUM_STAGE_SECOND_RESET; -} - -static enum_stage_t enum_stage_second_reset(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(pipe_hdl)); //Persist the default pipe through the reset - if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Second reset done"); - return ENUM_STAGE_SET_ADDR; -} - -static enum_stage_t enum_stage_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_DEV_ADDR); - enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage - if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to set address"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Setting address %d", ENUM_DEV_ADDR); - return ENUM_STAGE_SET_ADDR_CHECK; -} - -static enum_stage_t enum_stage_check_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) -{ - //Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); - assert(dequeued_enum_urb == enum_urb); - if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Set address transfer failed"); - return ENUM_STAGE_CLEANUP_FAILED; - } - //Update the pipe and device's address, and fill the address into the device object - ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(pipe_hdl, ENUM_DEV_ADDR)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(dev_hdl, ENUM_DEV_ADDR)); - ESP_LOGD(HUB_DRIVER_TAG, "Address set successfully"); - return ENUM_STAGE_GET_FULL_DEV_DESC; -} - -static enum_stage_t enum_stage_get_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer); - enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE; - if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to get full device descriptor"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Getting full device descriptor"); - return ENUM_STAGE_GET_FULL_DEV_DESC_CHECK; -} - -static enum_stage_t enum_stage_check_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) -{ - //Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); - assert(dequeued_enum_urb == enum_urb); - if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor transfer failed"); - return ENUM_STAGE_CLEANUP_FAILED; - } - if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t)) { - ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor too short"); - return ENUM_STAGE_CLEANUP_FAILED; - } - //Fill device descriptor into device - const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); - ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(dev_hdl, device_desc)); - ESP_LOGD(HUB_DRIVER_TAG, "Full device descriptor obtained"); - return ENUM_STAGE_GET_CONFIG_DESC; -} - -static enum_stage_t enum_stage_get_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - //Get the configuration descriptor at index 0 - USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX, ENUM_CTRL_TRANSFER_MAX_LEN); - enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN; - if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to get configuration descriptor"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Getting configuration descriptor"); - return ENUM_STAGE_GET_CONFIG_DESC_CHECK; -} - -static enum_stage_t enum_stage_check_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl) -{ - //Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); - assert(dequeued_enum_urb == enum_urb); - if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor transfer failed"); - return ENUM_STAGE_CLEANUP_FAILED; - } - usb_config_desc_t *config_desc = (usb_config_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t)); - if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_config_desc_t)) { - ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor too small"); - return ENUM_STAGE_CLEANUP_FAILED; - } - if (config_desc->wTotalLength > ENUM_CTRL_TRANSFER_MAX_LEN) { - ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor larger than control transfer max length"); - return ENUM_STAGE_CLEANUP_FAILED; - } - //Fill configuration descriptor into device - ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(dev_hdl, config_desc)); - return ENUM_STAGE_SET_CONFIG; -} - -static enum_stage_t enum_stage_set_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX + 1); - enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage - if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) { - ESP_LOGE(HUB_DRIVER_TAG, "Failed to set configuration"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Setting configuration"); - return ENUM_STAGE_SET_CONFIG_CHECK; -} - -static enum_stage_t enum_stage_check_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb) -{ - //Dequeue the URB - urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl); - assert(dequeued_enum_urb == enum_urb); - if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) { - ESP_LOGE(HUB_DRIVER_TAG, "Set configuration transfer failed"); - return ENUM_STAGE_CLEANUP_FAILED; - } - ESP_LOGD(HUB_DRIVER_TAG, "Configuration set successfully"); - return ENUM_STAGE_CLEANUP; -} - -static enum_stage_t enum_stage_cleanup(void) -{ - //We currently only support a single device connected to the root port. Move the device handle from enum to root - HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.root_dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ACTIVE; - HUB_DRIVER_EXIT_CRITICAL(); - usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; - p_hub_driver_obj->single_thread.enum_dev_hdl = NULL; - p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL; - //Update device object after enumeration is done - ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl)); - return ENUM_STAGE_NONE; -} - -static enum_stage_t enum_stage_cleanup_failed(void) -{ - //Enumeration failed. Clear the enum device handle and pipe - usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; - if (p_hub_driver_obj->single_thread.enum_dev_hdl) { - //Halt, flush, and dequeue enum default pipe - ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_HALT)); - ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_FLUSH)); - urb_t *dequeued_enum_urb = hcd_urb_dequeue(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl); - assert(dequeued_enum_urb == p_hub_driver_obj->single_thread.enum_urb); - ESP_ERROR_CHECK(usbh_hub_enum_failed(dev_hdl)); //Free the underlying device object first before recovering the port - } - p_hub_driver_obj->single_thread.enum_dev_hdl = NULL; - p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL; - HUB_DRIVER_ENTER_CRITICAL(); - //Enum could have failed due to a port error. If so, we need to trigger a port recovery - if (p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_ROOT_RECOVERY) { - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER; - } - HUB_DRIVER_EXIT_CRITICAL(); - return ENUM_STAGE_NONE; -} - static void enum_handle_events(void) { - enum_stage_t cur_stage = p_hub_driver_obj->single_thread.enum_stage; - hcd_pipe_handle_t enum_dflt_pipe_hdl = p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl; - urb_t *enum_urb = p_hub_driver_obj->single_thread.enum_urb; - usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl; - enum_stage_t next_stage; - switch (cur_stage) { + bool stage_pass; + enum_ctrl_t *enum_ctrl = &p_hub_driver_obj->single_thread.enum_ctrl; + switch (enum_ctrl->stage) { case ENUM_STAGE_START: - next_stage = enum_stage_start(); - break; - case ENUM_STAGE_GET_SHORT_DEV_DESC: - next_stage = enum_stage_get_short_dev_desc(enum_dflt_pipe_hdl, enum_urb); - break; - case ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK: - next_stage = enum_stage_check_short_dev_desc(enum_dflt_pipe_hdl, enum_urb); + stage_pass = enum_stage_start(enum_ctrl); break; case ENUM_STAGE_SECOND_RESET: - next_stage = enum_stage_second_reset(enum_dflt_pipe_hdl, enum_urb); + stage_pass = enum_stage_second_reset(enum_ctrl); break; + //Transfer submission stages + case ENUM_STAGE_GET_SHORT_DEV_DESC: case ENUM_STAGE_SET_ADDR: - next_stage = enum_stage_set_addr(enum_dflt_pipe_hdl, enum_urb); - break; - case ENUM_STAGE_SET_ADDR_CHECK: - next_stage = enum_stage_check_set_addr(enum_dflt_pipe_hdl, enum_urb, dev_hdl); - break; case ENUM_STAGE_GET_FULL_DEV_DESC: - next_stage = enum_stage_get_full_dev_desc(enum_dflt_pipe_hdl, enum_urb); - break; - case ENUM_STAGE_GET_FULL_DEV_DESC_CHECK: - next_stage = enum_stage_check_full_dev_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl); - break; - case ENUM_STAGE_GET_CONFIG_DESC: - next_stage = enum_stage_get_config_desc(enum_dflt_pipe_hdl, enum_urb); - break; - case ENUM_STAGE_GET_CONFIG_DESC_CHECK: - next_stage = enum_stage_check_config_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl); - break; + case ENUM_STAGE_GET_SHORT_CONFIG_DESC: + case ENUM_STAGE_GET_FULL_CONFIG_DESC: case ENUM_STAGE_SET_CONFIG: - next_stage = enum_stage_set_config(enum_dflt_pipe_hdl, enum_urb); + 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: + stage_pass = enum_stage_transfer(enum_ctrl); break; - case ENUM_STAGE_SET_CONFIG_CHECK: - next_stage = enum_stage_check_config(enum_dflt_pipe_hdl, enum_urb); + //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: + stage_pass = enum_stage_transfer_check(enum_ctrl); break; case ENUM_STAGE_CLEANUP: - next_stage = enum_stage_cleanup(); + enum_stage_cleanup(enum_ctrl); + stage_pass = true; break; case ENUM_STAGE_CLEANUP_FAILED: - next_stage = enum_stage_cleanup_failed(); + enum_stage_cleanup_failed(enum_ctrl); + stage_pass = true; break; default: - //Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup - next_stage = ENUM_STAGE_NONE; + //Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup. + stage_pass = true; break; } - p_hub_driver_obj->single_thread.enum_stage = next_stage; - HUB_DRIVER_ENTER_CRITICAL(); - if (next_stage == ENUM_STAGE_GET_SHORT_DEV_DESC || - next_stage == ENUM_STAGE_SECOND_RESET || - next_stage == ENUM_STAGE_SET_ADDR || - next_stage == ENUM_STAGE_GET_FULL_DEV_DESC || - next_stage == ENUM_STAGE_GET_CONFIG_DESC || - next_stage == ENUM_STAGE_SET_CONFIG || - next_stage == ENUM_STAGE_CLEANUP || - next_stage == ENUM_STAGE_CLEANUP_FAILED) { - p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT; + 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]); } - HUB_DRIVER_EXIT_CRITICAL(); + enum_set_next_stage(enum_ctrl, stage_pass); } // ---------------------------------------------- Hub Driver Functions ------------------------------------------------- @@ -543,7 +874,7 @@ esp_err_t hub_install(hub_config_t *hub_config) HUB_DRIVER_EXIT_CRITICAL(); //Allocate Hub driver object hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT); - urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN, 0, 0); + urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0, 0); if (hub_driver_obj == NULL || enum_urb == NULL) { return ESP_ERR_NO_MEM; } @@ -560,10 +891,10 @@ esp_err_t hub_install(hub_config_t *hub_config) if (ret != ESP_OK) { goto err; } - //Initialize hub driver object + //Initialize Hub driver object hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED; - hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_NONE; - hub_driver_obj->single_thread.enum_urb = enum_urb; + hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE; + hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb; hub_driver_obj->constant.root_port_hdl = port_hdl; hub_driver_obj->constant.notif_cb = hub_config->notif_cb; hub_driver_obj->constant.notif_cb_arg = hub_config->notif_cb_arg; @@ -576,7 +907,7 @@ esp_err_t hub_install(hub_config_t *hub_config) p_hub_driver_obj = hub_driver_obj; HUB_DRIVER_EXIT_CRITICAL(); //Indicate to USBH that the hub is installed - ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_callback, NULL)); + ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_req_callback, NULL)); ret = ESP_OK; return ret; @@ -599,7 +930,7 @@ esp_err_t hub_uninstall(void) ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl)); //Free Hub driver resources - urb_free(hub_driver_obj->single_thread.enum_urb); + urb_free(hub_driver_obj->single_thread.enum_ctrl.urb); heap_caps_free(hub_driver_obj); return ESP_OK; } @@ -615,7 +946,7 @@ esp_err_t hub_root_start(void) ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON); if (ret == ESP_OK) { HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERED; HUB_DRIVER_EXIT_CRITICAL(); } return ret; @@ -645,20 +976,37 @@ esp_err_t hub_process(void) HUB_DRIVER_EXIT_CRITICAL(); while (action_flags) { + /* + Mutually exclude Root event and Port disable: + If a device was waiting for its port to be disabled, and a port error occurs in that time, the root event + handler will send a USBH_HUB_EVENT_PORT_ERROR to the USBH already, thus freeing the device and canceling the + waiting of port disable. + */ if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) { root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl); + } else if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_DISABLE) { + ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port"); + hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE); + ESP_ERROR_CHECK(usbh_hub_pass_event(p_hub_driver_obj->single_thread.root_dev_hdl, USBH_HUB_EVENT_PORT_DISABLED)); + //The root port has been disabled, so the root_dev_hdl is no longer valid + p_hub_driver_obj->single_thread.root_dev_hdl = NULL; } - if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { - enum_handle_events(); - } + if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_RECOVER) { ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port"); ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl)); ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON)); HUB_DRIVER_ENTER_CRITICAL(); - p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD; + p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERED; HUB_DRIVER_EXIT_CRITICAL(); + //USBH requesting a port recovery means the device has already been freed. Clear root_dev_hdl + p_hub_driver_obj->single_thread.root_dev_hdl = NULL; } + + if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) { + enum_handle_events(); + } + HUB_DRIVER_ENTER_CRITICAL(); action_flags = p_hub_driver_obj->dynamic.flags.actions; p_hub_driver_obj->dynamic.flags.actions = 0; diff --git a/components/usb/include/esp_private/usb_phy.h b/components/usb/include/esp_private/usb_phy.h index 5e8f9c8df1..5260e0589e 100644 --- a/components/usb/include/esp_private/usb_phy.h +++ b/components/usb/include/esp_private/usb_phy.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -63,6 +63,8 @@ typedef struct phy_context_t *usb_phy_handle_t; /**< USB PHY context handle * * @brief Initialize a new USB PHY * Configure at least PHY source. * + * This function will enable the OTG Controller + * * @param[in] config USB PHY configurtion struct * @param[out] handle_ret USB PHY context handle * diff --git a/components/usb/include/usb/usb_helpers.h b/components/usb/include/usb/usb_helpers.h index 834d02ae25..955d2fa96d 100644 --- a/components/usb/include/usb/usb_helpers.h +++ b/components/usb/include/usb/usb_helpers.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -111,6 +111,44 @@ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_index(const usb_intf_desc_ */ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_address(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, uint8_t bEndpointAddress, int *offset); +// ----------------------------------------------- Descriptor Printing ------------------------------------------------- + +/** + * @brief Print class specific descriptor callback + * + * Optional callback to be provided to usb_print_config_descriptor() function. + * The callback is called when when a non-standard descriptor is encountered. + * The callback should decode the descriptor as print it. + */ +typedef void (*print_class_descriptor_cb)(const usb_standard_desc_t *); + +/** + * @brief Print device descriptor + * + * @param devc_desc Device descriptor + */ +void usb_print_device_descriptor(const usb_device_desc_t *devc_desc); + +/** + * @brief Print configuration descriptor + * + * - This function prints the full contents of a configuration descriptor (including interface and endpoint descriptors) + * - When a non-standard descriptor is encountered, this function will call the class_specific_cb if it is provided + * + * @param cfg_desc Configuration descriptor + * @param class_specific_cb Class specific descriptor callback. Can be NULL + */ +void usb_print_config_descriptor(const usb_config_desc_t *cfg_desc, print_class_descriptor_cb class_specific_cb); + +/** + * @brief Print a string descriptor + * + * This funciton will only print ASCII characters of the UTF-16 encoded string + * + * @param str_desc String descriptor + */ +void usb_print_string_descriptor(const usb_str_desc_t *str_desc); + // ------------------------------------------------------ Misc --------------------------------------------------------- /** @@ -118,6 +156,8 @@ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_address(const usb_config_d * * This is a convenience function to round up a size/length to an endpoint's MPS (Maximum packet size). This is useful * when calculating transfer or buffer lengths of IN endpoints. + * - If MPS <= 0, this function will return 0 + * - If num_bytes <= 0, this function will return 0 * * @param[in] num_bytes Number of bytes * @param[in] mps MPS @@ -125,31 +165,12 @@ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_address(const usb_config_d */ static inline int usb_round_up_to_mps(int num_bytes, int mps) { - if (num_bytes < 0 || mps < 0) { + if (num_bytes <= 0 || mps <= 0) { //MPS can never be zero return 0; } return ((num_bytes + mps - 1) / mps) * mps; } -/** - * @brief Print class specific descriptor callback - * - * Optional callback to be provided to usb_print_descriptors() function. - * The callback is called when when a non-standard descriptor is encountered. - * The callback should decode the descriptor as print it. - */ - -typedef void (*print_class_descriptor_cb)(const usb_standard_desc_t *); - -/** - * @brief Prints usb descriptors - * - * @param[in] device Handle to device - * @param[in] class_specific_cb Optional callback to print class specific descriptors - * @return esp_err_t - */ -esp_err_t usb_print_descriptors(usb_device_handle_t device, print_class_descriptor_cb class_specific_cb); - #ifdef __cplusplus } #endif diff --git a/components/usb/include/usb/usb_host.h b/components/usb/include/usb/usb_host.h index 8902bbe954..8c6f6f42b1 100644 --- a/components/usb/include/usb/usb_host.h +++ b/components/usb/include/usb/usb_host.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -72,6 +72,16 @@ typedef struct { }; } usb_host_client_event_msg_t; +// ------------------------ Info --------------------------- + +/** + * @brief Current information about the USB Host Library obtained via usb_host_lib_info() + */ +typedef struct { + int num_devices; /**< Current number of connected (and enumerated) devices */ + int num_clients; /**< Current number of registered clients */ +} usb_host_lib_info_t; + // ---------------------- Callbacks ------------------------ /** @@ -91,7 +101,11 @@ typedef void (*usb_host_client_event_cb_t)(const usb_host_client_event_msg_t *ev * Configuration structure of the USB Host Library. Provided in the usb_host_install() function */ typedef struct { - int intr_flags; /**< Interrupt flags for the underlying ISR used by the USB Host stack */ + bool skip_phy_setup; /**< If set, the USB Host Library will not configure the USB PHY thus allowing the user + to manually configure the USB PHY before calling usb_host_install(). Users should + 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_config_t; /** @@ -118,6 +132,9 @@ typedef struct { * - This function should only once to install the USB Host Library * - This function should be called before any other USB Host Library functions are called * + * @note If skip_phy_setup is set in the install configuration, the user is responsible for ensuring that the underlying + * Host Controller is enabled and the USB PHY (internal or external) is already setup before this function is + * called. * @param[in] config USB Host Library configuration * @return esp_err_t */ @@ -131,6 +148,8 @@ esp_err_t usb_host_install(const usb_host_config_t *config); * - All devices must have been freed by calling usb_host_device_free_all() and receiving the * USB_HOST_LIB_EVENT_FLAGS_ALL_FREE event flag * + * @note If skip_phy_setup was set when the Host Library was installed, the user is responsible for disabling the + * underlying Host Controller and USB PHY (internal or external). * @return esp_err_t */ esp_err_t usb_host_uninstall(void); @@ -140,10 +159,11 @@ esp_err_t usb_host_uninstall(void); * * - This function handles all of the USB Host Library's processing and should be called repeatedly in a loop * - Check event_flags_ret to see if an flags are set indicating particular USB Host Library events + * - This function should never be called by multiple threads simultaneously * * @note This function can block * @param[in] timeout_ticks Timeout in ticks to wait for an event to occur - * @param[out] event_flags_ret Event flags that indicate what USB Host Library event occurred + * @param[out] event_flags_ret Event flags that indicate what USB Host Library event occurred. * @return esp_err_t */ esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret); @@ -157,6 +177,14 @@ esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_f */ esp_err_t usb_host_lib_unblock(void); +/** + * @brief Get current information about the USB Host Library + * + * @param[out] info_ret USB Host Library Information + * @return esp_err_t + */ +esp_err_t usb_host_lib_info(usb_host_lib_info_t *info_ret); + // ------------------------------------------------ Client Functions --------------------------------------------------- /** @@ -186,6 +214,7 @@ esp_err_t usb_host_client_deregister(usb_host_client_handle_t client_hdl); * @brief USB Host Library client processing function * * - This function handles all of a client's processing and should be called repeatedly in a loop + * - For a particular client, this function should never be called by multiple threads simultaneously * * @note This function can block * @param[in] client_hdl Client handle @@ -256,8 +285,7 @@ esp_err_t usb_host_device_free_all(void); * * - This function fills an empty list with the address of connected devices * - The Device addresses can then used in usb_host_device_open() - * - If there are more devices than the list_len, this function will only fill - * up to list_len number of devices. + * - If there are more devices than the list_len, this function will only fill up to list_len number of devices. * * @param[in] list_len Length of the empty list * @param[inout] dev_addr_list Empty list to be filled diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index d49b81eb33..b11305900f 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/components/usb/include/usb/usb_types_ch9.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -217,11 +217,11 @@ _Static_assert(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb /** * @brief Initializer for a request to get an string descriptor */ -#define USB_SETUP_PACKET_INIT_GET_STR_DESC(setup_pkt_ptr, string_index, desc_len) ({ \ +#define USB_SETUP_PACKET_INIT_GET_STR_DESC(setup_pkt_ptr, string_index, lang_id, desc_len) ({ \ (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \ (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ (setup_pkt_ptr)->wValue = (USB_W_VALUE_DT_STRING << 8) | ((string_index) & 0xFF); \ - (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wIndex = (lang_id); \ (setup_pkt_ptr)->wLength = (desc_len); \ }) @@ -465,7 +465,7 @@ _Static_assert(sizeof(usb_ep_desc_t) == USB_EP_DESC_SIZE, "Size of usb_ep_desc_t /** * @brief Size of a short USB string descriptor in bytes */ -#define USB_STR_DESC_SIZE 4 +#define USB_STR_DESC_SIZE 2 /** * @brief Structure representing a USB string descriptor @@ -474,7 +474,7 @@ typedef union { struct { uint8_t bLength; /**< Size of the descriptor in bytes */ uint8_t bDescriptorType; /**< STRING Descriptor Type */ - uint16_t wData[1]; /**< UTF-16LE encoded */ + uint16_t wData[]; /**< UTF-16LE encoded */ } USB_DESC_ATTR; uint8_t val[USB_STR_DESC_SIZE]; } usb_str_desc_t; diff --git a/components/usb/include/usb/usb_types_stack.h b/components/usb/include/usb/usb_types_stack.h index 974d01def5..c438c54a7f 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-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,10 +49,13 @@ typedef struct usb_device_handle_s * usb_device_handle_t; * @brief Basic information of an enumerated device */ typedef struct { - usb_speed_t speed; /**< Device's speed */ - uint8_t dev_addr; /**< Device's address */ - uint8_t bMaxPacketSize0; /**< The maximum packet size of the device's default endpoint */ - uint8_t bConfigurationValue; /**< Device's current configuration number */ + usb_speed_t speed; /**< Device's speed */ + uint8_t dev_addr; /**< Device's address */ + uint8_t bMaxPacketSize0; /**< The maximum packet size of the device's default endpoint */ + uint8_t bConfigurationValue; /**< Device's current configuration number */ + const usb_str_desc_t *str_desc_manufacturer; /**< Pointer to Manufacturer string descriptor (can be NULL) */ + const usb_str_desc_t *str_desc_product; /**< Pointer to Product string descriptor (can be NULL) */ + const usb_str_desc_t *str_desc_serial_num; /**< Pointer to Serial Number string descriptor (can be NULL) */ } usb_device_info_t; // ------------------------------------------------ Transfer Related --------------------------------------------------- @@ -135,9 +138,32 @@ struct usb_transfer_s{ usb_transfer_cb_t callback; /**< Transfer callback */ void *context; /**< Context variable for transfer to associate transfer with something */ const int num_isoc_packets; /**< Only relevant to Isochronous. Number of service periods (i.e., intervals) to transfer data buffer over. */ - usb_isoc_packet_desc_t isoc_packet_desc[0]; /**< Descriptors for each Isochronous packet */ + usb_isoc_packet_desc_t isoc_packet_desc[]; /**< Descriptors for each Isochronous packet */ }; +/** + * @brief Terminate Bulk/Interrupt OUT transfer with a zero length packet + * + * OUT transfers normally terminate when the Host has transferred the exact amount of data it needs to the device. + * However, for bulk and interrupt OUT transfers, if the transfer size just happened to be a multiple of MPS, it will be + * impossible to know the boundary between two consecutive transfers to the same endpoint. + * + * Therefore, this flag will cause the transfer to automatically add a zero length packet (ZLP) at the end of the + * transfer if the following conditions are met: + * - The target endpoint is a Bulk/Interrupt OUT endpoint (Host to device) + * - The transfer's length (i.e., transfer.num_bytes) is a multiple of the endpoint's MPS + * + * Otherwise, this flag has no effect. + * + * Users should check whether their target device's class requires a ZLP, as not all Bulk/Interrupt OUT endpoints + * require them. For example: + * - For MSC Bulk Only Transport class, the Host MUST NEVER send a ZLP. Bulk transfer boundaries are determined by the CBW and CSW instead + * - For CDC Ethernet, the Host MUST ALWAYS send a ZLP if a segment (i.e., a transfer) is a multiple of MPS (See 3.3.1 Segment Delineation) + * + * @note See USB2.0 specification 5.7.3 and 5.8.3 for more details + * @note IN transfers normally terminate when the Host as receive the exact amount of data it needs (must be multiple of MPS) + * or the endpoint sends a short packet to the Host + */ #define USB_TRANSFER_FLAG_ZERO_PACK 0x01 /**< (For bulk OUT only). Indicates that a bulk OUT transfers should always terminate with a short packet, even if it means adding an extra zero length packet */ #ifdef __cplusplus diff --git a/components/usb/private_include/hcd.h b/components/usb/private_include/hcd.h index b1e24ae530..de0746461d 100644 --- a/components/usb/private_include/hcd.h +++ b/components/usb/private_include/hcd.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -180,9 +180,10 @@ typedef struct { * @brief Installs the Host Controller Driver * * - Allocates memory and interrupt resources for the HCD and underlying ports - * - Setups up HCD to use internal PHY * * @note This function must be called before any other HCD function is called + * @note Before calling this function, the Host Controller must already be un-clock gated and reset. The USB PHY + * (internal or external, and associated GPIOs) must already be configured. * * @param config HCD configuration * @retval ESP_OK: HCD successfully installed @@ -199,6 +200,9 @@ esp_err_t hcd_install(const hcd_config_t *config); * Before uninstalling the HCD, the following conditions should be met: * - All ports must be uninitialized, all pipes freed * + * @note This function will simply free the resources used by the HCD. The underlying Host Controller and USB PHY will + * not be disabled. + * * @retval ESP_OK: HCD successfully uninstalled * @retval ESP_ERR_INVALID_STATE: HCD is not in the right condition to be uninstalled */ diff --git a/components/usb/private_include/usbh.h b/components/usb/private_include/usbh.h index 6d530f7f49..35f0562130 100644 --- a/components/usb/private_include/usbh.h +++ b/components/usb/private_include/usbh.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -23,14 +23,44 @@ extern "C" { // ----------------------- Events -------------------------- typedef enum { - USBH_EVENT_DEV_NEW, /**< A new device has been enumerated and added to the device pool */ - USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ - USBH_EVENT_DEV_ALL_FREE, /**< All devices have been freed */ + USBH_EVENT_DEV_NEW, /**< A new device has been enumerated and added to the device pool */ + USBH_EVENT_DEV_GONE, /**< A device is gone. Clients should close the device */ + USBH_EVENT_DEV_ALL_FREE, /**< All devices have been freed */ } usbh_event_t; +/** + * @brief Hub driver requests + * + * Various requests of the Hub driver that the USBH can make. + */ typedef enum { - USBH_HUB_EVENT_CLEANUP_PORT, /**< Indicate to the Hub driver that it should clean up the port of a device (occurs after a gone device has been freed) */ - USBH_HUB_EVENT_DISABLE_PORT, /**< Indicate to the Hub driver that it should disable the port of a device (occurs after a device has been freed) */ + USBH_HUB_REQ_PORT_DISABLE, /**< Request that the Hub driver disable a particular port (occurs after a device + has been freed). Hub driver should respond with a USBH_HUB_EVENT_PORT_DISABLED */ + USBH_HUB_REQ_PORT_RECOVER, /**< Request that the Hub driver recovers a particular port (occurs after a gone + device has been freed). */ +} usbh_hub_req_t; + +/** + * @brief Hub driver events for the USBH + * + * These events as passed by the Hub driver to the USBH via usbh_hub_pass_event() + * + * USBH_HUB_EVENT_PORT_ERROR: + * - The port has encountered an error (such as a sudden disconnection). The device connected to that port is no longer valid. + * - The USBH should: + * - Trigger a USBH_EVENT_DEV_GONE + * - Prevent further transfers to the device + * - Trigger the device's cleanup if it is already closed + * - When the last client closes the device via usbh_dev_close(), free the device object and issue a USBH_HUB_REQ_PORT_RECOVER request + * + * USBH_HUB_EVENT_PORT_DISABLED: + * - A previous USBH_HUB_REQ_PORT_DISABLE has completed. + * - The USBH should free the device object + */ +typedef enum { + USBH_HUB_EVENT_PORT_ERROR, /**< The port has encountered an error (such as a sudden disconnection). The device + connected to that port should be marked gone. */ + USBH_HUB_EVENT_PORT_DISABLED, /**< Previous USBH_HUB_REQ_PORT_DISABLE request completed */ } usbh_hub_event_t; // ---------------------- Callbacks ------------------------ @@ -51,9 +81,11 @@ typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_e /** * @brief Callback used by the USBH to request actions from the Hub driver - * @note This callback is called from within usbh_process() + * + * The Hub Request Callback allows the USBH to request the Hub actions on a particular port. Conversely, the Hub driver + * will indicate completion of some of these requests to the USBH via the usbh_hub_event() funtion. */ -typedef void (*usbh_hub_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg); +typedef void (*usbh_hub_req_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_req_t hub_req, void *arg); // ----------------------- Objects ------------------------- @@ -88,6 +120,8 @@ typedef struct { * - This function will internally install the HCD * - This must be called before calling any Hub driver functions * + * @note Before calling this function, the Host Controller must already be un-clock gated and reset. The USB PHY + * (internal or external, and associated GPIOs) must already be configured. * @param usbh_config USBH driver configuration * @return esp_err_t */ @@ -99,6 +133,8 @@ esp_err_t usbh_install(const usbh_config_t *usbh_config); * - This function will uninstall the HCD * - The Hub driver must be uninstalled before calling this function * + * @note This function will simply free the resources used by the USBH. The underlying Host Controller and USB PHY will + * not be disabled. * @return esp_err_t */ esp_err_t usbh_uninstall(void); @@ -115,6 +151,15 @@ esp_err_t usbh_uninstall(void); */ esp_err_t usbh_process(void); +/** + * @brief Get the current number of devices + * + * @note This function can block + * @param[out] num_devs_ret Current number of devices + * @return esp_err_t + */ +esp_err_t usbh_num_devs(int *num_devs_ret); + // ------------------------------------------------ Device Functions --------------------------------------------------- // --------------------- Device Pool ----------------------- @@ -276,11 +321,11 @@ esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddr * - This should only be called after the USBH has already be installed * * @note Hub Driver only - * @param[in] hub_callback Hub callback + * @param[in] hub_req_callback Hub request callback * @param[in] callback_arg Callback argument * @return esp_err_t */ -esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg); +esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg); /** * @brief Indicates to USBH the start of enumeration for a device @@ -300,49 +345,16 @@ esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg); esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl); /** - * @brief Indicate that a device is gone + * @brief Indicates to the USBH that a hub event has occurred for a particular device * - * This Hub driver must call this function to indicate that a device is gone. A device is gone when: - * - It suddenly disconnects - * - Its upstream port or device has an error or is also gone. - * Marking a device as gone will: - * - Trigger a USBH_EVENT_DEV_GONE - * - Prevent further transfers to the device - * - Trigger the device's cleanup if it is already closed - * - When the last client closes the device via usbh_dev_close(), the device's resources will be cleaned up - * - * @note Hub Driver only - * @param[in] dev_hdl Device handle + * @param dev_hdl Device handle + * @param hub_event Hub event * @return esp_err_t */ -esp_err_t usbh_hub_mark_dev_gone(usb_device_handle_t dev_hdl); - -/** - * @brief Indicate that a device's port has been disabled - * - * - The Hub driver must call this function once it has disabled the port of a particular device - * - The Hub driver disables a device's port when requested by the USBH via the USBH_HUB_EVENT_DISABLE_PORT - * - This function will trigger the device's cleanup. - * - * @note Hub Driver only - * @param[in] dev_hdl Device handle - * @return esp_err_t - */ -esp_err_t usbh_hub_dev_port_disabled(usb_device_handle_t dev_hdl); +esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event); // ----------------- Enumeration Related ------------------- -/** - * @brief Fill the enumerating device's descriptor - * - * @note Hub Driver only - * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param device_desc - * @return esp_err_t - */ -esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); - /** * @brief Assign the enumerating device's address * @@ -354,6 +366,17 @@ esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_dev */ esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr); +/** + * @brief Fill the enumerating device's descriptor + * + * @note Hub Driver only + * @note Must call in sequence + * @param[in] dev_hdl Device handle + * @param device_desc + * @return esp_err_t + */ +esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc); + /** * @brief Fill the enumerating device's active configuration descriptor * @@ -367,15 +390,16 @@ esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_a esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full); /** - * @brief Assign the enumerating device's active configuration number + * @brief Fill one of the string descriptors of the enumerating device * * @note Hub Driver only * @note Must call in sequence - * @param[in] dev_hdl Device handle - * @param bConfigurationValue + * @param dev_hdl Device handle + * @param str_desc Pointer to string descriptor + * @param select Select which string descriptor. 0/1/2 for Manufacturer/Product/Serial Number string descriptors respecitvely * @return esp_err_t */ -esp_err_t usbh_hub_enum_fill_config_num(usb_device_handle_t dev_hdl, uint8_t bConfigurationValue); +esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select); /** * @brief Indicate the device enumeration is completed diff --git a/components/usb/test/common/test_usb_common.c b/components/usb/test/common/test_usb_common.c index 61f997a98a..c9855ace11 100644 --- a/components/usb/test/common/test_usb_common.c +++ b/components/usb/test/common/test_usb_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,27 +7,38 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "soc/usb_wrap_struct.h" +#include "esp_err.h" +#include "hal/usb_phy_types.h" +#include "esp_private/usb_phy.h" #include "test_usb_common.h" -void test_usb_force_conn_state(bool connected, TickType_t delay_ticks) +static usb_phy_handle_t phy_hdl = NULL; + +void test_usb_init_phy(void) +{ + //Initialize the internal USB PHY to connect to the USB OTG peripheral + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + .gpio_conf = NULL, + }; + ESP_ERROR_CHECK(usb_new_phy(&phy_config, &phy_hdl)); +} + +void test_usb_deinit_phy(void) +{ + //Deinitialize the internal USB PHY + ESP_ERROR_CHECK(usb_del_phy(phy_hdl)); + phy_hdl = NULL; +} + +void test_usb_set_phy_state(bool connected, TickType_t delay_ticks) { if (delay_ticks > 0) { //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. vTaskDelay(delay_ticks); } - usb_wrap_dev_t *wrap = &USB_WRAP; - if (connected) { - //Disable test mode to return to previous internal PHY configuration - wrap->test_conf.test_enable = 0; - } else { - /* - Mimic a disconnection by using the internal PHY's test mode. - Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0, - this will look like a disconnection. - */ - wrap->test_conf.val = 0; - wrap->test_conf.test_usb_wrap_oe = 1; - wrap->test_conf.test_enable = 1; - } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); } diff --git a/components/usb/test/common/test_usb_common.h b/components/usb/test/common/test_usb_common.h index fc8a8379d3..ac88f3432e 100644 --- a/components/usb/test/common/test_usb_common.h +++ b/components/usb/test/common/test_usb_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,10 +7,20 @@ #include #include "freertos/FreeRTOS.h" +/** + * @brief Initialize the internal USB PHY and USB Controller for USB Host testing + */ +void test_usb_init_phy(void); + +/** + * @brief Deinitalize the internal USB PHY and USB Controller after USB Host testing + */ +void test_usb_deinit_phy(void); + /** * @brief For the USB PHY into the connected or disconnected state * * @param connected For into connected state if true, disconnected if false * @param delay_ticks Delay in ticks before forcing state */ -void test_usb_force_conn_state(bool connected, TickType_t delay_ticks); +void test_usb_set_phy_state(bool connected, TickType_t delay_ticks); diff --git a/components/usb/test/common/test_usb_mock_classes.c b/components/usb/test/common/test_usb_mock_classes.c index 0e819281de..7a626cfbcd 100644 --- a/components/usb/test/common/test_usb_mock_classes.c +++ b/components/usb/test/common/test_usb_mock_classes.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,6 +14,49 @@ const char *MSC_CLIENT_TAG = "MSC Client"; +const uint8_t mock_msc_scsi_dev_desc[] = { + 0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x5F, 0x12, 0x8A, 0xC0, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01, +}; + +const uint8_t mock_msc_scsi_config_desc[] = { + 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0xF0, 0x09, 0x04, 0x00, 0x00, 0x02, 0x08, 0x06, 0x50, 0x00, 0x07, + 0x05, 0x01, 0x02, 0x40, 0x00, 0x01, 0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x01, +}; + + +const uint8_t mock_msc_scsi_str_desc_manu[] = { + 0x0c, 0x03, 0x41, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00, +}; + +const uint8_t mock_msc_scsi_str_desc_prod[] = { + 0x2c, 0x03, 0x41, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00, 0x20, 0x00, 0x55, 0x00, 0x53, 0x00, 0x42, + 0x00, 0x20, 0x00, 0x46, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x73, 0x00, 0x68, 0x00, 0x20, 0x00, 0x44, 0x00, 0x72, 0x00, + 0x69, 0x00, 0x76, 0x00, 0x65, 0x00, +}; + +const uint8_t mock_msc_scsi_str_desc_ser_num[] = { + 0x22, 0x03, 0x31, 0x00, 0x33, 0x00, 0x43, 0x00, 0x32, 0x00, 0x38, 0x00, 0x31, 0x00, 0x36, 0x00, 0x35, 0x00, 0x38, + 0x00, 0x32, 0x00, 0x31, 0x00, 0x38, 0x00, 0x30, 0x00, 0x30, 0x00, 0x38, 0x00, 0x45, 0x00, +}; + +const usb_ep_desc_t mock_msc_scsi_bulk_out_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), + .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, + .bEndpointAddress = 0x01, //EP 1 OUT + .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, + .wMaxPacketSize = 64, //MPS of 64 bytes + .bInterval = 1, +}; + +const usb_ep_desc_t mock_msc_scsi_bulk_in_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), + .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, + .bEndpointAddress = 0x82, //EP 2 IN + .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, + .wMaxPacketSize = 64, //MPS of 64 bytes + .bInterval = 1, +}; + void mock_msc_scsi_init_cbw(mock_msc_bulk_cbw_t *cbw, bool is_read, int offset, int num_sectors, uint32_t tag) { cbw->dCBWSignature = 0x43425355; //Fixed value @@ -60,6 +103,15 @@ bool mock_msc_scsi_check_csw(mock_msc_bulk_csw_t *csw, uint32_t tag_expect) // ---------------------------------------------------- HID Mouse ------------------------------------------------------ +const usb_ep_desc_t mock_hid_mouse_in_ep_desc = { + .bLength = sizeof(usb_ep_desc_t), + .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, + .bEndpointAddress = 0x81, //EP 1 IN + .bmAttributes = USB_BM_ATTRIBUTES_XFER_INT, + .wMaxPacketSize = 4, //MPS of 4 bytes + .bInterval = 10, //Interval of 10ms +}; + void mock_hid_process_report(mock_hid_mouse_report_t *report, int iter) { static int x_pos = 0; diff --git a/components/usb/test/common/test_usb_mock_classes.h b/components/usb/test/common/test_usb_mock_classes.h index ef39aff127..b225a37517 100644 --- a/components/usb/test/common/test_usb_mock_classes.h +++ b/components/usb/test/common/test_usb_mock_classes.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -88,23 +88,14 @@ Configuration Descriptor: If you're using a flash driver with different endpoints, modify the endpoint descriptors below. */ -static const usb_ep_desc_t mock_msc_scsi_bulk_out_ep_desc = { - .bLength = sizeof(usb_ep_desc_t), - .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, - .bEndpointAddress = 0x01, //EP 1 OUT - .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, - .wMaxPacketSize = 64, //MPS of 64 bytes - .bInterval = 1, -}; - -static const usb_ep_desc_t mock_msc_scsi_bulk_in_ep_desc = { - .bLength = sizeof(usb_ep_desc_t), - .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, - .bEndpointAddress = 0x82, //EP 2 IN - .bmAttributes = USB_BM_ATTRIBUTES_XFER_BULK, - .wMaxPacketSize = 64, //MPS of 64 bytes - .bInterval = 1, -}; +//Constant descriptors +extern const uint8_t mock_msc_scsi_dev_desc[]; +extern const uint8_t mock_msc_scsi_config_desc[]; +extern const uint8_t mock_msc_scsi_str_desc_manu[]; +extern const uint8_t mock_msc_scsi_str_desc_prod[]; +extern const uint8_t mock_msc_scsi_str_desc_ser_num[]; +extern const usb_ep_desc_t mock_msc_scsi_bulk_out_ep_desc; +extern const usb_ep_desc_t mock_msc_scsi_bulk_in_ep_desc; #define MOCK_MSC_SCSI_DEV_ID_VENDOR 0x125F #define MOCK_MSC_SCSI_DEV_ID_PRODUCT 0xc08A @@ -246,14 +237,8 @@ Device Descriptor: If you're using another mice with different endpoints, modify the endpoint descriptor below */ -static const usb_ep_desc_t mock_hid_mouse_in_ep_desc = { - .bLength = sizeof(usb_ep_desc_t), - .bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT, - .bEndpointAddress = 0x81, //EP 1 IN - .bmAttributes = USB_BM_ATTRIBUTES_XFER_INT, - .wMaxPacketSize = 4, //MPS of 4 bytes - .bInterval = 10, //Interval of 10ms -}; + +extern const usb_ep_desc_t mock_hid_mouse_in_ep_desc; #define MOCK_HID_MOUSE_DEV_ID_VENDOR 0x413C #define MOCK_HID_MOUSE_DEV_ID_PRODUCT 0x301A diff --git a/components/usb/test/hcd/test_hcd_common.c b/components/usb/test/hcd/test_hcd_common.c index 9116ffa72d..3741289dd8 100644 --- a/components/usb/test/hcd/test_hcd_common.c +++ b/components/usb/test/hcd/test_hcd_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -138,6 +138,7 @@ int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl) hcd_port_handle_t test_hcd_setup(void) { + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing //Create a queue for port callback to queue up port events QueueHandle_t port_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(port_event_msg_t)); TEST_ASSERT_NOT_EQUAL(NULL, port_evt_queue); @@ -157,7 +158,7 @@ hcd_port_handle_t test_hcd_setup(void) TEST_ASSERT_EQUAL(ESP_OK, hcd_port_init(PORT_NUM, &port_config, &port_hdl)); TEST_ASSERT_NOT_EQUAL(NULL, port_hdl); TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl)); - test_usb_force_conn_state(false, 0); //Force disconnected state on PHY + test_usb_set_phy_state(false, 0); //Force disconnected state on PHY return port_hdl; } @@ -171,6 +172,7 @@ void test_hcd_teardown(hcd_port_handle_t port_hdl) //Uninstall the HCD TEST_ASSERT_EQUAL(ESP_OK, hcd_uninstall()); vQueueDelete(port_evt_queue); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing } usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl) @@ -180,7 +182,7 @@ usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl) TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISCONNECTED, hcd_port_get_state(port_hdl)); //Wait for connection event printf("Waiting for connection\n"); - test_usb_force_conn_state(true, pdMS_TO_TICKS(100)); //Allow for connected state on PHY + test_usb_set_phy_state(true, pdMS_TO_TICKS(100)); //Allow for connected state on PHY test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_CONNECTION); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_CONNECTION, hcd_port_handle_event(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl)); @@ -209,7 +211,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled } //Wait for a safe disconnect printf("Waiting for disconnection\n"); - test_usb_force_conn_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY + test_usb_set_phy_state(false, pdMS_TO_TICKS(100)); //Force disconnected state on PHY test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); diff --git a/components/usb/test/hcd/test_hcd_common.h b/components/usb/test/hcd/test_hcd_common.h index a41c9fe451..282e733528 100644 --- a/components/usb/test/hcd/test_hcd_common.h +++ b/components/usb/test/hcd/test_hcd_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -65,7 +65,7 @@ void test_hcd_teardown(hcd_port_handle_t port_hdl); /** * @brief Wait for a connection on an HCD port * - * @note This function will internally call test_usb_force_conn_state() to allow for a connection + * @note This function will internally call test_usb_set_phy_state() to allow for a connection * * @param port_hdl Port handle * @return usb_speed_t Speed of the connected device @@ -75,7 +75,7 @@ usb_speed_t test_hcd_wait_for_conn(hcd_port_handle_t port_hdl); /** * @brief Wait for a disconnection on an HCD port * - * @note This fucntion will internally call test_usb_force_conn_state() to force a disconnection + * @note This fucntion will internally call test_usb_set_phy_state() to force a disconnection * * @param port_hdl Port handle * @param already_disabled Whether the HCD port is already in the disabled state diff --git a/components/usb/test/hcd/test_hcd_isoc.c b/components/usb/test/hcd/test_hcd_isoc.c index 7358ff979e..67b6d8f997 100644 --- a/components/usb/test/hcd/test_hcd_isoc.c +++ b/components/usb/test/hcd/test_hcd_isoc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -155,7 +155,7 @@ TEST_CASE("Test HCD isochronous pipe sudden disconnect", "[hcd][ignore]") } //Add a short delay to let the transfers run for a bit esp_rom_delay_us(POST_ENQUEUE_DELAY_US); - test_usb_force_conn_state(false, 0); + test_usb_set_phy_state(false, 0); //Disconnect event should have occurred. Handle the port event test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); diff --git a/components/usb/test/hcd/test_hcd_port.c b/components/usb/test/hcd/test_hcd_port.c index d3e2678160..c79c3047d7 100644 --- a/components/usb/test/hcd/test_hcd_port.c +++ b/components/usb/test/hcd/test_hcd_port.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -66,7 +66,7 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]") } //Add a short delay to let the transfers run for a bit esp_rom_delay_us(POST_ENQUEUE_DELAY_US); - test_usb_force_conn_state(false, 0); + test_usb_set_phy_state(false, 0); //Disconnect event should have occurred. Handle the port event test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); @@ -118,20 +118,19 @@ Test port suspend and resume with active pipes Purpose: - Test port suspend and resume procedure - - When suspending, the pipes should be halted before suspending the port. Any pending transfers should remain pending + - When suspending, the pipes should be halted before suspending the port - When resuming, the pipes should remain in the halted state - - Pipes on being cleared of the halt should resume transferring the pending transfers Procedure: - Setup the HCD and a port - Trigger a port connection - Create a default pipe - - Start transfers + - Test that port can't be suspended with an active pipe - Halt the default pipe after a short delay - Suspend the port - Resume the port - - Check that all the URBs have either completed successfully or been canceled by the pipe halt - - Cleanup URBs and default pipe + - Check that all the pipe is still halted + - Cleanup default pipe - Trigger disconnection and teardown */ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") @@ -142,26 +141,15 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") //Allocate some URBs and initialize their data buffers with control transfers hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, TEST_DEV_ADDR, port_speed); //Create a default pipe (using a NULL EP descriptor) - urb_t *urb_list[NUM_URBS]; - for (int i = 0; i < NUM_URBS; i++) { - urb_list[i] = test_hcd_alloc_urb(0, URB_DATA_BUFF_SIZE); - //Initialize with a "Get Config Descriptor request" - urb_list[i]->transfer.num_bytes = sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES; - USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES); - urb_list[i]->transfer.context = (void *)0xDEADBEEF; - } - //Enqueue URBs but immediately suspend the port - printf("Enqueuing URBs\n"); - for (int i = 0; i < NUM_URBS; i++) { - TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i])); - } - //Add a short delay to let the transfers run for a bit - esp_rom_delay_us(POST_ENQUEUE_DELAY_US); + //Test that suspending the port now fails as there is an active pipe + TEST_ASSERT_NOT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); + //Halt the default pipe before suspending TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); + //Suspend the port TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_SUSPEND)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_SUSPENDED, hcd_port_get_state(port_hdl)); @@ -172,30 +160,12 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]") TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_ENABLED, hcd_port_get_state(port_hdl)); printf("Resumed\n"); + //Clear the default pipe's halt TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_CLEAR)); TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); vTaskDelay(pdMS_TO_TICKS(100)); //Give some time for resumed URBs to complete - //Dequeue URBs - for (int i = 0; i < NUM_URBS; i++) { - urb_t *urb = hcd_urb_dequeue(default_pipe); - TEST_ASSERT_EQUAL(urb_list[i], urb); - TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); - if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { - //We must have transmitted at least the setup packet, but device may return less than bytes requested - TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); - TEST_ASSERT_LESS_OR_EQUAL(urb->transfer.num_bytes, urb->transfer.actual_num_bytes); - } else { - //A failed transfer should 0 actual number of bytes transmitted - TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes); - } - TEST_ASSERT_EQUAL(0xDEADBEEF, urb->transfer.context); - } - //Free URB list and pipe - for (int i = 0; i < NUM_URBS; i++) { - test_hcd_free_urb(urb_list[i]); - } test_hcd_pipe_free(default_pipe); //Cleanup test_hcd_wait_for_disconn(port_hdl, false); @@ -305,7 +275,7 @@ static void concurrent_task(void *arg) xSemaphoreTake(sync_sem, portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(10)); //Give a short delay let reset command start in main thread //Force a disconnection - test_usb_force_conn_state(false, 0); + test_usb_set_phy_state(false, 0); vTaskDelay(portMAX_DELAY); //Block forever and wait to be deleted } diff --git a/components/usb/test/usb_host/msc_client.h b/components/usb/test/usb_host/msc_client.h index ab2f0f4dd3..c3bf2063f2 100644 --- a/components/usb/test/usb_host/msc_client.h +++ b/components/usb/test/usb_host/msc_client.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,3 +19,5 @@ typedef struct { void msc_client_async_seq_task(void *arg); void msc_client_async_dconn_task(void *arg); + +void msc_client_async_enum_task(void *arg); diff --git a/components/usb/test/usb_host/msc_client_async_dconn.c b/components/usb/test/usb_host/msc_client_async_dconn.c index bbbd9170c2..061b8acce7 100644 --- a/components/usb/test/usb_host/msc_client_async_dconn.c +++ b/components/usb/test/usb_host/msc_client_async_dconn.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -36,6 +36,8 @@ Implementation of an asynchronous MSC client used for USB Host disconnection tes - Deregister MSC client */ +#define TEST_DCONN_ITERATIONS 3 + typedef enum { TEST_STAGE_WAIT_CONN, TEST_STAGE_DEV_OPEN, @@ -155,6 +157,7 @@ void msc_client_async_dconn_task(void *arg) bool exit_loop = false; bool skip_event_handling = false; + int dconn_iter = 0; while (!exit_loop) { if (!skip_event_handling) { TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(msc_obj.client_hdl, portMAX_DELAY)); @@ -166,6 +169,10 @@ void msc_client_async_dconn_task(void *arg) msc_obj.cur_stage = msc_obj.next_stage; switch (msc_obj.cur_stage) { + case TEST_STAGE_WAIT_CONN: { + //Nothing to do while waiting for connection + break; + } case TEST_STAGE_DEV_OPEN: { ESP_LOGD(MSC_CLIENT_TAG, "Open"); //Open the device @@ -219,7 +226,7 @@ void msc_client_async_dconn_task(void *arg) TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in[i])); } //Trigger a disconnect - test_usb_force_conn_state(false, 0); + test_usb_set_phy_state(false, 0); //Next stage set from transfer callback break; } @@ -227,7 +234,15 @@ void msc_client_async_dconn_task(void *arg) ESP_LOGD(MSC_CLIENT_TAG, "Close"); TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_release(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER)); TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(msc_obj.client_hdl, msc_obj.dev_hdl)); - exit_loop = true; + dconn_iter++; + if (dconn_iter < TEST_DCONN_ITERATIONS) { + //Start the next test iteration by going back to TEST_STAGE_WAIT_CONN and reenabling connections + msc_obj.next_stage = TEST_STAGE_WAIT_CONN; + skip_event_handling = true; //Need to execute TEST_STAGE_WAIT_CONN + test_usb_set_phy_state(true, 0); + } else { + exit_loop = true; + } break; } default: diff --git a/components/usb/test/usb_host/msc_client_async_enum.c b/components/usb/test/usb_host/msc_client_async_enum.c new file mode 100644 index 0000000000..9f0bcb1ec6 --- /dev/null +++ b/components/usb/test/usb_host/msc_client_async_enum.c @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_log.h" +#include "test_usb_mock_classes.h" +#include "test_usb_common.h" +#include "msc_client.h" +#include "usb/usb_host.h" +#include "unity.h" +#include "test_utils.h" + +/* +Implementation of an asynchronous MSC client used for USB Host enumeration test. + +- The MSC client will: + - Register itself as a client + - Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device + - Check the device and configuration descriptor of the device + - Check the device's information + - Close device + - Repeat for multiple iterations from waiting connection by forcing a disconnection + - Deregister MSC client +*/ + +#define TEST_ENUM_ITERATIONS 3 + +typedef enum { + TEST_STAGE_WAIT_CONN, + TEST_STAGE_DEV_OPEN, + TEST_STAGE_CHECK_DEV_DESC, + TEST_STAGE_CHECK_CONFIG_DESC, + TEST_STAGE_CHECK_STR_DESC, + TEST_STAGE_DEV_CLOSE, +} test_stage_t; + +typedef struct { + test_stage_t cur_stage; + test_stage_t next_stage; + uint8_t dev_addr_to_open; + usb_host_client_handle_t client_hdl; + usb_device_handle_t dev_hdl; +} msc_client_obj_t; + +static void msc_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + msc_client_obj_t *msc_obj = (msc_client_obj_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, msc_obj->cur_stage); + msc_obj->next_stage = TEST_STAGE_DEV_OPEN; + msc_obj->dev_addr_to_open = event_msg->new_dev.address; + break; + default: + abort(); //Should never occur in this test + break; + + } +} + +void msc_client_async_enum_task(void *arg) +{ + msc_client_obj_t msc_obj; + msc_obj.cur_stage = TEST_STAGE_WAIT_CONN; + msc_obj.next_stage = TEST_STAGE_WAIT_CONN; + msc_obj.client_hdl = NULL; + msc_obj.dev_addr_to_open = 0; + msc_obj.dev_hdl = NULL; + + //Register client + usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = MSC_ASYNC_CLIENT_MAX_EVENT_MSGS, + .async = { + .client_event_callback = msc_client_event_cb, + .callback_arg = (void *)&msc_obj, + }, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &msc_obj.client_hdl)); + + //Wait to be started by main thread + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + ESP_LOGD(MSC_CLIENT_TAG, "Starting"); + + bool exit_loop = false; + bool skip_event_handling = false; + int enum_iter = 0; + while (!exit_loop) { + if (!skip_event_handling) { + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(msc_obj.client_hdl, portMAX_DELAY)); + } + skip_event_handling = false; + if (msc_obj.cur_stage == msc_obj.next_stage) { + continue; + } + msc_obj.cur_stage = msc_obj.next_stage; + + switch (msc_obj.cur_stage) { + case TEST_STAGE_WAIT_CONN: { + //Wait for connection, nothing to do + break; + } + case TEST_STAGE_DEV_OPEN: { + ESP_LOGD(MSC_CLIENT_TAG, "Open"); + //Open the device + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(msc_obj.client_hdl, msc_obj.dev_addr_to_open, &msc_obj.dev_hdl)); + msc_obj.next_stage = TEST_STAGE_CHECK_DEV_DESC; + skip_event_handling = true; //Need to execute TEST_STAGE_CHECK_DEV_DESC + break; + } + case TEST_STAGE_CHECK_DEV_DESC: { + //Check the device descriptor + const usb_device_desc_t *device_desc; + const usb_device_desc_t *device_desc_ref = (const usb_device_desc_t *)mock_msc_scsi_dev_desc; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(msc_obj.dev_hdl, &device_desc)); + TEST_ASSERT_EQUAL(device_desc_ref->bLength, device_desc->bLength); + TEST_ASSERT_EQUAL(0, memcmp(device_desc_ref, device_desc, device_desc_ref->bLength)); + msc_obj.next_stage = TEST_STAGE_CHECK_CONFIG_DESC; + skip_event_handling = true; //Need to execute TEST_STAGE_CHECK_CONFIG_DESC + break; + } + + case TEST_STAGE_CHECK_CONFIG_DESC: { + //Check the configuration descriptor + const usb_config_desc_t *config_desc; + const usb_config_desc_t *config_desc_ref = (const usb_config_desc_t *)mock_msc_scsi_config_desc; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_active_config_descriptor(msc_obj.dev_hdl, &config_desc)); + TEST_ASSERT_EQUAL(config_desc_ref->wTotalLength, config_desc->wTotalLength); + TEST_ASSERT_EQUAL(0, memcmp(config_desc_ref, config_desc, config_desc_ref->wTotalLength)); + msc_obj.next_stage = TEST_STAGE_CHECK_STR_DESC; + skip_event_handling = true; //Need to execute TEST_STAGE_CHECK_STR_DESC + break; + } + case TEST_STAGE_CHECK_STR_DESC: { + usb_device_info_t dev_info; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_info(msc_obj.dev_hdl, &dev_info)); + //Check manufacturer string descriptors + const usb_str_desc_t *manu_str_desc_ref = (const usb_str_desc_t *)mock_msc_scsi_str_desc_manu; + const usb_str_desc_t *product_str_desc_ref = (const usb_str_desc_t *)mock_msc_scsi_str_desc_prod; + const usb_str_desc_t *ser_num_str_desc_ref = (const usb_str_desc_t *)mock_msc_scsi_str_desc_ser_num; + TEST_ASSERT_EQUAL(manu_str_desc_ref->bLength, dev_info.str_desc_manufacturer->bLength); + TEST_ASSERT_EQUAL(product_str_desc_ref->bLength, dev_info.str_desc_product->bLength); + TEST_ASSERT_EQUAL(ser_num_str_desc_ref->bLength, dev_info.str_desc_serial_num->bLength); + TEST_ASSERT_EQUAL(0, memcmp(manu_str_desc_ref, dev_info.str_desc_manufacturer , manu_str_desc_ref->bLength)); + TEST_ASSERT_EQUAL(0, memcmp(product_str_desc_ref, dev_info.str_desc_product , manu_str_desc_ref->bLength)); + TEST_ASSERT_EQUAL(0, memcmp(ser_num_str_desc_ref, dev_info.str_desc_serial_num , manu_str_desc_ref->bLength)); + //Get dev info and compare + msc_obj.next_stage = TEST_STAGE_DEV_CLOSE; + skip_event_handling = true; //Need to execute TEST_STAGE_DEV_CLOSE + break; + } + + case TEST_STAGE_DEV_CLOSE: { + ESP_LOGD(MSC_CLIENT_TAG, "Close"); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(msc_obj.client_hdl, msc_obj.dev_hdl)); + enum_iter++; + if (enum_iter < TEST_ENUM_ITERATIONS) { + //Start the next test iteration by disconnecting the device, then going back to TEST_STAGE_WAIT_CONN stage + test_usb_set_phy_state(false, 0); + test_usb_set_phy_state(true, 0); + msc_obj.next_stage = TEST_STAGE_WAIT_CONN; + skip_event_handling = true; //Need to execute TEST_STAGE_WAIT_CONN + } else { + exit_loop = true; + } + break; + } + default: + abort(); + break; + } + } + //Free transfers and deregister the client + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(msc_obj.client_hdl)); + ESP_LOGD(MSC_CLIENT_TAG, "Done"); + vTaskDelete(NULL); +} diff --git a/components/usb/test/usb_host/test_usb_host_async.c b/components/usb/test/usb_host/test_usb_host_async.c index aa3a394e96..48cccadd22 100644 --- a/components/usb/test/usb_host/test_usb_host_async.c +++ b/components/usb/test/usb_host/test_usb_host_async.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include "freertos/semphr.h" #include "esp_err.h" #include "esp_intr_alloc.h" +#include "test_usb_common.h" #include "test_usb_mock_classes.h" #include "msc_client.h" #include "ctrl_client.h" @@ -44,10 +45,12 @@ Procedure: - Uninstall USB Host Library */ -TEST_CASE("Test USB Host async (single client)", "[usb_host][ignore]") +TEST_CASE("Test USB Host async client (single client)", "[usb_host][ignore]") { + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing //Install USB Host usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); @@ -83,6 +86,7 @@ TEST_CASE("Test USB Host async (single client)", "[usb_host][ignore]") vTaskDelay(10); //Clean up USB Host ESP_ERROR_CHECK(usb_host_uninstall()); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing } /* @@ -105,10 +109,12 @@ Procedure: - Free all devices - Uninstall USB Host Library */ -TEST_CASE("Test USB Host async (multi client)", "[usb_host][ignore]") +TEST_CASE("Test USB Host async client (multi client)", "[usb_host][ignore]") { + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing //Install USB Host usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); @@ -155,4 +161,147 @@ TEST_CASE("Test USB Host async (multi client)", "[usb_host][ignore]") vTaskDelay(10); //Clean up USB Host ESP_ERROR_CHECK(usb_host_uninstall()); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing +} + +/* +Test USB Host Asynchronous API Usage + +Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class) + +Purpose: + - Test that incorrect usage of USB Host Asynchronous API will returns errors + +Procedure: + - Install USB Host Library + - Register two clients and all event handler functions from the same loop + - Wait for each client to detect device connection + - Check that both clients can open the same device + - Check that a client cannot open a non-existent device + - Check that only one client can claim a particular interface + - Check that a client cannot release an already released interface + - Wait for device disconnection + - Cleanup +*/ + +static uint8_t dev_addr = 0; + +typedef enum { + CLIENT_TEST_STAGE_NONE, + CLIENT_TEST_STAGE_CONN, + CLIENT_TEST_STAGE_DCONN, +} client_test_stage_t; + +static void test_async_client_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + client_test_stage_t *stage = (client_test_stage_t *)arg; + + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + if (dev_addr == 0) { + dev_addr = event_msg->new_dev.address; + } else { + TEST_ASSERT_EQUAL(dev_addr, event_msg->new_dev.address); + } + *stage = CLIENT_TEST_STAGE_CONN; + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + *stage = CLIENT_TEST_STAGE_DCONN; + break; + default: + abort(); + break; + } +} + +TEST_CASE("Test USB Host async API", "[usb_host][ignore]") +{ + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing + + //Install USB Host + usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Register two clients + client_test_stage_t client0_stage = CLIENT_TEST_STAGE_NONE; + client_test_stage_t client1_stage = CLIENT_TEST_STAGE_NONE; + + usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 5, + .async = { + .client_event_callback = test_async_client_cb, + .callback_arg = (void *)&client0_stage, + }, + }; + usb_host_client_handle_t client0_hdl; + usb_host_client_handle_t client1_hdl; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &client0_hdl)); + client_config.async.callback_arg = (void *)&client1_stage; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &client1_hdl)); + + //Wait until the device connects and the clients receive the event + while (!(client0_stage == CLIENT_TEST_STAGE_CONN && client1_stage == CLIENT_TEST_STAGE_CONN)) { + usb_host_lib_handle_events(0, NULL); + usb_host_client_handle_events(client0_hdl, 0); + usb_host_client_handle_events(client1_hdl, 0); + vTaskDelay(10); + } + + //Check that both clients can open the device + TEST_ASSERT_NOT_EQUAL(0, dev_addr); + usb_device_handle_t client0_dev_hdl; + usb_device_handle_t client1_dev_hdl; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, dev_addr, &client0_dev_hdl)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(client1_hdl, dev_addr, &client1_dev_hdl)); + TEST_ASSERT_EQUAL(client0_dev_hdl, client1_dev_hdl); //Check that its the same device + //Check that a client cannot open a non-existent device + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, 0, &client0_dev_hdl)); + + //Check that the device cannot be opened again by the same client + usb_device_handle_t dummy_dev_hdl; + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client0_hdl, dev_addr, &dummy_dev_hdl)); + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_device_open(client1_hdl, dev_addr, &dummy_dev_hdl)); + //Check that both clients cannot claim the same interface + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_claim(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING)); + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_claim(client1_hdl, client1_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING)); + //Check that client0 cannot claim the same interface multiple times + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_claim(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING)); + + //Check that client0 can release the interface + TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_release(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER)); + //Check that client0 cannot release interface it has not claimed + TEST_ASSERT_NOT_EQUAL(ESP_OK, usb_host_interface_release(client0_hdl, client0_dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER)); + + //Wait until the device disconnects and the clients receive the event + test_usb_set_phy_state(false, 0); + while (!(client0_stage == CLIENT_TEST_STAGE_DCONN && client1_stage == CLIENT_TEST_STAGE_DCONN)) { + usb_host_lib_handle_events(0, NULL); + usb_host_client_handle_events(client0_hdl, 0); + usb_host_client_handle_events(client1_hdl, 0); + vTaskDelay(10); + } + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(client0_hdl, client0_dev_hdl)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(client1_hdl, client1_dev_hdl)); + + //Deregister the clients + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(client0_hdl)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(client1_hdl)); + + while (1) { + uint32_t event_flags; + usb_host_lib_handle_events(0, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + break; + } + vTaskDelay(10); + } + + //Cleanup + TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + test_usb_deinit_phy(); } diff --git a/components/usb/test/usb_host/test_usb_host_plugging.c b/components/usb/test/usb_host/test_usb_host_plugging.c index 4136529abf..4198b3205f 100644 --- a/components/usb/test/usb_host/test_usb_host_plugging.c +++ b/components/usb/test/usb_host/test_usb_host_plugging.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,7 +7,6 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/timers.h" #include "esp_err.h" #include "esp_intr_alloc.h" #include "test_usb_common.h" @@ -20,56 +19,88 @@ // --------------------------------------------------- Test Cases ------------------------------------------------------ -//Safe approximation of time it takes to connect and enumerate the device -#define TEST_FORCE_DCONN_DELAY_MS 400 +/* +Test USB Host Library Sudden Disconnection Handling (no clients) +Purpose: +- Test that sudden disconnections are handled properly when there are no clients +- Test that devices can reconnect after a sudden disconnection has been handled by the USB Host Library -static void trigger_dconn_timer_cb(TimerHandle_t xTimer) -{ - printf("Forcing Sudden Disconnect\n"); - test_usb_force_conn_state(false, 0); -} +Procedure: +- Install USB Host Library +- Wait for connection (and enumeration) to occur +- Force a disconnection, then wait for disconnection to be handled (USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) +- Allow connections again, and repeat test for multiple iterations +*/ + +#define TEST_DCONN_NO_CLIENT_ITERATIONS 3 TEST_CASE("Test USB Host sudden disconnection (no client)", "[usb_host][ignore]") { + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing //Install USB Host Library usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); printf("Installed\n"); - //Allocate timer to force disconnection after a short delay - TimerHandle_t timer_hdl = xTimerCreate("dconn", - pdMS_TO_TICKS(TEST_FORCE_DCONN_DELAY_MS), - pdFALSE, - NULL, - trigger_dconn_timer_cb); - TEST_ASSERT_NOT_EQUAL(NULL, timer_hdl); - TEST_ASSERT_EQUAL(pdPASS, xTimerStart(timer_hdl, portMAX_DELAY)); - + bool connected = false; + int dconn_iter = 0; while (1) { //Start handling system events uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (!connected) { + usb_host_lib_info_t lib_info; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_lib_info(&lib_info)); + if (lib_info.num_devices == 1) { + //We've just connected. Trigger a disconnect + connected = true; + printf("Forcing Sudden Disconnect\n"); + test_usb_set_phy_state(false, 0); + } + } if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - printf("All devices cleaned up\n"); - break; + //The device has disconnected and it's disconnection has been handled + printf("Dconn iter %d done\n", dconn_iter); + if (++dconn_iter < TEST_DCONN_NO_CLIENT_ITERATIONS) { + //Start next iteration + connected = false; + test_usb_set_phy_state(true, 0); + } else { + break; + } } } - //Cleanup timer - TEST_ASSERT_EQUAL(pdPASS, xTimerDelete(timer_hdl, portMAX_DELAY)); //Clean up USB Host ESP_ERROR_CHECK(usb_host_uninstall()); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing } +/* +Test USB Host Library Sudden Disconnection Handling (with client) +Purpose: +- Test that sudden disconnections are handled properly when there are registered clients +- Test that devices can reconnect after a sudden disconnection has been handled by the USB Host Library + +Procedure: + - Install USB Host Library + - Create a task to run an MSC client + - Start the MSC disconnect client task. It will open the device then force a disconnect for multiple iterations + - Wait for USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS and USB_HOST_LIB_EVENT_FLAGS_ALL_FREE before uninstalling +*/ + #define TEST_FORCE_DCONN_NUM_TRANSFERS 3 #define TEST_MSC_SCSI_TAG 0xDEADBEEF TEST_CASE("Test USB Host sudden disconnection (single client)", "[usb_host][ignore]") { + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing //Install USB Host usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); @@ -108,4 +139,63 @@ TEST_CASE("Test USB Host sudden disconnection (single client)", "[usb_host][igno vTaskDelay(10); //Clean up USB Host ESP_ERROR_CHECK(usb_host_uninstall()); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing +} + +/* +Test USB Host Library Enumeration +Purpose: +- Test that the USB Host Library enumerates device correctly + +Procedure: +- Install USB Host Library +- Create a task to run an MSC client +- Start the MSC enumeration client task. It will: + - Wait for device connection + - Open the device + - Check details of the device's enumeration + - Disconnect the device, and repeat the steps above for multiple iterations. +- Wait for USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS and USB_HOST_LIB_EVENT_FLAGS_ALL_FREE before uninstalling +*/ + +#define TEST_ENUM_ITERATIONS 3 + +TEST_CASE("Test USB Host enumeration", "[usb_host][ignore]") +{ + test_usb_init_phy(); //Initialize the internal USB PHY and USB Controller for testing + //Install USB Host + usb_host_config_t host_config = { + .skip_phy_setup = true, //test_usb_init_phy() will already have setup the internal USB PHY for us + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + printf("Installed\n"); + + //Create task to run client that checks the enumeration of the device + TaskHandle_t task_hdl; + xTaskCreatePinnedToCore(msc_client_async_enum_task, "async", 6144, NULL, 2, &task_hdl, 0); + //Start the task + xTaskNotifyGive(task_hdl); + + bool all_clients_gone = false; + bool all_dev_free = false; + while (!all_clients_gone || !all_dev_free) { + //Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all()); + all_clients_gone = true; + } + if (all_clients_gone && event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + all_dev_free = true; + } + } + + //Short delay to allow task to be cleaned up + vTaskDelay(10); + //Clean up USB Host + ESP_ERROR_CHECK(usb_host_uninstall()); + test_usb_deinit_phy(); //Deinitialize the internal USB PHY after testing } diff --git a/components/usb/usb_helpers.c b/components/usb/usb_helpers.c index ac1caf1425..c74451c6bb 100644 --- a/components/usb/usb_helpers.c +++ b/components/usb/usb_helpers.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,8 +15,6 @@ #include "esp_check.h" #include "usb/usb_host.h" -static const char *TAG = "usb_helper"; - // ---------------------------------------- Configuration Descriptor Parsing ------------------------------------------- const usb_standard_desc_t *usb_parse_next_descriptor(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, int *offset) @@ -171,7 +169,7 @@ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_address(const usb_config_d return ep_desc; } -// ------------------------------------------ Descriptor printing --------------------------------------------- +// ----------------------------------------------- Descriptor Printing ------------------------------------------------- static void print_ep_desc(const usb_ep_desc_t *ep_desc) { @@ -232,8 +230,12 @@ static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) printf("bMaxPower %dmA\n", cfg_desc->bMaxPower * 2); } -static void print_device_descriptor(const usb_device_desc_t *devc_desc) +void usb_print_device_descriptor(const usb_device_desc_t *devc_desc) { + if (devc_desc == NULL) { + return; + } + printf("*** Device descriptor ***\n"); printf("bLength %d\n", devc_desc->bLength); printf("bDescriptorType %d\n", devc_desc->bDescriptorType); @@ -251,8 +253,12 @@ static void print_device_descriptor(const usb_device_desc_t *devc_desc) printf("bNumConfigurations %d\n", devc_desc->bNumConfigurations); } -static void print_config_descriptor(const usb_config_desc_t *cfg_desc, print_class_descriptor_cb class_specific_cb) +void usb_print_config_descriptor(const usb_config_desc_t *cfg_desc, print_class_descriptor_cb class_specific_cb) { + if (cfg_desc == NULL) { + return; + } + int offset = 0; uint16_t wTotalLength = cfg_desc->wTotalLength; const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)cfg_desc; @@ -280,22 +286,21 @@ static void print_config_descriptor(const usb_config_desc_t *cfg_desc, print_cla } while (next_desc != NULL); } -esp_err_t usb_print_descriptors(usb_device_handle_t device, print_class_descriptor_cb class_specific_cb) +void usb_print_string_descriptor(const usb_str_desc_t *str_desc) { - if (device == NULL) { - return ESP_ERR_INVALID_ARG; + if (str_desc == NULL) { + return; } - const usb_config_desc_t *config_desc; - const usb_device_desc_t *device_desc; - - ESP_RETURN_ON_ERROR( usb_host_get_device_descriptor(device, &device_desc), TAG, "Failed to get devices descriptor" ); - ESP_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(device, &config_desc), TAG, "Failed to get config descriptor" ); - - print_device_descriptor(device_desc); - print_config_descriptor(config_desc, class_specific_cb); - - return ESP_OK; + for (int i = 0; i < str_desc->bLength/2; i++) { + /* + USB String descriptors of UTF-16. + Right now We just skip any character larger than 0xFF to stay in BMP Basic Latin and Latin-1 Supplement range. + */ + if (str_desc->wData[i] > 0xFF) { + continue; + } + printf("%c", (char)str_desc->wData[i]); + } + printf("\n"); } - -// ------------------------------------------------------ Misc --------------------------------------------------------- diff --git a/components/usb/usb_host.c b/components/usb/usb_host.c index 8d2f3159c4..b4ced72a54 100644 --- a/components/usb/usb_host.c +++ b/components/usb/usb_host.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,7 @@ Warning: The USB Host Library API is still a beta version and may be subject to #include "esp_heap_caps.h" #include "hub.h" #include "usbh.h" +#include "esp_private/usb_phy.h" #include "usb/usb_host.h" static portMUX_TYPE host_lock = portMUX_INITIALIZER_UNLOCKED; @@ -146,6 +147,7 @@ typedef struct { struct { SemaphoreHandle_t event_sem; SemaphoreHandle_t mux_lock; + usb_phy_handle_t phy_handle; //Will be NULL if host library is installed with skip_phy_setup } constant; } host_lib_t; @@ -374,6 +376,21 @@ esp_err_t usb_host_install(const usb_host_config_t *config) TAILQ_INIT(&host_lib_obj->mux_protected.client_tailq); host_lib_obj->constant.event_sem = event_sem; host_lib_obj->constant.mux_lock = mux_lock; + //Setup the USB PHY if necessary (USB PHY driver will also enable the underlying Host Controller) + if (!config->skip_phy_setup) { + //Host Library defaults to internal PHY + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + .gpio_conf = NULL, + }; + ret = usb_new_phy(&phy_config, &host_lib_obj->constant.phy_handle); + if (ret != ESP_OK) { + goto phy_err; + } + } //Install USBH usbh_config_t usbh_config = { .notif_cb = notif_callback, @@ -420,6 +437,10 @@ assign_err: hub_err: ESP_ERROR_CHECK(usbh_uninstall()); usbh_err: + if (p_host_lib_obj->constant.phy_handle) { + ESP_ERROR_CHECK(usb_del_phy(p_host_lib_obj->constant.phy_handle)); + } +phy_err: alloc_err: if (mux_lock) { vSemaphoreDelete(mux_lock); @@ -444,7 +465,6 @@ esp_err_t usb_host_uninstall(void) //Stop the root hub ESP_ERROR_CHECK(hub_root_stop()); - //Uninstall Hub and USBH ESP_ERROR_CHECK(hub_uninstall()); ESP_ERROR_CHECK(usbh_uninstall()); @@ -454,6 +474,10 @@ esp_err_t usb_host_uninstall(void) p_host_lib_obj = NULL; HOST_EXIT_CRITICAL(); + //If the USB PHY was setup, then delete it + if (host_lib_obj->constant.phy_handle) { + ESP_ERROR_CHECK(usb_del_phy(host_lib_obj->constant.phy_handle)); + } //Free memory objects vSemaphoreDelete(host_lib_obj->constant.mux_lock); vSemaphoreDelete(host_lib_obj->constant.event_sem); @@ -518,6 +542,23 @@ esp_err_t usb_host_lib_unblock(void) return ESP_OK; } +esp_err_t usb_host_lib_info(usb_host_lib_info_t *info_ret) +{ + HOST_CHECK(info_ret != NULL, ESP_ERR_INVALID_ARG); + int num_devs_temp; + int num_clients_temp; + HOST_ENTER_CRITICAL(); + HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE); + num_clients_temp = p_host_lib_obj->dynamic.flags.num_clients; + HOST_EXIT_CRITICAL(); + usbh_num_devs(&num_devs_temp); + + //Write back return values + info_ret->num_devices = num_devs_temp; + info_ret->num_clients = num_clients_temp; + return ESP_OK; +} + // ------------------------------------------------ Client Functions --------------------------------------------------- // ----------------------- Private ------------------------- @@ -971,14 +1012,14 @@ static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_h if (ret != ESP_OK) { goto ep_alloc_err; } - //Store endpoint object into interface object + //Fill the interface object with the allocated endpoints intf_obj->constant.endpoints[i] = ep_obj; } //Add interface object to client (safe because we have already taken the mutex) TAILQ_INSERT_TAIL(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry); //Add each endpoint to the client's endpoint list HOST_ENTER_CRITICAL(); - for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + for (int i = 0; i < intf_desc->bNumEndpoints; i++) { TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry); } HOST_EXIT_CRITICAL(); @@ -988,7 +1029,7 @@ static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_h return ret; ep_alloc_err: - for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) { + for (int i = 0; i < intf_desc->bNumEndpoints; i++) { endpoint_free(dev_hdl, intf_obj->constant.endpoints[i]); intf_obj->constant.endpoints[i] = NULL; } diff --git a/components/usb/usbh.c b/components/usb/usbh.c index ab72e97fb4..858ce3d0c9 100644 --- a/components/usb/usbh.c +++ b/components/usb/usbh.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,18 +22,15 @@ #include "usb/usb_types_ch9.h" //Device action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within usbh_process(). Some actions are mutually exclusive -#define DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH 0x01 //Halt all non-default pipes then flush them (called after a device gone is gone) -#define DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH 0x02 //Retire all URBS in the default pipe -#define DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE 0x04 //Dequeue all URBs from default pipe -#define DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR 0x08 //Move the default pipe to the active state -#define DEV_FLAG_ACTION_SEND_GONE_EVENT 0x10 //Send a USB_HOST_CLIENT_EVENT_DEV_GONE event -#define DEV_FLAG_ACTION_FREE 0x20 //Free the device object -#define DEV_FLAG_ACTION_PORT_DISABLE 0x40 //Request the hub driver to disable the port of the device -#define DEV_FLAG_ACTION_SEND_NEW 0x80 //Send a new device event - -#define DEV_ENUM_TODO_FLAG_DEV_ADDR 0x01 -#define DEV_ENUM_TODO_FLAG_DEV_DESC 0x02 -#define DEV_ENUM_TODO_FLAG_CONFIG_DESC 0x04 +#define DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH 0x0001 //Halt all non-default pipes then flush them (called after a device gone is gone) +#define DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH 0x0002 //Retire all URBS in the default pipe +#define DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE 0x0004 //Dequeue all URBs from default pipe +#define DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR 0x0008 //Move the default pipe to the active state +#define DEV_FLAG_ACTION_SEND_GONE_EVENT 0x0010 //Send a USB_HOST_CLIENT_EVENT_DEV_GONE event +#define DEV_FLAG_ACTION_FREE 0x0020 //Free the device object +#define DEV_FLAG_ACTION_FREE_AND_RECOVER 0x0040 //Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards. +#define DEV_FLAG_ACTION_PORT_DISABLE 0x0080 //Request the hub driver to disable the port of the device +#define DEV_FLAG_ACTION_SEND_NEW 0x0100 //Send a new device event #define EP_NUM_MIN 1 #define EP_NUM_MAX 16 @@ -45,23 +42,22 @@ struct device_s { TAILQ_ENTRY(device_s) tailq_entry; union { struct { - uint32_t actions: 8; uint32_t in_pending_list: 1; uint32_t is_gone: 1; uint32_t waiting_close: 1; uint32_t waiting_port_disable: 1; uint32_t waiting_free: 1; - uint32_t reserved19: 19; + uint32_t reserved27: 27; }; uint32_t val; } flags; + uint32_t action_flags; int num_ctrl_xfers_inflight; usb_device_state_t state; uint32_t ref_count; } dynamic; //Mux protected members must be protected by the USBH mux_lock when accessed struct { - usb_config_desc_t *config_desc; hcd_pipe_handle_t ep_in[EP_NUM_MAX - 1]; //IN EP owner contexts. -1 to exclude the default endpoint hcd_pipe_handle_t ep_out[EP_NUM_MAX - 1]; //OUT EP owner contexts. -1 to exclude the default endpoint } mux_protected; @@ -72,7 +68,10 @@ struct device_s { uint8_t address; usb_speed_t speed; const usb_device_desc_t *desc; - uint32_t enum_todo_flags; + const usb_config_desc_t *config_desc; + const usb_str_desc_t *str_desc_manu; + const usb_str_desc_t *str_desc_product; + const usb_str_desc_t *str_desc_ser_num; } constant; }; @@ -90,8 +89,8 @@ typedef struct { struct { usb_notif_cb_t notif_cb; void *notif_cb_arg; - usbh_hub_cb_t hub_cb; - void *hub_cb_arg; + usbh_hub_req_cb_t hub_req_cb; + void *hub_req_cb_arg; usbh_event_cb_t event_cb; void *event_cb_arg; usbh_ctrl_xfer_cb_t ctrl_xfer_cb; @@ -157,7 +156,6 @@ static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, dev //Note: dev_obj->constant.address is assigned later during enumeration dev_obj->constant.speed = speed; dev_obj->constant.desc = dev_desc; - dev_obj->constant.enum_todo_flags = (DEV_ENUM_TODO_FLAG_DEV_ADDR | DEV_ENUM_TODO_FLAG_DEV_DESC | DEV_ENUM_TODO_FLAG_CONFIG_DESC); *dev_obj_ret = dev_obj; ret = ESP_OK; return ret; @@ -173,10 +171,22 @@ static void device_free(device_t *dev_obj) if (dev_obj == NULL) { return; } - //Configuration must be freed - assert(dev_obj->mux_protected.config_desc == NULL); //Sanity check. No need for mux - ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe)); + //Configuration might not have been allocated (in case of early enumeration failure) + if (dev_obj->constant.config_desc) { + heap_caps_free((usb_config_desc_t *)dev_obj->constant.config_desc); + } + //String descriptors might not have been allocated (in case of early enumeration failure) + if (dev_obj->constant.str_desc_manu) { + heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_manu); + } + if (dev_obj->constant.str_desc_product) { + heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_product); + } + if (dev_obj->constant.str_desc_ser_num) { + heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_ser_num); + } heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc); + ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe)); heap_caps_free(dev_obj); } @@ -193,7 +203,7 @@ static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags) //Move device form idle device list to callback device list TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); - dev_obj->dynamic.flags.actions |= action_flags; + dev_obj->dynamic.action_flags |= action_flags; dev_obj->dynamic.flags.in_pending_list = 1; call_notif_cb = true; } else { @@ -282,10 +292,8 @@ static bool handle_dev_free(device_t *dev_obj) USBH_EXIT_CRITICAL(); p_usbh_obj->mux_protected.num_device--; bool all_free = (p_usbh_obj->mux_protected.num_device == 0); - heap_caps_free(dev_obj->mux_protected.config_desc); - dev_obj->mux_protected.config_desc = NULL; - device_free(dev_obj); xSemaphoreGive(p_usbh_obj->constant.mux_lock); + device_free(dev_obj); return all_free; } @@ -391,8 +399,8 @@ esp_err_t usbh_process(void) TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry); TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); //Clear the device's flags - uint32_t action_flags = dev_obj->dynamic.flags.actions; - dev_obj->dynamic.flags.actions = 0; + uint32_t action_flags = dev_obj->dynamic.action_flags; + dev_obj->dynamic.action_flags = 0; dev_obj->dynamic.flags.in_pending_list = 0; /* --------------------------------------------------------------------- @@ -440,16 +448,22 @@ esp_err_t usbh_process(void) - New device event is requested followed immediately by a disconnection - Port disable requested followed immediately by a disconnection */ - if (action_flags & DEV_FLAG_ACTION_FREE) { + if (action_flags & (DEV_FLAG_ACTION_FREE | DEV_FLAG_ACTION_FREE_AND_RECOVER)) { + //Cache a copy of the port handle as we are about to free the device object + hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl; ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address); if (handle_dev_free(dev_obj)) { ESP_LOGD(USBH_TAG, "Device all free"); p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg); } + //Check if we need to recover the device's port + if (action_flags & DEV_FLAG_ACTION_FREE_AND_RECOVER) { + p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg); + } } else if (action_flags & DEV_FLAG_ACTION_PORT_DISABLE) { //Request that the HUB disables this device's port ESP_LOGD(USBH_TAG, "Disable device port %d", dev_obj->constant.address); - p_usbh_obj->constant.hub_cb(dev_obj->constant.port_hdl, USBH_HUB_EVENT_DISABLE_PORT, p_usbh_obj->constant.hub_cb_arg); + p_usbh_obj->constant.hub_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg); } else if (action_flags & DEV_FLAG_ACTION_SEND_NEW) { ESP_LOGD(USBH_TAG, "New device %d", dev_obj->constant.address); p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_NEW, p_usbh_obj->constant.event_cb_arg); @@ -464,6 +478,15 @@ esp_err_t usbh_process(void) return ESP_OK; } +esp_err_t usbh_num_devs(int *num_devs_ret) +{ + USBH_CHECK(num_devs_ret != NULL, ESP_ERR_INVALID_ARG); + xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); + *num_devs_ret = p_usbh_obj->mux_protected.num_device; + xSemaphoreGive(p_usbh_obj->constant.mux_lock); + return ESP_OK; +} + // ------------------------------------------------ Device Functions --------------------------------------------------- // --------------------- Device Pool ----------------------- @@ -543,16 +566,16 @@ esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl) device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.num_ctrl_xfers_inflight == 0, ESP_ERR_INVALID_STATE); dev_obj->dynamic.ref_count--; bool call_notif_cb = false; if (dev_obj->dynamic.ref_count == 0) { - //Sanity check. This can only be set when ref count reaches 0 - assert(!dev_obj->dynamic.flags.waiting_free); + //Sanity check. + assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); //There cannot be any control transfer inflight + assert(!dev_obj->dynamic.flags.waiting_free); //This can only be set when ref count reaches 0 if (dev_obj->dynamic.flags.is_gone) { //Device is already gone so it's port is already disabled. Trigger the USBH process to free the device dev_obj->dynamic.flags.waiting_free = 1; - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it } else if (dev_obj->dynamic.flags.waiting_close) { //Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled dev_obj->dynamic.flags.waiting_port_disable = 1; @@ -632,8 +655,6 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ device_t *dev_obj = (device_t *)dev_hdl; esp_err_t ret; - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); //Device must be configured, or not attached (if it suddenly disconnected) USBH_ENTER_CRITICAL(); if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED)) { @@ -646,14 +667,14 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_ dev_info->dev_addr = dev_obj->constant.address; dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0; USBH_EXIT_CRITICAL(); - if (dev_obj->mux_protected.config_desc == NULL) { - dev_info->bConfigurationValue = 0; - } else { - dev_info->bConfigurationValue = dev_obj->mux_protected.config_desc->bConfigurationValue; - } + assert(dev_obj->constant.config_desc); + dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue; + //String descriptors are allowed to be NULL as not all devices support them + dev_info->str_desc_manufacturer = dev_obj->constant.str_desc_manu; + dev_info->str_desc_product = dev_obj->constant.str_desc_product; + dev_info->str_desc_serial_num = dev_obj->constant.str_desc_ser_num; ret = ESP_OK; exit: - xSemaphoreGive(p_usbh_obj->constant.mux_lock); return ret; } @@ -676,8 +697,6 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config device_t *dev_obj = (device_t *)dev_hdl; esp_err_t ret; - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); //Device must be in the configured state USBH_ENTER_CRITICAL(); if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { @@ -686,10 +705,10 @@ esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config goto exit; } USBH_EXIT_CRITICAL(); - *config_desc_ret = dev_obj->mux_protected.config_desc; + assert(dev_obj->constant.config_desc); + *config_desc_ret = dev_obj->constant.config_desc; ret = ESP_OK; exit: - xSemaphoreGive(p_usbh_obj->constant.mux_lock); return ret; } @@ -729,13 +748,8 @@ esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config { USBH_CHECK(dev_hdl != NULL && ep_config != NULL && pipe_hdl_ret != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - - USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED, ESP_ERR_INVALID_STATE); - dev_obj->dynamic.ref_count++; //Increase the ref_count to keep the device alive while allocating the endpoint - USBH_EXIT_CRITICAL(); - esp_err_t ret; + //Allocate HCD pipe hcd_pipe_config_t pipe_config = { .callback = ep_config->pipe_cb, @@ -757,17 +771,22 @@ esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config //We need to take the mux_lock to access mux_protected members xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - if (is_in && dev_obj->mux_protected.ep_in[addr - 1] == NULL) { //Is an IN EP + USBH_ENTER_CRITICAL(); + //Check the device's state before we assign the pipes to the endpoint + if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) { + USBH_EXIT_CRITICAL(); + ret = ESP_ERR_INVALID_STATE; + goto assign_err; + } + USBH_EXIT_CRITICAL(); + //Assign the allocated pipe to the correct endpoint + if (is_in && dev_obj->mux_protected.ep_in[addr - 1] == NULL) { //Is an IN EP dev_obj->mux_protected.ep_in[addr - 1] = pipe_hdl; assigned = true; - } else { + } else if (dev_obj->mux_protected.ep_out[addr - 1] == NULL) { //Is an OUT EP dev_obj->mux_protected.ep_out[addr - 1] = pipe_hdl; assigned = true; } - //Restore ref_count - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.ref_count--; - USBH_EXIT_CRITICAL(); xSemaphoreGive(p_usbh_obj->constant.mux_lock); if (!assigned) { @@ -864,17 +883,17 @@ exit: // ------------------- Device Related ---------------------- -esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg) +esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg) { - USBH_CHECK(hub_callback != NULL, ESP_ERR_INVALID_ARG); + USBH_CHECK(hub_req_callback != NULL, ESP_ERR_INVALID_ARG); USBH_ENTER_CRITICAL(); //Check that USBH is already installed USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE); //Check that Hub has not be installed yet - USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_cb == NULL, ESP_ERR_INVALID_STATE); - p_usbh_obj->constant.hub_cb = hub_callback; - p_usbh_obj->constant.hub_cb_arg = callback_arg; + USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_req_cb == NULL, ESP_ERR_INVALID_STATE); + p_usbh_obj->constant.hub_req_cb = hub_req_callback; + p_usbh_obj->constant.hub_req_cb_arg = callback_arg; USBH_EXIT_CRITICAL(); return ESP_OK; @@ -897,26 +916,41 @@ esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, us return ret; } -esp_err_t usbh_hub_mark_dev_gone(usb_device_handle_t dev_hdl) +esp_err_t usbh_hub_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - USBH_ENTER_CRITICAL(); - dev_obj->dynamic.flags.is_gone = 1; bool call_notif_cb; - //Check if the device can be freed now - if (dev_obj->dynamic.ref_count == 0) { - dev_obj->dynamic.flags.waiting_free = 1; - //Device is already waiting free so none of it's pipes will be in use. Can free immediately. - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); - } else { - call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH | - DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | - DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | - DEV_FLAG_ACTION_SEND_GONE_EVENT); + switch (hub_event) { + case USBH_HUB_EVENT_PORT_ERROR: { + USBH_ENTER_CRITICAL(); + dev_obj->dynamic.flags.is_gone = 1; + //Check if the device can be freed now + if (dev_obj->dynamic.ref_count == 0) { + dev_obj->dynamic.flags.waiting_free = 1; + //Device is already waiting free so none of it's pipes will be in use. Can free immediately. + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE_AND_RECOVER); //Port error occurred so we need to recover it + } else { + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PIPE_HALT_AND_FLUSH | + DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH | + DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | + DEV_FLAG_ACTION_SEND_GONE_EVENT); + } + USBH_EXIT_CRITICAL(); + break; + } + case USBH_HUB_EVENT_PORT_DISABLED: { + USBH_ENTER_CRITICAL(); + assert(dev_obj->dynamic.ref_count == 0); //At this stage, the device should have been closed by all users + dev_obj->dynamic.flags.waiting_free = 1; + call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); + USBH_EXIT_CRITICAL(); + break; + } + default: + return ESP_ERR_INVALID_ARG; } - USBH_EXIT_CRITICAL(); if (call_notif_cb) { p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); @@ -924,24 +958,6 @@ esp_err_t usbh_hub_mark_dev_gone(usb_device_handle_t dev_hdl) return ESP_OK; } -esp_err_t usbh_hub_dev_port_disabled(usb_device_handle_t dev_hdl) -{ - USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); - device_t *dev_obj = (device_t *)dev_hdl; - - USBH_ENTER_CRITICAL(); - assert(dev_obj->dynamic.ref_count == 0); //At this stage, the device should have been closed by all users - dev_obj->dynamic.flags.waiting_free = 1; - bool call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE); - USBH_EXIT_CRITICAL(); - - if (call_notif_cb) { - ESP_LOGD(USBH_TAG, "Notif free"); - p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg); - } - return ESP_OK; -} - // ----------------- Enumeration Related ------------------- esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr) @@ -950,12 +966,10 @@ esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_a device_t *dev_obj = (device_t *)dev_hdl; USBH_ENTER_CRITICAL(); - USBH_CHECK_FROM_CRIT(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_DEV_ADDR, ESP_ERR_INVALID_STATE); dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS; USBH_EXIT_CRITICAL(); //We can modify the info members outside the critical section - dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_DEV_ADDR; dev_obj->constant.address = dev_addr; return ESP_OK; } @@ -965,8 +979,6 @@ esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_dev USBH_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; //We can modify the info members outside the critical section - USBH_CHECK(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_DEV_DESC, ESP_ERR_INVALID_STATE); - dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_DEV_DESC; memcpy((usb_device_desc_t *)dev_obj->constant.desc, device_desc, sizeof(usb_device_desc_t)); return ESP_OK; } @@ -975,43 +987,52 @@ esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_ { USBH_CHECK(dev_hdl != NULL && config_desc_full != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - esp_err_t ret; //Allocate memory to store the configuration descriptor usb_config_desc_t *config_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT); //Buffer to copy over full configuration descriptor (wTotalLength) if (config_desc == NULL) { - ret = ESP_ERR_NO_MEM; - goto err; + return ESP_ERR_NO_MEM; } //Copy the configuration descriptor memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength); - //Assign the config object to the device object - if (!(dev_obj->constant.enum_todo_flags & DEV_ENUM_TODO_FLAG_CONFIG_DESC)) { - ret = ESP_ERR_INVALID_STATE; - goto assign_err; + //Assign the config desc to the device object + assert(dev_obj->constant.config_desc == NULL); + dev_obj->constant.config_desc = config_desc; + return ESP_OK; +} + +esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select) +{ + USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG); + device_t *dev_obj = (device_t *)dev_hdl; + //Allocate memory to store the manufacturer string descriptor + usb_str_desc_t *str_desc_fill = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT); + if (str_desc_fill == NULL) { + return ESP_ERR_NO_MEM; } - - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - assert(dev_obj->mux_protected.config_desc == NULL); - dev_obj->mux_protected.config_desc = config_desc; - xSemaphoreGive(p_usbh_obj->constant.mux_lock); - - //We can modify the info members outside the critical section - dev_obj->constant.enum_todo_flags &= ~DEV_ENUM_TODO_FLAG_CONFIG_DESC; - ret = ESP_OK; - return ret; - -assign_err: - heap_caps_free(config_desc); -err: - return ret; + //Copy the string descriptor + memcpy(str_desc_fill, str_desc, str_desc->bLength); + //Assign filled string descriptor to the device object + switch (select) { + case 0: + assert(dev_obj->constant.str_desc_manu == NULL); + dev_obj->constant.str_desc_manu = str_desc_fill; + break; + case 1: + assert(dev_obj->constant.str_desc_product == NULL); + dev_obj->constant.str_desc_product = str_desc_fill; + break; + default: //2 + assert(dev_obj->constant.str_desc_ser_num == NULL); + dev_obj->constant.str_desc_ser_num = str_desc_fill; + break; + } + return ESP_OK; } esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - USBH_CHECK(dev_obj->constant.enum_todo_flags == 0, ESP_ERR_INVALID_STATE); //All enumeration stages to be done //We need to take the mux_lock to access mux_protected members xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); @@ -1037,16 +1058,6 @@ esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl) { USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG); device_t *dev_obj = (device_t *)dev_hdl; - - //We need to take the mux_lock to access mux_protected members - xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY); - usb_config_desc_t *config_desc = dev_obj->mux_protected.config_desc; - dev_obj->mux_protected.config_desc = NULL; - xSemaphoreGive(p_usbh_obj->constant.mux_lock); - - if (config_desc) { - heap_caps_free(config_desc); - } device_free(dev_obj); return ESP_OK; } diff --git a/docs/_static/usb_host_lib_entities.png b/docs/_static/usb_host_lib_entities.png new file mode 100644 index 0000000000..c22186dae4 Binary files /dev/null and b/docs/_static/usb_host_lib_entities.png differ diff --git a/docs/_static/usb_host_lib_lifecycle.png b/docs/_static/usb_host_lib_lifecycle.png new file mode 100644 index 0000000000..f83e6de3b3 Binary files /dev/null and b/docs/_static/usb_host_lib_lifecycle.png differ diff --git a/docs/en/api-reference/peripherals/usb_host.rst b/docs/en/api-reference/peripherals/usb_host.rst index 91a2ae44d1..169328c57a 100644 --- a/docs/en/api-reference/peripherals/usb_host.rst +++ b/docs/en/api-reference/peripherals/usb_host.rst @@ -4,7 +4,378 @@ USB Host .. warning:: The USB Host Library API is a beta version thus is subject to change. -The following document lists the API and types of the USB Host Library (that is currently under development). +The document provides information regarding the USB Host Library. This document is split into the following sections: + +.. contents:: Sections + :depth: 2 + + +.. ---------------------------------------------------- Overview ------------------------------------------------------- + +Overview +-------- + +The USB Host Library (hereinafter referred to as the Host Library) is the lowest public facing API layer of the ESP-IDF USB Host Stack. In most cases, applications that require USB Host functionality will not need to interface with the Host Library directly. Instead, most applications will use the API provided by a host class driver that is implemented on top of the Host Library. + +However, users may want to use the Host Library directly for some of (but not limited to) the following reasons: + +- The user needs to implement a custom host class driver such as a vendor specific class driver +- The user has a requirement for a lower level of abstraction due to resource/latency requirements + +Features & Limitations +^^^^^^^^^^^^^^^^^^^^^^ + +The Host Library has the following features: + +- Supports Full Speed (FS) and Low Speed (LS) Devices +- Supports all four transfer types (Control, Bulk, Interrupt, and Isochronous) +- Allows multiple class drivers to run simultaneously (i.e., multiple clients of the Host Library) +- A single device can be used by multiple clients simultaneously (e.g., composite devices) +- The Host Library itself (and the underlying Host Stack) does not internally instantiate any OS tasks. The number of tasks are entirely controlled by how the Host Library interface is used. However, a general rule of thumb regarding the number of tasks is ``(the number of host class drivers running + 1)``. + +Currently, the Host Library (and the underlying Host Stack) has the following limitations: + +- Only supports a single device, but the Host Library's API is designed for multiple device support. +- Only supports Asynchronous transfers +- Transfer timeouts are not supported yet + + +.. -------------------------------------------------- Architecture ----------------------------------------------------- + +Architecture +------------ + +.. figure:: ../../../_static/usb_host_lib_entities.png + :align: center + :alt: Diagram of the Key Entities of USB Host Functionality + :figclass: align-center + + Diagram of the key entities involved in USB Host functionality + +The diagram above shows the key entities that are involved when implementing USB Host functionality. These entities are: + +- The **Host Library** +- **Clients** of the Host Library +- **Devices** +- Host Library **Daemon Task** + +Host Library +^^^^^^^^^^^^ + +The Host Library is the a lowest public facing layer of the USB Host Stack. Any other IDF component (such as a class driver or a user component) that needs to communicate with a connected USB device can only do so using the Host Library API either directly or indirectly. + +The Host Library's API is split into two sub-sets, namely the **Library API** and **Client API**. + +- The Client API handles the communication between a client of the Host Library and one or more USB devices. The Client API should only be called by registered clients of the Host Library. +- The Library API handles all of the Host Library processing that is not specific to a single client (e.g., device enumeration). Usually, the library API is called by a Host Library Daemon Task. + +Clients +^^^^^^^ + +A client of the Host Library is a software component (such as a host class driver or user component) that uses the Host Library to communicate with a USB device. Generally each client has a one-to-one relation with a task, meaning that for a particular client, all of its Client API calls should be done from the context of the same task. + +By organizing the software components that use the Host Library's into clients, the Host Library can delegate the handling of all client events (i.e., the events specific to that client) to the client's task. In other words, each client task is responsible for all the required processing and event handling associated with the USB communication that the client initiates. + +Daemon Task +^^^^^^^^^^^ + +Although the Host Library delegates the handling of client events to the clients themselves, there are still Library events (i.e., events that are not specific to a client) that need to be handled. Library event handling can include things such as: + +- Handling USB device connection, enumeration, and disconnection +- Rerouting control transfers to/from clients +- Forwarding events to clients + +Therefore, in addition to the client tasks, the Host Library also requires a task (usually the Host Library Daemon Task) to handle all of the library events. + +Devices +^^^^^^^ + +The Host Library hides the details of device handling (such as connection, memory allocation, and enumeration) from the clients. The clients are provided only with a list of already connected and enumerated devices to choose from. During enumeration, each device is configured to use configuration 1. + +It is possible for a two or more clients to simultaneously communicate with the same device as long as they are not communicating to the same interface. However, multiple clients can simultaneously communicate with the same device's default endpoint (EP0), which will result in their control transfers being serialized. + +For a client to communicate with a device, the client must: + +#. Open the device using the device's address. This lets the Host Library know that the client is using that device. +#. Claim the interface(s) that will be used for communication. This prevents other clients from claiming the same interface(s). +#. Send transfers to the endpoints in the claimed interface. The client's task is responsible for handling its own processing and events. + + +.. ------------------------------------------------------ Usage -------------------------------------------------------- + +Usage +----- + +The Host Library (and the underlying Host Stack) will not create any tasks. All tasks (i.e., the client tasks and the Daemon Task) will need to be created by the class drivers or the user. Instead, the Host Library provides two event handler functions that will handle all of the required Host Library processing, thus these functions should be called repeatedly from the client tasks and the Daemon Task. Therefore, the implementation of client tasks and the Daemon Task will be the largely centered around the invocation of these event handler functions. + +Host Library & Daemon Task +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Basic Usage +""""""""""" + +The Host Library API provides :cpp:func:`usb_host_lib_handle_events` to handle library events. This function should be called repeatedly, typically from the daemon task. Some notable features regarding :cpp:func:`usb_host_lib_handle_events` are: + +- The function can block until a library event needs handling +- Event flags are returned on each invocation. These event flags are useful for knowing when the Host Library can be uninstalled. + +A bare-bones Daemon Task would resemble something like the following code snippet: + +.. code-block:: c + + #include "usb/usb_host.h" + + void daemon_task(void *arg) + { + ... + bool exit = false; + while (!exit) { + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + ... + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ... + } + ... + } + ... + } + +.. note:: + See the :example:`peripherals/usb/host/usb_host_lib` example for a full implementation of the Daemon Task + +Lifecycle +""""""""" + +.. figure:: ../../../_static/usb_host_lib_lifecycle.png + :align: center + :alt: Graph of Typical USB Host Library Lifecycle + :figclass: align-center + + Graph of Typical USB Host Library Lifecycle + +The graph above illustrates the typical lifecycle of the Host Library with multiple clients and devices. Specifically, the example involves... + +- two registered clients (Client 1 and Client 2) +- two connected devices (Device 1 and Device 2), where Client 1 communicates with Device 1 and Client 2 communicates with Device 2. + +With reference the graph above, the typical lifecycle involves the following key stages. + +1. The Host Library is installed by calling :cpp:func:`usb_host_install`. + - Installation must be done before any other Host Library API is called. + - Where :cpp:func:`usb_host_install` is called (e.g., from the Daemon Task or another task) will depend on the synchronization logic between the Daemon Task, client tasks, and the rest of the system. +2. Once the Host Library is installed, the clients can be registered by calling :cpp:func:`usb_host_client_register`. + - This is typically called from the client task (where the client task waits for a signal from the Daemon Task). + - This can be called elsewhere if necessary as long it is called after :cpp:func:`usb_host_install`. +3. Device 1 connects and is then enumerated. + - Each registered client (in this case Client 1 and Client 2) are notified of the new device by way of the :cpp:enumerator:`USB_HOST_CLIENT_EVENT_NEW_DEV` event. + - Client 1 opens Device 1 and begins communication with it. +4. Similarly Device 2 connects and is enumerated. + - Client 1 and 2 are notified of a new device (via a :cpp:enumerator:`USB_HOST_CLIENT_EVENT_NEW_DEV` event). + - Client 2 opens Device 2 and begins communication with it. +5. Device 1 suddenly disconnects. + - Client 1 is notified by way of :cpp:enumerator:`USB_HOST_CLIENT_EVENT_DEV_GONE` and begins its cleanup. + - Client 2 is not notified as it has not opened Device 1. +6. Client 1 completes its clean up and deregisters by calling :cpp:func:`usb_host_client_deregister`. + - This is typically called from the client task before the task exits. + - This can be called elsewhere if necessary as long as Client 1 has already completed its clean up. +7. Client 2 completes its communication with Device 2. Client 2 then closes Device 2 and deregisters itself. + - The Daemon Task is notified of the deregistration of all clients by way the :c:macro:`USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS` event flag as Client 2 is the last client to deregister. + - Device 2 is still allocated (i.e., not freed) as it is still connected albeit not currently opened by any client. +8. The Daemon Task decides to cleanup as there are no more clients. + - The Daemon Task must free Device 2 first by calling :cpp:func:`usb_host_device_free_all`. + - If :cpp:func:`usb_host_device_free_all` was able to free all devices, the function will return `ESP_OK` indicating that all devices have been freed. + - If :cpp:func:`usb_host_device_free_all` was unable to free all devices (e.g., because the device is still opened by a client), the function will return `ESP_ERR_NOT_FINISHED`. + - The Daemon Task must wait for :cpp:func:`usb_host_lib_handle_events` to return the :c:macro:`USB_HOST_LIB_EVENT_FLAGS_ALL_FREE` event flag in order to know when all devices have been freed. +9. Once the Daemon Task has verified that all clients have deregistered and all devices have been freed, it can now uninstall the Host Library by calling :cpp:func:`usb_host_uninstall`. + +Clients & Class Driver +^^^^^^^^^^^^^^^^^^^^^^ + +Basic Usage +""""""""""" + +The Host Library API provides :cpp:func:`usb_host_client_handle_events` to handle a particular client's events. This function should be called repeatedly, typically from the client's task. Some notable features regarding :cpp:func:`usb_host_client_handle_events` are: + +- The function can block until a client event needs handling +- The function's primary purpose is to call the various event handling callbacks when a client event occurs. + +The following callbacks are called from within :cpp:func:`usb_host_client_handle_events` thus allowing the client task to be notified of events. + +- The client event callback of type :cpp:type:`usb_host_client_event_cb_t` which delivers client event messages to the client. Client event messages indicate events such as the addition or removal of a device. +- The USB transfer completion callback of type :cpp:type:`usb_transfer_cb_t` which indicates that a particular USB transfer previously submitted by the client has completed. + +.. note:: + Given that the callbacks are called from within :cpp:func:`usb_host_client_handle_events`, users should avoid blocking from within the callbacks as this will result in :cpp:func:`usb_host_client_handle_events` being blocked as well, thus preventing other pending client events from being handled. + +The following code snippet demonstrates a bare-bones host class driver and its client task. The code snippet contains: + +- A simple client task function ``client_task`` that calls :cpp:func:`usb_host_client_handle_events` in a loop. +- Implementations of a client event callback and transfer completion callbacks. +- Implementation of a simple state machine for the class driver. The class driver simply opens a device, sends an OUT transfer to EP1, then closes the device. + +.. code-block:: c + + #include + #include "usb/usb_host.h" + + #define CLASS_DRIVER_ACTION_OPEN_DEV 0x01 + #define CLASS_DRIVER_ACTION_TRANSFER 0x02 + #define CLASS_DRIVER_ACTION_CLOSE_DEV 0x03 + + struct class_driver_control { + uint32_t actions; + uint8_t dev_addr; + usb_host_client_handle_t client_hdl; + usb_device_handle_t dev_hdl; + }; + + static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) + { + //This is function is called from within usb_host_client_handle_events(). Don't block and try to keep it short + struct class_driver_control *class_driver_obj = (struct class_driver_control *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + class_driver_obj->actions |= CLASS_DRIVER_ACTION_OPEN_DEV; + class_driver_obj->dev_addr = event_msg->new_dev.address; //Store the address of the new device + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + class_driver_obj->actions |= CLASS_DRIVER_ACTION_CLOSE_DEV; + break; + default: + break; + } + } + + static void transfer_cb(usb_transfer_t *transfer) + { + //This is function is called from within usb_host_client_handle_events(). Don't block and try to keep it short + struct class_driver_control *class_driver_obj = (struct class_driver_control *)transfer->context; + printf("Transfer status %d, actual number of bytes transferred %d\n", transfer->status, transfer->actual_num_bytes); + class_driver_obj->actions |= CLASS_DRIVER_ACTION_CLOSE_DEV; + } + + void client_task(void *arg) + { + ... //Wait until Host Library is installed + //Initialize class driver objects + struct class_driver_control class_driver_obj = {0}; + //Register the client + usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 5, + .async = { + .client_event_callback = client_event_cb, + .callback_arg = &class_driver_obj, + } + }; + usb_host_client_register(&client_config, &class_driver_obj.client_hdl); + //Allocate a USB transfer + usb_transfer_t *transfer; + usb_host_transfer_alloc(1024, 0, &transfer); + + //Event handling loop + bool exit = false; + while (!exit) { + //Call the client event handler function + usb_host_client_handle_events(class_driver_obj.client_hdl, portMAX_DELAY); + //Execute pending class driver actions + if (class_driver_obj.actions & CLASS_DRIVER_ACTION_OPEN_DEV) { + //Open the device and claim interface 1 + usb_host_device_open(class_driver_obj.client_hdl, class_driver_obj.dev_addr, &class_driver_obj.dev_hdl); + usb_host_interface_claim(class_driver_obj.client_hdl, class_driver_obj.dev_hdl, 1, 0); + } + if (class_driver_obj.actions & CLASS_DRIVER_ACTION_TRANSFER) { + //Send an OUT transfer to EP1 + memset(transfer->data_buffer, 0xAA, 1024); + transfer->num_bytes = 1024; + transfer->device_handle = class_driver_obj.dev_hdl; + transfer->bEndpointAddress = 0x01; + transfer->callback = transfer_cb; + transfer->context = (void *)&class_driver_obj; + usb_host_transfer_submit(transfer); + } + if (class_driver_obj.actions & CLASS_DRIVER_ACTION_CLOSE_DEV) { + //Release the interface and close the device + usb_host_interface_release(class_driver_obj.client_hdl, class_driver_obj.dev_hdl, 1); + usb_host_device_close(class_driver_obj.client_hdl, class_driver_obj.dev_hdl); + exit = true; + } + ... //Handle any other actions required by the class driver + } + + //Cleanup class driver + usb_host_transfer_free(transfer); + usb_host_client_deregister(class_driver_obj.client_hdl); + ... //Delete the task and any other signal Daemon Task if required + } + +.. note:: + An actual host class driver will likely supported many more features, thus will have a much more complex state machine. A host class driver will likely also need to: + + - Be able to open multiple devices + - Parse an opened device's descriptors to identify if the device is of the target class + - Communicate with multiple endpoints of an interface in a particular order + - Claim multiple interfaces of a device + - Handle various errors + +Lifecycle +""""""""" + +The typical life cycle of a client task and class driver will go through the following stages: + +#. Wait for some signal regarding the Host Library being installed. +#. Register the client via :cpp:func:`usb_host_client_register` and allocate any other class driver resources (e.g., allocating transfers using :cpp:func:`usb_host_transfer_alloc`). +#. For each new device that the class driver needs to communicate with: + + a. Check if the device is already connected via :cpp:func:`usb_host_device_addr_list_fill`. + b. If the device is not already connected, wait for a :cpp:enumerator:`USB_HOST_CLIENT_EVENT_NEW_DEV` event from the client event callback. + c. Open the device via :cpp:func:`usb_host_device_open`. + d. Parse the device and configuration descriptors via :cpp:func:`usb_host_get_device_descriptor` and :cpp:func:`usb_host_get_active_config_descriptor` respectively. + e. Claim the necessary interfaces of the device via :cpp:func:`usb_host_interface_claim`. + +#. Submit transfers to the device via :cpp:func:`usb_host_transfer_submit` or :cpp:func:`usb_host_transfer_submit_control`. +#. Once an opened device is no longer needed by the class driver, or has disconnected (as indicated by a :cpp:enumerator:`USB_HOST_CLIENT_EVENT_DEV_GONE` event): + + a. Stop any previously submitted transfers to the device's endpoints by calling :cpp:func:`usb_host_endpoint_halt` and :cpp:func:`usb_host_endpoint_flush` on those endpoints. + b. Release all previously claimed interfaces via :cpp:func:`usb_host_interface_release`. + c. Close the device via :cpp:func:`usb_host_device_close`. + +#. Deregister the client via :cpp:func:`usb_host_client_deregister` and free any other class driver resources. +#. Delete the client task. Signal the Daemon Task if necessary. + + +.. ---------------------------------------------------- Examples ------------------------------------------------------- + +Examples +-------- + +Host Library Examples +^^^^^^^^^^^^^^^^^^^^^ + +The :example:`peripherals/usb/host/usb_host_lib` demonstrates basic usage of the USB Host Library's API to implement a pseudo class driver. + +Class Driver Examples +^^^^^^^^^^^^^^^^^^^^^ + +The USB Host Stack provides a number examples that implement host class drivers using the Host Library's API. + +CDC-ACM +""""""" + +* A host class driver for the Communication Device Class (Abstract Control Model) is currently implemented as an example component (found via :example:`peripherals/usb/host/cdc/common/cdc_acm_host`). +* The :example:`peripherals/usb/host/cdc/cdc_acm_host` example uses the CDC-ACM host driver component to communicate with CDC-ACM devices +* The :example:`peripherals/usb/host/cdc/cdc_acm_bg96` example uses the CDC-ACM host driver component to communicate with non-compliant CDC-ACM devices (i.e., vendor-specific classes that support a subset of CDC-ACM features) such as the Quectel BG96 modem. + +MSC +""" + +* A host class driver for the Mass Storage Class (Bulk-Only Transport) is current implemented as an example found via :example:`peripherals/usb/host/msc`. + + +.. -------------------------------------------------- API Reference ---------------------------------------------------- API Reference ------------- diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp b/examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp index f7b39949a3..5e7581788c 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp +++ b/examples/peripherals/usb/host/cdc/cdc_acm_bg96/main/cdc_acm_host_bg96.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ @@ -178,6 +178,7 @@ extern "C" void app_main(void) //Install USB Host driver. Should only be called once in entire application ESP_LOGI(TAG, "Installing USB Host"); usb_host_config_t host_config = { + .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c b/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c index 3f6c21d3d5..8534c4416c 100644 --- a/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c +++ b/examples/peripherals/usb/host/cdc/cdc_acm_host/main/usb-cdc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ @@ -53,6 +53,7 @@ void app_main(void) //Install USB Host driver. Should only be called once in entire application ESP_LOGI(TAG, "Installing USB Host"); usb_host_config_t host_config = { + .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c index fff3c91f5a..b1bf076238 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,6 +14,7 @@ #include "esp_log.h" #include "esp_err.h" +#include "esp_private/usb_phy.h" #include "usb/usb_host.h" #include "usb/cdc_acm_host.h" #include @@ -27,33 +28,32 @@ static uint8_t tx_buf[] = "HELLO"; static uint8_t tx_buf2[] = "WORLD"; static int nb_of_responses; static int nb_of_responses2; +static usb_phy_handle_t phy_hdl = NULL; -void test_usb_force_conn_state(bool connected, TickType_t delay_ticks) +static void force_conn_state(bool connected, TickType_t delay_ticks) { + TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl); if (delay_ticks > 0) { //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. vTaskDelay(delay_ticks); } - usb_wrap_dev_t *wrap = &USB_WRAP; - if (connected) { - //Disable test mode to return to previous internal PHY configuration - wrap->test_conf.test_enable = 0; - } else { - /* - Mimic a disconnection by using the internal PHY's test mode. - Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0, - this will look like a disconnection. - */ - wrap->test_conf.val = 0; - wrap->test_conf.test_usb_wrap_oe = 1; - wrap->test_conf.test_enable = 1; - } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); } void usb_lib_task(void *arg) { + //Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + .gpio_conf = NULL, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); // Install USB Host driver. Should only be called once in entire application const usb_host_config_t host_config = { + .skip_phy_setup = true, .intr_flags = ESP_INTR_FLAG_LEVEL1, }; TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config)); @@ -79,6 +79,9 @@ void usb_lib_task(void *arg) vTaskDelay(10); // Short delay to allow clients clean-up usb_host_lib_handle_events(0, NULL); // Make sure there are now pending events TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + //Tear down USB PHY + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); + phy_hdl = NULL; vTaskDelete(NULL); } @@ -290,11 +293,11 @@ TEST_CASE("USB Host CDC-ACM driver: Sudden disconnection test", "[cdc_acm][ignor TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); TEST_ASSERT_NOT_NULL(cdc_dev); - test_usb_force_conn_state(false, pdMS_TO_TICKS(10)); + force_conn_state(false, pdMS_TO_TICKS(10)); // Notify will succeed only if CDC_ACM_HOST_DEVICE_DISCONNECTED notification was generated TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(false, pdMS_TO_TICKS(100))); - test_usb_force_conn_state(true, 0); // Switch back to real PHY + force_conn_state(true, 0); // Switch back to real PHY TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); vTaskDelay(20); //Short delay to allow task to be cleaned up } diff --git a/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c b/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c index ad4b3f642f..402b4fcf09 100644 --- a/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c +++ b/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -363,7 +363,6 @@ esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle msc_device->handle, msc_device->config.iface_num, 0) ); - MSC_GOTO_ON_ERROR( msc_mass_reset(msc_device) ); MSC_GOTO_ON_ERROR( msc_get_max_lun(msc_device, &lun) ); MSC_GOTO_ON_ERROR( scsi_cmd_inquiry(msc_device) ); MSC_GOTO_ON_ERROR( msc_wait_for_ready_state(msc_device, WAIT_FOR_READY_TIMEOUT_MS) ); @@ -432,7 +431,7 @@ static esp_err_t msc_read_string_desc(msc_device_t *dev, uint8_t index, wchar_t } usb_transfer_t *xfer = dev->xfer; - USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)xfer->data_buffer, index, 64); + USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)xfer->data_buffer, index, 0x409, 64); MSC_RETURN_ON_ERROR( msc_control_transfer(dev, xfer, USB_SETUP_PACKET_SIZE + 64) ); usb_standard_desc_t *desc = (usb_standard_desc_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE); @@ -469,7 +468,14 @@ esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_dev esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device) { - return usb_print_descriptors(((msc_device_t *)device)->handle, NULL); + msc_device_t *dev = (msc_device_t *)device; + const usb_device_desc_t *device_desc; + const usb_config_desc_t *config_desc; + MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &device_desc) ); + MSC_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(dev->handle, &config_desc) ); + usb_print_device_descriptor(device_desc); + usb_print_config_descriptor(config_desc, NULL); + return ESP_OK; } static void transfer_callback(usb_transfer_t *transfer) diff --git a/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c b/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c index 98d47aac69..58adb6369c 100644 --- a/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c +++ b/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,7 @@ #include "freertos/semphr.h" #include "esp_err.h" #include "esp_log.h" +#include "esp_private/usb_phy.h" #include "usb/usb_host.h" #include "msc_host.h" #include "msc_host_vfs.h" @@ -45,27 +46,16 @@ static SemaphoreHandle_t ready_to_deinit_usb; static msc_host_device_handle_t device; static msc_host_vfs_handle_t vfs_handle; static volatile bool waiting_for_sudden_disconnect; +static usb_phy_handle_t phy_hdl = NULL; -static void test_usb_force_conn_state(bool connected, TickType_t delay_ticks) +static void force_conn_state(bool connected, TickType_t delay_ticks) { + TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl); if (delay_ticks > 0) { //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. vTaskDelay(delay_ticks); } - usb_wrap_dev_t *wrap = &USB_WRAP; - if (connected) { - //Disable test mode to return to previous internal PHY configuration - wrap->test_conf.test_enable = 0; - } else { - /* - Mimic a disconnection by using the internal PHY's test mode. - Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0, - this will look like a disconnection. - */ - wrap->test_conf.val = 0; - wrap->test_conf.test_usb_wrap_oe = 1; - wrap->test_conf.test_enable = 1; - } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); } static void msc_event_cb(const msc_host_event_t *event, void *arg) @@ -173,7 +163,7 @@ static void check_sudden_disconnect(void) ESP_LOGI(TAG, "Trigger a disconnect"); //Trigger a disconnect waiting_for_sudden_disconnect = true; - test_usb_force_conn_state(false, 0); + force_conn_state(false, 0); // Make sure flag was leared in callback vTaskDelay( pdMS_TO_TICKS(100) ); @@ -193,7 +183,19 @@ static void msc_setup(void) TEST_ASSERT( app_queue = xQueueCreate(5, sizeof(msc_host_event_t)) ); - const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 }; + //Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + .gpio_conf = NULL, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; ESP_OK_ASSERT( usb_host_install(&host_config) ); task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL); @@ -229,6 +231,9 @@ static void msc_teardown(void) xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY); vSemaphoreDelete(ready_to_deinit_usb); ESP_OK_ASSERT( usb_host_uninstall() ); + //Tear down USB PHY + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); + phy_hdl = NULL; vQueueDelete(app_queue); } diff --git a/examples/peripherals/usb/host/msc/main/msc_example_main.c b/examples/peripherals/usb/host/msc/main/msc_example_main.c index 6514f9174d..4a3d7371dd 100644 --- a/examples/peripherals/usb/host/msc/main/msc_example_main.c +++ b/examples/peripherals/usb/host/msc/main/msc_example_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -133,7 +133,10 @@ void app_main(void) app_queue = xQueueCreate(3, sizeof(msc_host_event_t)); assert(app_queue); - const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 }; + const usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; ESP_ERROR_CHECK( usb_host_install(&host_config) ); task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL); diff --git a/examples/peripherals/usb/host/usb_host_lib/CMakeLists.txt b/examples/peripherals/usb/host/usb_host_lib/CMakeLists.txt new file mode 100644 index 0000000000..6b59337fc5 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(usb_host_lib_example) diff --git a/examples/peripherals/usb/host/usb_host_lib/README.md b/examples/peripherals/usb/host/usb_host_lib/README.md new file mode 100644 index 0000000000..f6b746c7a7 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/README.md @@ -0,0 +1,182 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# USB Host Library Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates the basic usage of the [USB Host Library API](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html) by implementing a pseudo class driver and a Host Library daemon task. The example does the following: + +1. Install Host Library and register a client +2. Waits for a device connection +3. Prints the device's information (such as device/configuration/string descriptors) +4. Waits for the device to disconnect +5. Deregister the client and uninstall the Host Library + +The example demonstrates the following aspects of the USB Host Library API: + +- How to use the Library API to: + - Install and uninstall the USB Host Library + - Run the library event handler function a daemon task + - How to handle library events +- How to use the Client API from a client task to: + - Register and deregister a client of the USB Host Library + - Run the client event handler functions + - How to handle client events via various callbacks + - Open and close a device + - Get a device's descriptors + +## How to use example + +### Hardware Required + +An ESP board that supports USB-OTG. The example uses the ESP's internal USB PHY, however the internal USB PHY's pins will need to be connected to a USB port (i.e., a USB breakout board) as follows: + +- GND and 5V signals of the ESP board to the GND and 5V lines of the USB port +- GPIO 19 to D- +- GPIO 20 to D+ + +### Configure the project + +``` +idf.py menuconfig +``` + +* The USB Host Stack has a maximum supported transfer size for control transfer during device enumeration. This size is specified via the USB_HOST_CONTROL_TRANSFER_MAX_SIZE configuration option and has a default value of 256 bytes. Therefore, if devices with length config/string descriptors are used, users may want to increase the size of this configuration. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (261) cpu_start: Starting scheduler on PRO CPU. +I (267) DAEMON: Installing USB Host Library +I (297) CLASS: Registering Client +I (5067) CLASS: Opening device at address 1 +I (5067) CLASS: Getting device information +I (5067) CLASS: Full speed +I (5067) CLASS: bConfigurationValue 1 +I (5067) CLASS: Getting device descriptor +*** Device descriptor *** +bLength 18 +bDescriptorType 1 +bcdUSB 2.00 +bDeviceClass 0xef +bDeviceSubClass 0x2 +bDeviceProtocol 0x1 +bMaxPacketSize0 64 +idVendor 0x303a +idProduct 0x1001 +bcdDevice 1.00 +iManufacturer 1 +iProduct 2 +iSerialNumber 3 +bNumConfigurations 1 +I (5097) CLASS: Getting config descriptor +*** Configuration descriptor *** +bLength 9 +bDescriptorType 2 +wTotalLength 98 +bNumInterfaces 3 +bConfigurationValue 1 +iConfiguration 0 +bmAttributes 0xc0 +bMaxPower 500mA + *** Interface descriptor *** + bLength 9 + bDescriptorType 4 + bInterfaceNumber 0 + bAlternateSetting 0 + bNumEndpoints 1 + bInterfaceClass 0x0 + iInterface 0 + *** Endpoint descriptor *** + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x82 EP 2 IN + bmAttributes 0x3 INT + wMaxPacketSize 64 + bInterval 1 + *** Interface descriptor *** + bLength 9 + bDescriptorType 4 + bInterfaceNumber 1 + bAlternateSetting 0 + bNumEndpoints 2 + bInterfaceClass 0x0 + iInterface 0 + *** Endpoint descriptor *** + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x1 EP 1 OUT + bmAttributes 0x2 BULK + wMaxPacketSize 64 + bInterval 1 + *** Endpoint descriptor *** + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x81 EP 1 IN + bmAttributes 0x2 BULK + wMaxPacketSize 64 + bInterval 1 + *** Interface descriptor *** + bLength 9 + bDescriptorType 4 + bInterfaceNumber 2 + bAlternateSetting 0 + bNumEndpoints 2 + bInterfaceClass 0x1 + iInterface 0 + *** Endpoint descriptor *** + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x2 EP 2 OUT + bmAttributes 0x2 BULK + wMaxPacketSize 64 + bInterval 1 + *** Endpoint descriptor *** + bLength 7 + bDescriptorType 5 + bEndpointAddress 0x83 EP 3 IN + bmAttributes 0x2 BULK + wMaxPacketSize 64 + bInterval 1 +I (5227) CLASS: Getting Manufacturer string descriptor +Espressif +I (5237) CLASS: Getting Product string descriptor +USB JTAG/serial debug unit +I (5247) CLASS: Getting Serial Number string descriptor +7C:DF:A1:E0:10:50 +``` + +## Troubleshooting + +To obtain more debug, users should set the [log level](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/log.html) to debug via menuconfig. + +### Failing Enumeration + +``` +I (262) cpu_start: Starting scheduler on PRO CPU. +I (268) DAEMON: Installing USB Host Library +I (298) CLASS: Registering Client +E (2748) HUB: Short string desc corrupt +E (2748) HUB: Stage failed: CHECK_SHORT_MANU_STR_DESC +``` + +The log output demonstrates a device that has failed. The Hub Driver will output some error logs indicating which stage of enumeration has failed. + +### Blank String Descriptors + +The current USB Host Library will automatically cache the Manufacturer, Product, and Serial Number string descriptors of the device during enumeration. However, when fetching the string descriptors, the USB Host Library will only fetch those strings descriptors of they of LANGID code 0x0409 (i.e., English - United States). Therefore, if the example does not print a particular descriptor, it is likely that the string descriptor was not cached during enumeration. \ No newline at end of file diff --git a/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt b/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt new file mode 100644 index 0000000000..62648acbe0 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c new file mode 100644 index 0000000000..15ae3201f8 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c @@ -0,0 +1,188 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "usb/usb_host.h" + +#define CLIENT_NUM_EVENT_MSG 5 + +#define ACTION_OPEN_DEV 0x01 +#define ACTION_GET_DEV_INFO 0x02 +#define ACTION_GET_DEV_DESC 0x04 +#define ACTION_GET_CONFIG_DESC 0x08 +#define ACTION_GET_STR_DESC 0x10 +#define ACTION_CLOSE_DEV 0x20 +#define ACTION_EXIT 0x40 + +typedef struct { + usb_host_client_handle_t client_hdl; + uint8_t dev_addr; + usb_device_handle_t dev_hdl; + uint32_t actions; +} class_driver_t; + +static const char *TAG = "CLASS"; + +static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + class_driver_t *driver_obj = (class_driver_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + if (driver_obj->dev_addr == 0) { + driver_obj->dev_addr = event_msg->new_dev.address; + //Open the device next + driver_obj->actions |= ACTION_OPEN_DEV; + } + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + if (driver_obj->dev_hdl != NULL) { + //Cancel any other actions and close the device next + driver_obj->actions = ACTION_CLOSE_DEV; + } + break; + default: + //Should never occur + abort(); + } +} + +static void action_open_dev(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_addr != 0); + ESP_LOGI(TAG, "Opening device at address %d", driver_obj->dev_addr); + ESP_ERROR_CHECK(usb_host_device_open(driver_obj->client_hdl, driver_obj->dev_addr, &driver_obj->dev_hdl)); + //Get the device's information next + driver_obj->actions &= ~ACTION_OPEN_DEV; + driver_obj->actions |= ACTION_GET_DEV_INFO; +} + +static void action_get_info(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(TAG, "Getting device information"); + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + ESP_LOGI(TAG, "\t%s speed", (dev_info.speed == USB_SPEED_LOW) ? "Low" : "Full"); + ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue); + //Todo: Print string descriptors + + //Get the device descriptor next + driver_obj->actions &= ~ACTION_GET_DEV_INFO; + driver_obj->actions |= ACTION_GET_DEV_DESC; +} + +static void action_get_dev_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(TAG, "Getting device descriptor"); + const usb_device_desc_t *dev_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(driver_obj->dev_hdl, &dev_desc)); + usb_print_device_descriptor(dev_desc); + //Get the device's config descriptor next + driver_obj->actions &= ~ACTION_GET_DEV_DESC; + driver_obj->actions |= ACTION_GET_CONFIG_DESC; +} + +static void action_get_config_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(TAG, "Getting config descriptor"); + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(driver_obj->dev_hdl, &config_desc)); + usb_print_config_descriptor(config_desc, NULL); + //Get the device's string descriptors next + driver_obj->actions &= ~ACTION_GET_CONFIG_DESC; + driver_obj->actions |= ACTION_GET_STR_DESC; +} + +static void action_get_str_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + if (dev_info.str_desc_manufacturer) { + ESP_LOGI(TAG, "Getting Manufacturer string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_manufacturer); + } + if (dev_info.str_desc_product) { + ESP_LOGI(TAG, "Getting Product string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_product); + } + if (dev_info.str_desc_serial_num) { + ESP_LOGI(TAG, "Getting Serial Number string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_serial_num); + } + //Nothing to do until the device disconnects + driver_obj->actions &= ~ACTION_GET_STR_DESC; +} + +static void aciton_close_dev(class_driver_t *driver_obj) +{ + ESP_ERROR_CHECK(usb_host_device_close(driver_obj->client_hdl, driver_obj->dev_hdl)); + driver_obj->dev_hdl = NULL; + driver_obj->dev_addr = 0; + //We need to exit the event handler loop + driver_obj->actions &= ~ACTION_CLOSE_DEV; + driver_obj->actions |= ACTION_EXIT; +} + +void class_driver_task(void *arg) +{ + SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; + class_driver_t driver_obj = {0}; + + //Wait until daemon task has installed USB Host Library + xSemaphoreTake(signaling_sem, portMAX_DELAY); + + ESP_LOGI(TAG, "Registering Client"); + usb_host_client_config_t client_config = { + .is_synchronous = false, //Synchronous clients currently not supported. Set this to false + .max_num_event_msg = CLIENT_NUM_EVENT_MSG, + .async = { + .client_event_callback = client_event_cb, + .callback_arg = (void *)&driver_obj, + }, + }; + ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl)); + + while (1) { + if (driver_obj.actions == 0) { + usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY); + } else { + if (driver_obj.actions & ACTION_OPEN_DEV) { + action_open_dev(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_DEV_INFO) { + action_get_info(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_DEV_DESC) { + action_get_dev_desc(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_CONFIG_DESC) { + action_get_config_desc(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_STR_DESC) { + action_get_str_desc(&driver_obj); + } + if (driver_obj.actions & ACTION_CLOSE_DEV) { + aciton_close_dev(&driver_obj); + } + if (driver_obj.actions & ACTION_EXIT) { + break; + } + } + } + + ESP_LOGI(TAG, "Deregistering Client"); + ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl)); + + //Wait to be deleted + xSemaphoreGive(signaling_sem); + vTaskSuspend(NULL); +} 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 new file mode 100644 index 0000000000..9ee388a284 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_intr_alloc.h" +#include "usb/usb_host.h" + +#define DAEMON_TASK_PRIORITY 2 +#define CLASS_TASK_PRIORITY 3 + +extern void class_driver_task(void *arg); + +static const char *TAG = "DAEMON"; + +static void host_lib_daemon_task(void *arg) +{ + SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; + + ESP_LOGI(TAG, "Installing USB Host Library"); + usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + //Signal to the class driver task that the host library is installed + xSemaphoreGive(signaling_sem); + vTaskDelay(10); //Short delay to let client task spin up + + bool has_clients = true; + bool has_devices = true; + while (has_clients || has_devices ) { + uint32_t event_flags; + ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags)); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + has_clients = false; + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + has_devices = false; + } + } + ESP_LOGI(TAG, "No more clients and devices"); + + //Uninstall the USB Host Library + ESP_ERROR_CHECK(usb_host_uninstall()); + //Wait to be deleted + xSemaphoreGive(signaling_sem); + vTaskSuspend(NULL); +} + +void app_main(void) +{ + SemaphoreHandle_t signaling_sem = xSemaphoreCreateBinary(); + + TaskHandle_t daemon_task_hdl; + TaskHandle_t class_driver_task_hdl; + //Create daemon task + xTaskCreatePinnedToCore(host_lib_daemon_task, + "daemon", + 4096, + (void *)signaling_sem, + DAEMON_TASK_PRIORITY, + &daemon_task_hdl, + 0); + //Create the class driver task + xTaskCreatePinnedToCore(class_driver_task, + "class", + 4096, + (void *)signaling_sem, + CLASS_TASK_PRIORITY, + &class_driver_task_hdl, + 0); + + vTaskDelay(10); //Add a short delay to let the tasks run + + //Wait for the tasks to complete + for (int i = 0; i < 2; i++) { + xSemaphoreTake(signaling_sem, portMAX_DELAY); + } + + //Delete the tasks + vTaskDelete(class_driver_task_hdl); + vTaskDelete(daemon_task_hdl); +}