kopia lustrzana https://github.com/espressif/esp-idf
Merge branch 'feature/usb_host_cleanup_v4.4' into 'release/v4.4'
USB: Host stack cleanup and QOL update + wTotalLength Fix + omit MSC reset (v4.4.1) See merge request espressif/esp-idf!16678pull/8460/head
commit
2e8df440c1
|
@ -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 <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
1062
components/usb/hub.c
1062
components/usb/hub.c
Plik diff jest za duży
Load Diff
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <stdbool.h>
|
||||
#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));
|
||||
}
|
||||
|
|
|
@ -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 <stdbool.h>
|
||||
#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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 <stdio.h>
|
||||
#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
|
||||
}
|
||||
|
|
|
@ -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 ---------------------------------------------------------
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 64 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 24 KiB |
|
@ -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 <string.h>
|
||||
#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
|
||||
-------------
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 <string.h>
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c"
|
||||
INCLUDE_DIRS ".")
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#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);
|
||||
}
|
|
@ -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);
|
||||
}
|
Ładowanie…
Reference in New Issue