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!16678
pull/8460/head
morris 2022-03-02 16:49:38 +08:00
commit 2e8df440c1
42 zmienionych plików z 2676 dodań i 852 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Plik diff jest za duży Load Diff

Wyświetl plik

@ -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
*

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

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

Wyświetl plik

@ -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

Wyświetl plik

@ -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
*/

Wyświetl plik

@ -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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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

Wyświetl plik

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

Wyświetl plik

@ -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

Wyświetl plik

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

Wyświetl plik

@ -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
}

Wyświetl plik

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

Wyświetl plik

@ -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:

Wyświetl plik

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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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 ---------------------------------------------------------

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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
-------------

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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.

Wyświetl plik

@ -0,0 +1,2 @@
idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c"
INCLUDE_DIRS ".")

Wyświetl plik

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

Wyświetl plik

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