Add USB Host Library

This commit adds the preliminary version of the USB Host Library. This commit contains:

- USBH (USB Host Driver)
- Hub Driver that only supports a single device and device enumeration
- USB Host Library (asychronous API)
- Test cases for USB Host Library asychronous API

The following changes were made to the existing HCD:
- Removed HCD_PIPE_STATE_INVALID. Pipes are no longer invalidated
- Changed pipe commands to halt, flush, and clear. Pipes need to be manually
  halted, flush, and cleared.
- Halting and flushing a pipe will execute the pipe callback if it causes a
  HCD_PIPE_EVENT_URB_DONE event
This commit is contained in:
Darian Leung 2021-08-24 23:20:50 +08:00
parent da12db2904
commit accbaee57c
37 changed files with 6795 additions and 1575 deletions

View File

@ -16,7 +16,7 @@
Note: This header file contains USB2.0 related types and macros that can be used by code specific to the DWC_OTG
controller (i.e., the HW specific layers of the USB host stack). Thus, this header is only meant to be used below (and
including) the HAL layer. For types and macros that are HW implementation agnostic (i.e., HCD layer and above), add them
to the "usb.h" header instead.
to the "usb/usb_types_ch9.h" header instead.
*/
#pragma once

View File

@ -151,7 +151,7 @@ typedef struct {
//Channel control, status, and information
union {
struct {
uint32_t active: 1; /**< The channel is enabled */
uint32_t active: 1; /**< Debugging bit to indicate whether channel is enabled */
uint32_t halt_requested: 1; /**< A halt has been requested */
uint32_t error_pending: 1; /**< The channel is waiting for the error to be handled */
uint32_t reserved: 1;
@ -811,6 +811,8 @@ usbh_hal_chan_t *usbh_hal_get_chan_pending_intr(usbh_hal_context_t *hal);
* - Returns the corresponding event for that channel
*
* @param chan_obj Channel object
* @note If the host port has an error (e.g., a sudden disconnect or an port error), any active channels will not
* receive an interrupt. Each active channel must be manually halted.
* @return usbh_hal_chan_event_t Channel event
*/
usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj);

View File

@ -291,27 +291,19 @@ void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int
bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj)
{
//Cannot request halt on a channel that is pending error handling
HAL_ASSERT(!chan_obj->flags.error_pending);
if (usbh_ll_chan_is_active(chan_obj->regs) || chan_obj->flags.active) {
HAL_ASSERT(chan_obj->flags.active && !chan_obj->flags.error_pending);
if (usbh_ll_chan_is_active(chan_obj->regs)) {
usbh_ll_chan_halt(chan_obj->regs);
chan_obj->flags.halt_requested = 1;
return false;
} else {
chan_obj->flags.active = 0;
return true;
}
return true;
}
// ------------------------------------------------- Event Handling ----------------------------------------------------
//When a device on the port is no longer valid (e.g., disconnect, port error). All channels are no longer valid
static void chan_all_halt(usbh_hal_context_t *hal)
{
for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) {
if (hal->channels.hdls[i] != NULL) {
hal->channels.hdls[i]->flags.active = 0;
}
}
}
usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
{
uint32_t intrs_core = usb_ll_intr_read_and_clear(hal->dev); //Read and clear core interrupts
@ -321,7 +313,7 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
intrs_port = usbh_ll_hprt_intr_read_and_clear(hal->dev);
}
//Note: Do not change order of checks. Regressing events (e.g. enable -> disabled, connected -> connected)
//always take precendance. ENABLED < DISABLED < CONN < DISCONN < OVRCUR
//always take precedence. ENABLED < DISABLED < CONN < DISCONN < OVRCUR
usbh_hal_port_event_t event = USBH_HAL_PORT_EVENT_NONE;
//Check if this is a core or port event
@ -330,13 +322,11 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
if (intrs_core & USB_LL_INTR_CORE_DISCONNINT) {
event = USBH_HAL_PORT_EVENT_DISCONN;
debounce_lock_enable(hal);
chan_all_halt(hal); //All channels are halted on a disconnect
//Mask the port connection and disconnection interrupts to prevent repeated triggering
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTOVRCURRCHNG) {
//Check if this is an overcurrent or an overcurrent cleared
if (usbh_ll_hprt_get_port_overcur(hal->dev)) {
event = USBH_HAL_PORT_EVENT_OVRCUR;
chan_all_halt(hal); //All channels are halted on an overcurrent
} else {
event = USBH_HAL_PORT_EVENT_OVRCUR_CLR;
}
@ -345,14 +335,13 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
event = USBH_HAL_PORT_EVENT_ENABLED;
} else { //Host port has been disabled
event = USBH_HAL_PORT_EVENT_DISABLED;
chan_all_halt(hal); //All channels are halted when the port is disabled
}
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTCONNDET && !hal->flags.dbnc_lock_enabled) {
event = USBH_HAL_PORT_EVENT_CONN;
debounce_lock_enable(hal);
}
}
//Port events always take precendance over channel events
//Port events always take precedence over channel events
if (event == USBH_HAL_PORT_EVENT_NONE && (intrs_core & USB_LL_INTR_CORE_HCHINT)) {
//One or more channels have pending interrupts. Store the mask of those channels
hal->channels.chan_pend_intrs_msk = usbh_ll_get_chan_intrs_msk(hal->dev);

View File

@ -4,7 +4,13 @@ set(priv_include)
set(priv_require)
if(CONFIG_USB_OTG_SUPPORTED)
list(APPEND srcs "hcd.c")
list(APPEND srcs "hcd.c"
"hub.c"
"usb_host_misc.c"
"usb_host.c"
"usb_private.c"
"usbh.c")
list(APPEND include "include")
list(APPEND priv_include "private_include")
list(APPEND priv_require "hal" "driver")
endif()

View File

@ -6,4 +6,51 @@ menu "USB-OTG"
bool
default y if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
endmenu
config USB_HOST_CONTROL_TRANSFER_MAX_SIZE
depends on USB_OTG_SUPPORTED
int "Largest size (in bytes) of transfers to/from default endpoints"
default 256
help
Each USB device attached is allocated a dedicated buffer for its OUT/IN transfers to/from the device's
control endpoint. The maximum size of that buffer is determined by this option. The limited size of the
transfer buffer have the following implications:
- The maximum length of control transfers is limited
- Device's with configuration descriptors larger than this limit cannot be supported
choice USB_HOST_HW_BUFFER_BIAS
depends on USB_OTG_SUPPORTED
prompt "Hardware FIFO size biasing"
default USB_HOST_HW_BUFFER_BIAS_BALANCED
help
The underlying hardware has size adjustable FIFOs to cache USB packets on reception (IN) or for
transmission (OUT). The size of these FIFOs will affect the largest MPS (maximum packet size) and the
maximum number of packets that can be cached at any one time. The hardware contains the following
FIFOS: RX (for all IN packets), Non-periodic TX (for Bulk and Control OUT packets), and Periodic TX
(for Interrupt and Isochronous OUT packets). This configuration option allows biasing the FIFO sizes
towards a particular use case, which may be necessary for devices that have endpoints with large MPS.
The MPS limits for each biasing are listed below:
Balanced:
- IN (all transfer types), 408 bytes
- OUT non-periodic (Bulk/Control), 192 bytes (i.e., 3 x 64 byte packets)
- OUT periodic (Interrupt/Isochronous), 192 bytes
Bias IN:
- IN (all transfer types), 600 bytes
- OUT non-periodic (Bulk/Control), 64 bytes (i.e., 1 x 64 byte packets)
- OUT periodic (Interrupt/Isochronous), 128 bytes
Bias Periodic OUT:
- IN (all transfer types), 128 bytes
- OUT non-periodic (Bulk/Control), 64 bytes (i.e., 1 x 64 byte packets)
- OUT periodic (Interrupt/Isochronous), 600 bytes
config USB_HOST_HW_BUFFER_BIAS_BALANCED
bool "Balanced"
config USB_HOST_HW_BUFFER_BIAS_IN
bool "Bias IN"
config USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT
bool "Periodic OUT"
endchoice
endmenu #USB-OTG

File diff suppressed because it is too large Load Diff

668
components/usb/hub.c Normal file
View File

@ -0,0 +1,668 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "usb_private.h"
#include "hcd.h"
#include "hub.h"
/*
Implementation of the HUB driver that only supports the root hub with a single port. Therefore, we currently don't
implement the bare minimum to control the root HCD port.
*/
#define HUB_ROOT_PORT_NUM 1 //HCD only supports one port
#ifdef CONFIG_USB_HOST_HW_BUFFER_BIAS_IN
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_RX
#elif CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_PTX
#else //CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED
#define HUB_ROOT_HCD_PORT_FIFO_BIAS HCD_PORT_FIFO_BIAS_BALANCED
#endif
#define ENUM_CTRL_TRANSFER_MAX_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
#define ENUM_DEV_ADDR 1 //Device address used in enumeration
#define ENUM_CONFIG_INDEX 0 //Index of the first configuration of the device
#define ENUM_DEV_DESC_REQ_SIZE 64 //Worst case size for device descriptor request
#define ENUM_LOW_SPEED_MPS 8 //Worst case MPS for the default endpoint of a low-speed device
#define ENUM_FULL_SPEED_MPS 64 //Worst case MPS for the default endpoint of a full-speed device
#define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01
#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x02
#define HUB_DRIVER_FLAG_ACTION_PORT_RECOVER 0x04
typedef enum {
HUB_DRIVER_STATE_INSTALLED,
HUB_DRIVER_STATE_ROOT_POWERD,
HUB_DRIVER_STATE_ROOT_ENUMERATING,
HUB_DRIVER_STATE_ROOT_ACTIVE,
HUB_DRIVER_STATE_ROOT_RECOVERY,
} hub_driver_state_t;
typedef enum {
ENUM_STAGE_NONE, /**< There is no device awaiting enumeration */
ENUM_STAGE_START, /**< Start of enumeration. Allocates device object */
ENUM_STAGE_GET_SHORT_DEV_DESC, /**< Getting short device descriptor */
ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK, /**< Check that short device descriptor was obtained */
ENUM_STAGE_SECOND_RESET, /**< Doing second reset */
ENUM_STAGE_SET_ADDR, /**< Setting address */
ENUM_STAGE_SET_ADDR_CHECK, /**< Check that address was set successful */
ENUM_STAGE_GET_FULL_DEV_DESC, /**< Getting full device descriptor */
ENUM_STAGE_GET_FULL_DEV_DESC_CHECK, /**< Check that full device descriptor was obtained */
ENUM_STAGE_GET_CONFIG_DESC, /**< Getting full configuration descriptor */
ENUM_STAGE_GET_CONFIG_DESC_CHECK, /**< Check that full configuration descriptor was obtained */
ENUM_STAGE_SET_CONFIG, /**< Set configuration number */
ENUM_STAGE_SET_CONFIG_CHECK, /**< Check that configuration number was set */
ENUM_STAGE_CLEANUP, /**< Clean up successful enumeration. Adds enumerated device to USBH */
ENUM_STAGE_CLEANUP_FAILED, /**< Cleanup failed enumeration. Free device resources */
} enum_stage_t;
typedef struct {
//Dynamic members require a critical section
struct {
union {
struct {
uint32_t actions: 8;
uint32_t reserved24: 24;
};
uint32_t val;
} flags;
hub_driver_state_t driver_state;
usb_device_handle_t root_dev_hdl; //Indicates if an enumerated device is connected to the root port
} dynamic;
//Single thread members don't require a critical section so long as they are never accessed from multiple threads
struct {
enum_stage_t enum_stage;
urb_t *enum_urb;
usb_device_handle_t enum_dev_hdl; //Handle of the device being enumerated. Moves to root_dev_hdl on enumeration completion
hcd_pipe_handle_t enum_dflt_pipe_hdl;
} single_thread;
//Constant members do no change after installation thus do not require a critical section
struct {
hcd_port_handle_t root_port_hdl;
usb_notif_cb_t notif_cb;
void *notif_cb_arg;
} constant;
} hub_driver_t;
static hub_driver_t *p_hub_driver_obj = NULL;
static portMUX_TYPE hub_driver_lock = portMUX_INITIALIZER_UNLOCKED;
const char *HUB_DRIVER_TAG = "HUB";
#define HUB_DRIVER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&hub_driver_lock)
#define HUB_DRIVER_ENTER_CRITICAL() portENTER_CRITICAL(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL() portEXIT_CRITICAL(&hub_driver_lock)
#define HUB_DRIVER_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&hub_driver_lock)
#define HUB_DRIVER_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&hub_driver_lock)
#define HUB_DRIVER_CHECK(cond, ret_val) ({ \
if (!(cond)) { \
return (ret_val); \
} \
})
#define HUB_DRIVER_CHECK_FROM_CRIT(cond, ret_val) ({ \
if (!(cond)) { \
HUB_DRIVER_EXIT_CRITICAL(); \
return ret_val; \
} \
})
// ------------------------------------------------- Event Handling ----------------------------------------------------
// ---------------------- Callbacks ------------------------
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr)
{
HUB_DRIVER_ENTER_CRITICAL_SAFE();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ROOT_EVENT;
HUB_DRIVER_EXIT_CRITICAL_SAFE();
bool yield;
if (in_isr) {
p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg);
} else {
yield = false;
}
return yield;
}
static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
{
//Note: This callback may have triggered when a failed enumeration is already cleaned up (e.g., due to a failed port reset)
HUB_DRIVER_ENTER_CRITICAL_SAFE();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
HUB_DRIVER_EXIT_CRITICAL_SAFE();
return p_hub_driver_obj->constant.notif_cb(USB_NOTIF_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.notif_cb_arg);
}
static void usbh_hub_callback(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg)
{
//We currently only support the root port
assert(port_hdl == p_hub_driver_obj->constant.root_port_hdl);
HUB_DRIVER_ENTER_CRITICAL();
//Any hub_event results in whatever device connected to the root port to no longer be valid. We clear root_dev_hdl here.
usb_device_handle_t dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl;
p_hub_driver_obj->dynamic.root_dev_hdl = NULL;
assert(dev_hdl);
bool call_port_disable = false;
switch (hub_event) {
case USBH_HUB_EVENT_CLEANUP_PORT:
//After USBH has cleaned up gone device. The port can be recovered.
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER;
break;
case USBH_HUB_EVENT_DISABLE_PORT:
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD;
call_port_disable = true;
break;
default:
abort(); //Should never occur
break;
}
HUB_DRIVER_EXIT_CRITICAL();
if (call_port_disable) {
ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port");
hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE);
ESP_ERROR_CHECK(usbh_hub_dev_port_disabled(dev_hdl));
}
}
// ---------------------- Handlers -------------------------
static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
{
hcd_port_event_t port_event = hcd_port_handle_event(root_port_hdl);
switch (port_event) {
case HCD_PORT_EVENT_NONE:
//Nothing to do
break;
case HCD_PORT_EVENT_CONNECTION: {
if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) == ESP_OK) {
ESP_LOGD(HUB_DRIVER_TAG, "Root port reset");
//Start enumeration
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ENUMERATING;
HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_START;
} else {
ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed");
}
break;
}
case HCD_PORT_EVENT_DISCONNECTION:
case HCD_PORT_EVENT_ERROR:
case HCD_PORT_EVENT_OVERCURRENT: {
usb_device_handle_t dev_hdl = NULL;
HUB_DRIVER_ENTER_CRITICAL();
switch (p_hub_driver_obj->dynamic.driver_state) {
case HUB_DRIVER_STATE_ROOT_POWERD:
//This occurred before enumeration. Therefore, there's no device and we can go straight to port recovery
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
break;
case HUB_DRIVER_STATE_ROOT_ENUMERATING:
//This occurred during enumeration. Therefore, we need to recover the failed enumeration
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
p_hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_CLEANUP_FAILED;
break;
case HUB_DRIVER_STATE_ROOT_ACTIVE:
//There was an enumerated device. We need to indicate to USBH that the device is gone
dev_hdl = p_hub_driver_obj->dynamic.root_dev_hdl;
break;
default:
abort(); //Should never occur
break;
}
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_RECOVERY;
HUB_DRIVER_EXIT_CRITICAL();
if (dev_hdl) {
ESP_ERROR_CHECK(usbh_hub_mark_dev_gone(dev_hdl));
}
break;
}
default:
abort(); //Should never occur
break;
}
}
// ------------------------------------------------- Enum Functions ----------------------------------------------------
static enum_stage_t enum_stage_start(void)
{
usb_speed_t speed;
if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) {
return ENUM_STAGE_NONE;
}
usb_device_handle_t enum_dev_hdl;
hcd_pipe_handle_t enum_dflt_pipe_hdl;
//We use NULL as the parent device to indicate the root hub port 1. We currently only support a single device
if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) {
return ENUM_STAGE_NONE;
}
//Set our own default pipe callback
ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL));
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->single_thread.enum_dev_hdl = enum_dev_hdl;
p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = enum_dflt_pipe_hdl;
HUB_DRIVER_EXIT_CRITICAL();
ESP_LOGD(HUB_DRIVER_TAG, "Enumeration starting");
return ENUM_STAGE_GET_SHORT_DEV_DESC;
}
static enum_stage_t enum_stage_get_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer);
enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE;
if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to get short device descriptor");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Getting short device descriptor");
return ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK;
}
static enum_stage_t enum_stage_check_short_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
//Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl);
assert(dequeued_enum_urb == enum_urb);
if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor transfer failed");
return ENUM_STAGE_CLEANUP_FAILED;
}
usb_device_desc_t *device_desc = (usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + 8) { //Device must return at least 8 bytes in its data stage
ESP_LOGE(HUB_DRIVER_TAG, "Short device descriptor too short");
return ENUM_STAGE_CLEANUP_FAILED;
}
//Update the MPS of the default pipe
if (hcd_pipe_update_mps(pipe_hdl, device_desc->bMaxPacketSize0) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to update default pipe MPS");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Short device descriptor obtained");
return ENUM_STAGE_SECOND_RESET;
}
static enum_stage_t enum_stage_second_reset(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(pipe_hdl)); //Persist the default pipe through the reset
if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Second reset done");
return ENUM_STAGE_SET_ADDR;
}
static enum_stage_t enum_stage_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
USB_SETUP_PACKET_INIT_SET_ADDR((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_DEV_ADDR);
enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage
if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to set address");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Setting address %d", ENUM_DEV_ADDR);
return ENUM_STAGE_SET_ADDR_CHECK;
}
static enum_stage_t enum_stage_check_set_addr(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl)
{
//Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl);
assert(dequeued_enum_urb == enum_urb);
if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Set address transfer failed");
return ENUM_STAGE_CLEANUP_FAILED;
}
//Update the pipe and device's address, and fill the address into the device object
ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(pipe_hdl, ENUM_DEV_ADDR));
ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(dev_hdl, ENUM_DEV_ADDR));
ESP_LOGD(HUB_DRIVER_TAG, "Address set successfully");
return ENUM_STAGE_GET_FULL_DEV_DESC;
}
static enum_stage_t enum_stage_get_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
USB_SETUP_PACKET_INIT_GET_DEVICE_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer);
enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_DEV_DESC_REQ_SIZE;
if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to get full device descriptor");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Getting full device descriptor");
return ENUM_STAGE_GET_FULL_DEV_DESC_CHECK;
}
static enum_stage_t enum_stage_check_full_dev_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl)
{
//Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl);
assert(dequeued_enum_urb == enum_urb);
if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor transfer failed");
return ENUM_STAGE_CLEANUP_FAILED;
}
if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t)) {
ESP_LOGE(HUB_DRIVER_TAG, "Full device descriptor too short");
return ENUM_STAGE_CLEANUP_FAILED;
}
//Fill device descriptor into device
const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(dev_hdl, device_desc));
ESP_LOGD(HUB_DRIVER_TAG, "Full device descriptor obtained");
return ENUM_STAGE_GET_CONFIG_DESC;
}
static enum_stage_t enum_stage_get_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
//Get the configuration descriptor at index 0
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX, ENUM_CTRL_TRANSFER_MAX_LEN);
enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN;
if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to get configuration descriptor");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Getting configuration descriptor");
return ENUM_STAGE_GET_CONFIG_DESC_CHECK;
}
static enum_stage_t enum_stage_check_config_desc(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb, usb_device_handle_t dev_hdl)
{
//Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl);
assert(dequeued_enum_urb == enum_urb);
if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor transfer failed");
return ENUM_STAGE_CLEANUP_FAILED;
}
usb_config_desc_t *config_desc = (usb_config_desc_t *)(enum_urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
if (enum_urb->transfer.actual_num_bytes < sizeof(usb_setup_packet_t) + sizeof(usb_config_desc_t)) {
ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor too small");
return ENUM_STAGE_CLEANUP_FAILED;
}
if (config_desc->wTotalLength > ENUM_CTRL_TRANSFER_MAX_LEN) {
ESP_LOGE(HUB_DRIVER_TAG, "Configuration descriptor larger than control transfer max length");
return ENUM_STAGE_CLEANUP_FAILED;
}
//Fill configuration descriptor into device
ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(dev_hdl, config_desc));
return ENUM_STAGE_SET_CONFIG;
}
static enum_stage_t enum_stage_set_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
USB_SETUP_PACKET_INIT_SET_CONFIG((usb_setup_packet_t *)enum_urb->transfer.data_buffer, ENUM_CONFIG_INDEX + 1);
enum_urb->transfer.num_bytes = sizeof(usb_setup_packet_t); //No data stage
if (hcd_urb_enqueue(pipe_hdl, enum_urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to set configuration");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Setting configuration");
return ENUM_STAGE_SET_CONFIG_CHECK;
}
static enum_stage_t enum_stage_check_config(hcd_pipe_handle_t pipe_hdl, urb_t *enum_urb)
{
//Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(pipe_hdl);
assert(dequeued_enum_urb == enum_urb);
if (enum_urb->transfer.status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Set configuration transfer failed");
return ENUM_STAGE_CLEANUP_FAILED;
}
ESP_LOGD(HUB_DRIVER_TAG, "Configuration set successfully");
return ENUM_STAGE_CLEANUP;
}
static enum_stage_t enum_stage_cleanup(void)
{
//We currently only support a single device connected to the root port. Move the device handle from enum to root
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.root_dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl;
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_ACTIVE;
HUB_DRIVER_EXIT_CRITICAL();
usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl;
p_hub_driver_obj->single_thread.enum_dev_hdl = NULL;
p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL;
//Update device object after enumeration is done
ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl));
return ENUM_STAGE_NONE;
}
static enum_stage_t enum_stage_cleanup_failed(void)
{
//Enumeration failed. Clear the enum device handle and pipe
usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl;
if (p_hub_driver_obj->single_thread.enum_dev_hdl) {
//Halt, flush, and dequeue enum default pipe
ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_HALT));
ESP_ERROR_CHECK(hcd_pipe_command(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl, HCD_PIPE_CMD_FLUSH));
urb_t *dequeued_enum_urb = hcd_urb_dequeue(p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl);
assert(dequeued_enum_urb == p_hub_driver_obj->single_thread.enum_urb);
ESP_ERROR_CHECK(usbh_hub_enum_failed(dev_hdl)); //Free the underlying device object first before recovering the port
}
p_hub_driver_obj->single_thread.enum_dev_hdl = NULL;
p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl = NULL;
HUB_DRIVER_ENTER_CRITICAL();
//Enum could have failed due to a port error. If so, we need to trigger a port recovery
if (p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_ROOT_RECOVERY) {
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_RECOVER;
}
HUB_DRIVER_EXIT_CRITICAL();
return ENUM_STAGE_NONE;
}
static void enum_handle_events(void)
{
enum_stage_t cur_stage = p_hub_driver_obj->single_thread.enum_stage;
hcd_pipe_handle_t enum_dflt_pipe_hdl = p_hub_driver_obj->single_thread.enum_dflt_pipe_hdl;
urb_t *enum_urb = p_hub_driver_obj->single_thread.enum_urb;
usb_device_handle_t dev_hdl = p_hub_driver_obj->single_thread.enum_dev_hdl;
enum_stage_t next_stage;
switch (cur_stage) {
case ENUM_STAGE_START:
next_stage = enum_stage_start();
break;
case ENUM_STAGE_GET_SHORT_DEV_DESC:
next_stage = enum_stage_get_short_dev_desc(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_GET_SHORT_DEV_DESC_CHECK:
next_stage = enum_stage_check_short_dev_desc(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_SECOND_RESET:
next_stage = enum_stage_second_reset(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_SET_ADDR:
next_stage = enum_stage_set_addr(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_SET_ADDR_CHECK:
next_stage = enum_stage_check_set_addr(enum_dflt_pipe_hdl, enum_urb, dev_hdl);
break;
case ENUM_STAGE_GET_FULL_DEV_DESC:
next_stage = enum_stage_get_full_dev_desc(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_GET_FULL_DEV_DESC_CHECK:
next_stage = enum_stage_check_full_dev_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl);
break;
case ENUM_STAGE_GET_CONFIG_DESC:
next_stage = enum_stage_get_config_desc(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_GET_CONFIG_DESC_CHECK:
next_stage = enum_stage_check_config_desc(enum_dflt_pipe_hdl, enum_urb, dev_hdl);
break;
case ENUM_STAGE_SET_CONFIG:
next_stage = enum_stage_set_config(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_SET_CONFIG_CHECK:
next_stage = enum_stage_check_config(enum_dflt_pipe_hdl, enum_urb);
break;
case ENUM_STAGE_CLEANUP:
next_stage = enum_stage_cleanup();
break;
case ENUM_STAGE_CLEANUP_FAILED:
next_stage = enum_stage_cleanup_failed();
break;
default:
//Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup
next_stage = ENUM_STAGE_NONE;
break;
}
p_hub_driver_obj->single_thread.enum_stage = next_stage;
HUB_DRIVER_ENTER_CRITICAL();
if (next_stage == ENUM_STAGE_GET_SHORT_DEV_DESC ||
next_stage == ENUM_STAGE_SECOND_RESET ||
next_stage == ENUM_STAGE_SET_ADDR ||
next_stage == ENUM_STAGE_GET_FULL_DEV_DESC ||
next_stage == ENUM_STAGE_GET_CONFIG_DESC ||
next_stage == ENUM_STAGE_SET_CONFIG ||
next_stage == ENUM_STAGE_CLEANUP ||
next_stage == ENUM_STAGE_CLEANUP_FAILED) {
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
}
HUB_DRIVER_EXIT_CRITICAL();
}
// ---------------------------------------------- Hub Driver Functions -------------------------------------------------
esp_err_t hub_install(hub_config_t *hub_config)
{
HUB_DRIVER_ENTER_CRITICAL();
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE);
HUB_DRIVER_EXIT_CRITICAL();
//Allocate Hub driver object
hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT);
urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN, 0, 0);
if (hub_driver_obj == NULL || enum_urb == NULL) {
return ESP_ERR_NO_MEM;
}
esp_err_t ret;
//Install HCD port
hcd_port_config_t port_config = {
.fifo_bias = HUB_ROOT_HCD_PORT_FIFO_BIAS,
.callback = root_port_callback,
.callback_arg = NULL,
.context = NULL,
};
hcd_port_handle_t port_hdl;
ret = hcd_port_init(HUB_ROOT_PORT_NUM, &port_config, &port_hdl);
if (ret != ESP_OK) {
goto err;
}
//Initialize hub driver object
hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED;
hub_driver_obj->single_thread.enum_stage = ENUM_STAGE_NONE;
hub_driver_obj->single_thread.enum_urb = enum_urb;
hub_driver_obj->constant.root_port_hdl = port_hdl;
hub_driver_obj->constant.notif_cb = hub_config->notif_cb;
hub_driver_obj->constant.notif_cb_arg = hub_config->notif_cb_arg;
HUB_DRIVER_ENTER_CRITICAL();
if (p_hub_driver_obj != NULL) {
HUB_DRIVER_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto assign_err;
}
p_hub_driver_obj = hub_driver_obj;
HUB_DRIVER_EXIT_CRITICAL();
//Indicate to USBH that the hub is installed
ESP_ERROR_CHECK(usbh_hub_is_installed(usbh_hub_callback, NULL));
ret = ESP_OK;
return ret;
assign_err:
ESP_ERROR_CHECK(hcd_port_deinit(port_hdl));
err:
urb_free(enum_urb);
heap_caps_free(hub_driver_obj);
return ret;
}
esp_err_t hub_uninstall(void)
{
HUB_DRIVER_ENTER_CRITICAL();
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE);
hub_driver_t *hub_driver_obj = p_hub_driver_obj;
p_hub_driver_obj = NULL;
HUB_DRIVER_EXIT_CRITICAL();
ESP_ERROR_CHECK(hcd_port_deinit(hub_driver_obj->constant.root_port_hdl));
//Free Hub driver resources
urb_free(hub_driver_obj->single_thread.enum_urb);
heap_caps_free(hub_driver_obj);
return ESP_OK;
}
esp_err_t hub_root_start(void)
{
HUB_DRIVER_ENTER_CRITICAL();
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state == HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE);
HUB_DRIVER_EXIT_CRITICAL();
//Power ON the root port
esp_err_t ret;
ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON);
if (ret == ESP_OK) {
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD;
HUB_DRIVER_EXIT_CRITICAL();
}
return ret;
}
esp_err_t hub_root_stop(void)
{
HUB_DRIVER_ENTER_CRITICAL();
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj != NULL, ESP_ERR_INVALID_STATE);
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj->dynamic.driver_state != HUB_DRIVER_STATE_INSTALLED, ESP_ERR_INVALID_STATE);
HUB_DRIVER_EXIT_CRITICAL();
esp_err_t ret;
ret = hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_OFF);
if (ret == ESP_OK) {
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_INSTALLED;
HUB_DRIVER_EXIT_CRITICAL();
}
return ret;
}
esp_err_t hub_process(void)
{
HUB_DRIVER_ENTER_CRITICAL();
uint32_t action_flags = p_hub_driver_obj->dynamic.flags.actions;
p_hub_driver_obj->dynamic.flags.actions = 0;
HUB_DRIVER_EXIT_CRITICAL();
while (action_flags) {
if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) {
root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl);
}
if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) {
enum_handle_events();
}
if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_RECOVER) {
ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port");
ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl));
ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON));
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.driver_state = HUB_DRIVER_STATE_ROOT_POWERD;
HUB_DRIVER_EXIT_CRITICAL();
}
HUB_DRIVER_ENTER_CRITICAL();
action_flags = p_hub_driver_obj->dynamic.flags.actions;
p_hub_driver_obj->dynamic.flags.actions = 0;
HUB_DRIVER_EXIT_CRITICAL();
}
return ESP_OK;
}

View File

@ -0,0 +1,408 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
#include "esp_intr_alloc.h"
//Include the other USB Host Library headers as well
#include "usb/usb_host_misc.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_types_stack.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------- Macros and Types --------------------------------------------------
// ----------------------- Handles -------------------------
typedef void * usb_host_client_handle_t; /**< Handle to a client using the USB Host Library */
// ----------------------- Events --------------------------
#define USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS 0x01 /**< All clients have been deregistered from the USB Host Library */
#define USB_HOST_LIB_EVENT_FLAGS_ALL_FREE 0x02 /**< The USB Host Library has freed all devices */
/**
* @brief The type event in a client event message
*/
typedef enum {
USB_HOST_CLIENT_EVENT_NEW_DEV, /**< A new device has been enumerated and added to the USB Host Library */
USB_HOST_CLIENT_EVENT_DEV_GONE, /**< A device opened by the client is now gone */
} usb_host_client_event_t;
/**
* @brief Client event message
*
* Client event messages are sent to each client of the USB Host Library in order to notify them of various
* USB Host Library events such as:
* - Addition of new devices
* - Removal of existing devices
*
* @note The event message structure has a union with members corresponding to each particular event. Based on the event
* type, only the relevant member field should be accessed.
*/
typedef struct {
usb_host_client_event_t event; /**< Type of event */
union {
struct {
uint8_t address; /**< New device's address */
} new_dev;
struct {
usb_device_handle_t dev_hdl; /**< The handle of the device that was gone */
} dev_gone;
};
} usb_host_client_event_msg_t;
// ---------------------- Callbacks ------------------------
/**
* @brief Client event callback
*
* - Each client of the USB Host Library must register an event callback to receive event messages from the USB Host
* Library.
* - The client event callback is run from the context of the clients usb_host_client_handle_events() function
*/
typedef void (*usb_host_client_event_cb_t)(const usb_host_client_event_msg_t *event_msg, void *arg);
// -------------------- Configurations ---------------------
/**
* @brief USB Host Library configuration
*
* 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 */
} usb_host_config_t;
/**
* @brief USB Host Library Client configuration
*
* Configuration structure for a USB Host Library client. Provided in usb_host_client_register()
*/
typedef struct {
usb_host_client_event_cb_t client_event_callback; /**< Client's event callback function */
void *callback_arg; /**< Event callback function argument */
int max_num_event_msg; /**< Maximum number of event messages that can be stored (e.g., 3) */
} usb_host_client_config_t;
// ------------------------------------------------ Library Functions --------------------------------------------------
/**
* @brief Install the USB Host Library
*
* - 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
*
* @param[in] config USB Host Library configuration
* @return esp_err_t
*/
esp_err_t usb_host_install(const usb_host_config_t *config);
/**
* @brief Uninstall the USB Host Library
*
* - This function should be called to uninstall the USB Host Library, thereby freeing its resources
* - All clients must have been deregistered before calling this function
* - 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
*
* @return esp_err_t
*/
esp_err_t usb_host_uninstall(void);
/**
* @brief Handle USB Host Library events
*
* - 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
*
* @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
* @return esp_err_t
*/
esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret);
// ------------------------------------------------ Client Functions ---------------------------------------------------
/**
* @brief Register a client of the USB Host Library
*
* - This function registers a client of the USB Host Library
* - Once a client is registered, its processing function usb_host_client_handle_events() should be called repeatedly
*
* @param[in] client_config Client configuration
* @param[out] client_hdl_ret Client handle
* @return esp_err_t
*/
esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret);
/**
* @brief Deregister a USB Host Library client
*
* - This function deregisters a client of the USB Host Library
* - The client must have closed all previously opened devices before attempting to deregister
*
* @param[in] client_hdl Client handle
* @return esp_err_t
*/
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
*
* @param[in] client_hdl Client handle
* @param[in] timeout_ticks Timeout in ticks to wait for an event to occur
* @return esp_err_t
*/
esp_err_t usb_host_client_handle_events(usb_host_client_handle_t client_hdl, TickType_t timeout_ticks);
/**
* @brief Unblock a client
*
* - This function simply unblocks a client if it is blocked on the usb_host_client_handle_events() function.
* - This function is useful when need to unblock a client in order to deregister it.
*
* @param[in] client_hdl Client handle
* @return esp_err_t
*/
esp_err_t usb_host_client_unblock(usb_host_client_handle_t client_hdl);
// ------------------------------------------------- Device Handling ---------------------------------------------------
/**
* @brief Open a device
*
* - This function allows a client to open a device
* - A client must open a device first before attempting to use it (e.g., sending transfers, device requests etc.)
*
* @param[in] client_hdl Client handle
* @param[in] dev_addr Device's address
* @param[out] dev_hdl_ret Device's handle
* @return esp_err_t
*/
esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_addr, usb_device_handle_t *dev_hdl_ret);
/**
* @brief Close a device
*
* - This function allows a client to close a device
* - A client must close a device after it has finished using the device
* - A client must close all devices it has opened before deregistering
*
* @param[in] client_hdl Client handle
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl);
/**
* @brief Indicate that all devices can be freed when possible
*
* - This function marks all devices as waiting to be freed
* - If a device is not opened by any clients, it will be freed immediately
* - If a device is opened by at least one client, the device will be free when the last client closes that device.
* - Wait for the USB_HOST_LIB_EVENT_FLAGS_ALL_FREE flag to be set by usb_host_lib_handle_events() in order to know
* when all devices have been freed
* - This function is useful when cleaning up devices before uninstalling the USB Host Library
*
* @return esp_err_t
*/
esp_err_t usb_host_device_free_all(void);
// ------------------------------------------------- Device Requests ---------------------------------------------------
// ------------------- Cached Requests ---------------------
/**
* @brief Get a device's information
*
* - This function gets some basic information of a device
* - The device must be opened first before attempting to get its information
*
* @param[in] dev_hdl Device handle
* @param[out] dev_info Device information
* @return esp_err_t
*/
esp_err_t usb_host_device_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info);
// ----------------------------------------------- Descriptor Requests -------------------------------------------------
// ----------------- Cached Descriptors --------------------
/**
* @brief Get a device's device descriptor
*
* - A client must call usb_host_device_open() first
* - No control transfer is sent. The device's descriptor is cached on enumeration
* - This function simple returns a pointer to the cached descriptor
*
* @note No control transfer is sent. The device's descriptor is cached on enumeration
* @param[in] dev_hdl Device handle
* @param[out] device_desc Device descriptor
* @return esp_err_t
*/
esp_err_t usb_host_get_device_descriptor(usb_device_handle_t dev_hdl, const usb_device_desc_t **device_desc);
/**
* @brief Get a device's active configuration descriptor
*
* - A client must call usb_host_device_open() first
* - No control transfer is sent. The device's active configuration descriptor is cached on enumeration
* - This function simple returns a pointer to the cached descriptor
*
* @note No control transfer is sent. A device's active configuration descriptor is cached on enumeration
* @param[in] dev_hdl Device handle
* @param[out] config_desc Configuration descriptor
* @return esp_err_t
*/
esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc);
// ----------------------------------------------- Interface Functions -------------------------------------------------
/**
* @brief Function for a client to claim an device's interface
*
* - A client must claim a device's interface before attempting to communicate with any of its endpoints
* - Once an interface is claimed by a client, it cannot be claimed by any other client.
*
* @param[in] client_hdl Client handle
* @param[in] dev_hdl Device handle
* @param[in] bInterfaceNumber Interface number
* @param[in] bAlternateSetting Interface alternate setting number
* @return esp_err_t
*/
esp_err_t usb_host_interface_claim(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber, uint8_t bAlternateSetting);
/**
* @brief Function for a client to release a device's interface
*
* - A client should release a device's interface after it no longer needs to communicate with the interface
* - A client must release all of its interfaces of a device it has claimed before being able to close the device
*
* @param[in] client_hdl Client handle
* @param[in] dev_hdl Device handle
* @param[in] bInterfaceNumber Interface number
* @return esp_err_t
*/
esp_err_t usb_host_interface_release(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber);
/**
* @brief Halt a particular endpoint
*
* - The device must have been opened by a client
* - The endpoint must be part of an interface claimed by a client
* - Once halted, the endpoint must be cleared using usb_host_endpoint_clear() before it can communicate again
*
* @param dev_hdl Device handle
* @param bEndpointAddress Endpoint address
* @return esp_err_t
*/
esp_err_t usb_host_endpoint_halt(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress);
/**
* @brief Flush a particular endpoint
*
* - The device must have been opened by a client
* - The endpoint must be part of an interface claimed by a client
* - The endpoint must have been halted (either through a transfer error, or usb_host_endpoint_halt())
* - Flushing an endpoint will caused an queued up transfers to be canceled
*
* @param dev_hdl Device handle
* @param bEndpointAddress Endpoint address
* @return esp_err_t
*/
esp_err_t usb_host_endpoint_flush(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress);
/**
* @brief Clear a halt on a particular endpoint
*
* - The device must have been opened by a client
* - The endpoint must be part of an interface claimed by a client
* - The endpoint must have been halted (either through a transfer error, or usb_host_endpoint_halt())
* - If the endpoint has any queued up transfers, clearing a halt will resume their execution
*
* @param dev_hdl Device handle
* @param bEndpointAddress Endpoint address
* @return esp_err_t
*/
esp_err_t usb_host_endpoint_clear(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress);
// ------------------------------------------------ Asynchronous I/O ---------------------------------------------------
/**
* @brief Allocate a transfer object
*
* - This function allocates a transfer object
* - Each transfer object has a fixed sized buffer specified on allocation
* - A transfer object can be re-used indefinitely
* - A transfer can be submitted using usb_host_transfer_submit() or usb_host_transfer_submit_control()
*
* @param[in] data_buffer_size Size of the transfer's data buffer
* @param[in] num_isoc_packets Number of isochronous packets in transfer (set to 0 for non-isochronous transfers)
* @param[out] transfer Transfer object
* @return esp_err_t
*/
esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer);
/**
* @brief Free a transfer object
*
* - Free a transfer object previously allocated using usb_host_transfer_alloc()
* - The transfer must not be in-flight when attempting to free it
*
* @param[in] transfer Transfer object
* @return esp_err_t
*/
esp_err_t usb_host_transfer_free(usb_transfer_t *transfer);
/**
* @brief Submit a non-control transfer
*
* - Submit a transfer to a particular endpoint. The device and endpoint number is specified inside the transfer
* - The transfer must be properly initialized before submitting
* - On completion, the transfer's callback will be called from the client's usb_host_client_handle_events() function.
*
* @param[in] transfer Initialized transfer object
* @return esp_err_t
*/
esp_err_t usb_host_transfer_submit(usb_transfer_t *transfer);
/**
* @brief Submit a control transfer
*
* - Submit a control transfer to a particular device. The client must have opened the device first
* - The transfer must be properly initialized before submitting. The first 8 bytes of the transfer's data buffer should
* contain the control transfer setup packet
* - On completion, the transfer's callback will be called from the client's usb_host_client_handle_events() function.
*
* @param[in] client_hdl Client handle
* @param[in] transfer Initialized transfer object
* @return esp_err_t
*/
esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, usb_transfer_t *transfer);
/**
* @brief Cancel a submitted transfer
*
* - Cancel a previously submitted transfer
* - In its current implementation, any transfer that is already in-flight will not be canceled
*
* @param transfer Transfer object
* @return esp_err_t
*/
esp_err_t usb_host_transfer_cancel(usb_transfer_t *transfer);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,130 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "usb/usb_types_ch9.h"
#ifdef __cplusplus
extern "C" {
#endif
// ---------------------------------------- Configuration Descriptor Parsing -------------------------------------------
/**
* @brief Get the next descriptor
*
* Given a particular descriptor within a full configuration descriptor, get the next descriptor within the
* configuration descriptor. This is a convenience function that can be used to walk each individual descriptor within
* a full configuration descriptor.
*
* @param[in] cur_desc Current descriptor
* @param[in] wTotalLength Total length of the configuration descriptor
* @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset of
* the current descriptor. On output, it is the offset of the returned descriptor.
* @return usb_standard_desc_t* Next descriptor, NULL if end of configuration descriptor reached
*/
const usb_standard_desc_t *usb_host_parse_next_descriptor(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, int *offset);
/**
* @brief Get the next descriptor of a particular type
*
* Given a particular descriptor within a full configuration descriptor, get the next descriptor of a particular type
* (i.e., using the bDescriptorType value) within the configuration descriptor.
*
* @param[in] cur_desc Current descriptor
* @param[in] wTotalLength Total length of the configuration descriptor
* @param[in] bDescriptorType Type of the next descriptor to get
* @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset of
* the current descriptor. On output, it is the offset of the returned descriptor.
* @return usb_standard_desc_t* Next descriptor, NULL if end descriptor is not found or configuration descriptor reached
*/
const usb_standard_desc_t *usb_host_parse_next_descriptor_of_type(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, uint8_t bDescriptorType, int *offset);
/**
* @brief Get the number of alternate settings for a bInterfaceNumber
*
* Given a particular configuration descriptor, for a particular bInterfaceNumber, get the number of alternate settings
* available for that interface (i.e., the max possible value of bAlternateSetting for that bInterfaceNumber).
*
* @param[in] config_desc Pointer to the start of a full configuration descriptor
* @param[in] bInterfaceNumber Interface number
* @return int The number of alternate settings that the interface has, -1 if bInterfaceNumber not found
*/
int usb_host_parse_interface_number_of_alternate(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber);
/**
* @brief Get a particular interface descriptor (using bInterfaceNumber and bAlternateSetting)
*
* Given a full configuration descriptor, get a particular interface descriptor.
*
* @note To get the number of alternate settings for a particular bInterfaceNumber, call
* usb_host_parse_interface_number_of_alternate()
*
* @param[in] config_desc Pointer to the start of a full configuration descriptor
* @param[in] bInterfaceNumber Interface number
* @param[in] bAlternateSetting Alternate setting number
* @param[out] offset Byte offset of the interface descriptor relative to the start of the configuration descriptor. Can be NULL.
* @return const usb_intf_desc_t* Pointer to interface descriptor, NULL if not found.
*/
const usb_intf_desc_t *usb_host_parse_interface(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, int *offset);
/**
* @brief Get an endpoint descriptor within an interface descriptor
*
* Given an interface descriptor, get the Nth endpoint descriptor of the interface. The number of endpoints in an
* interface is indicated by the bNumEndpoints field of the interface descriptor.
*
* @note If bNumEndpoints is 0, it means the interface uses the default endpoint only
*
* @param[in] intf_desc Pointer to thee start of an interface descriptor
* @param[in] index Endpoint index
* @param[in] wTotalLength Total length of the containing configuration descriptor
* @param[inout] offset Byte offset relative to the start of the configuration descriptor. On input, it is the offset
* of the interface descriptor. On output, it is the offset of the endpoint descriptor.
* @return const usb_ep_desc_t* Pointer to endpoint descriptor, NULL if not found.
*/
const usb_ep_desc_t *usb_host_parse_endpoint_by_index(const usb_intf_desc_t *intf_desc, int index, uint16_t wTotalLength, int *offset);
/**
* @brief Get an endpoint descriptor based on the endpoint's address
*
* Given a configuration descriptor, get an endpoint descriptor based on it's bEndpointAddress, bAlternateSetting, and
* bInterfaceNumber.
*
* @param[in] config_desc Pointer to the start of a full configuration descriptor
* @param[in] bInterfaceNumber Interface number
* @param[in] bAlternateSetting Alternate setting number
* @param[in] bEndpointAddress Endpoint address
* @param[out] offset Byte offset of the endpoint descriptor relative to the start of the configuration descriptor. Can be NULL
* @return const usb_ep_desc_t* Pointer to endpoint descriptor, NULL if not found.
*/
const usb_ep_desc_t *usb_host_parse_endpoint_by_address(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, uint8_t bEndpointAddress, int *offset);
// ------------------------------------------------------ Misc ---------------------------------------------------------
/**
* @brief Round up to an integer multiple of an endpoint's MPS
*
* 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.
*
* @param[in] num_bytes Number of bytes
* @param[in] mps MPS
* @return int Round up integer multiple of MPS
*/
static inline int usb_host_round_up_to_mps(int num_bytes, int mps)
{
if (num_bytes < 0 || mps < 0) {
return 0;
}
return ((num_bytes + mps - 1) / mps) * mps;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,464 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation
and mode (i.e., Host or Device) agnostic.
- On the USB Host Stack, this header is only meant to be used on the HCD layer and above. For types and macros on the
Host stack that are HW implementation specific (i.e., HAL layer and below), add them to the "hal/usb_types_private.h"
header instead.
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
#define USB_DESC_ATTR __attribute__((packed))
// ---------------------------------------------------- Chapter 9 ------------------------------------------------------
/**
* @brief USB2.0 device states
*/
typedef enum {
USB_DEVICE_STATE_NOT_ATTACHED, //Not in the USB spec, but is a catch all state for devices that need to be cleaned up after a sudden disconnection or port error
USB_DEVICE_STATE_ATTACHED,
USB_DEVICE_STATE_POWERED,
USB_DEVICE_STATE_DEFAULT,
USB_DEVICE_STATE_ADDRESS,
USB_DEVICE_STATE_CONFIGURED,
USB_DEVICE_STATE_SUSPENDED,
} usb_device_state_t;
/**
* @brief Descriptor types from USB2.0 specification table 9.5
*/
#define USB_B_DESCRIPTOR_TYPE_DEVICE 0x01
#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 0x02
#define USB_B_DESCRIPTOR_TYPE_STRING 0x03
#define USB_B_DESCRIPTOR_TYPE_INTERFACE 0x04
#define USB_B_DESCRIPTOR_TYPE_ENDPOINT 0x05
#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 0x06
#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 0x07
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 0x08
/**
* @brief Descriptor types from USB 2.0 ECN
*/
#define USB_B_DESCRIPTOR_TYPE_OTG 0x09
#define USB_B_DESCRIPTOR_TYPE_DEBUG 0x0a
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION 0x0b
/**
* @brief Descriptor types from Wireless USB spec
*/
#define USB_B_DESCRIPTOR_TYPE_SECURITY 0x0c
#define USB_B_DESCRIPTOR_TYPE_KEY 0x0d
#define USB_B_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 0x0e
#define USB_B_DESCRIPTOR_TYPE_BOS 0x0f
#define USB_B_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 0x10
#define USB_B_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMP 0x11
#define USB_B_DESCRIPTOR_TYPE_WIRE_ADAPTER 0x21
#define USB_B_DESCRIPTOR_TYPE_RPIPE 0x22
#define USB_B_DESCRIPTOR_TYPE_CS_RADIO_CONTROL 0x23
/**
* @brief Descriptor types from UAS specification
*/
#define USB_B_DESCRIPTOR_TYPE_PIPE_USAGE 0x24
// -------------------- Setup Packet -----------------------
/**
* @brief Size of a USB control transfer setup packet in bytes
*/
#define USB_SETUP_PACKET_SIZE 8
/**
* @brief Structure representing a USB control transfer setup packet
*/
typedef union {
struct {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __attribute__((packed));
uint8_t val[USB_SETUP_PACKET_SIZE];
} usb_setup_packet_t;
_Static_assert(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb_setup_packet_t incorrect");
/**
* @brief Bit masks belonging to the bmRequestType field of a setup packet
*/
#define USB_BM_REQUEST_TYPE_DIR_OUT (0X00 << 7)
#define USB_BM_REQUEST_TYPE_DIR_IN (0x01 << 7)
#define USB_BM_REQUEST_TYPE_TYPE_STANDARD (0x00 << 5)
#define USB_BM_REQUEST_TYPE_TYPE_CLASS (0x01 << 5)
#define USB_BM_REQUEST_TYPE_TYPE_VENDOR (0x02 << 5)
#define USB_BM_REQUEST_TYPE_TYPE_RESERVED (0x03 << 5)
#define USB_BM_REQUEST_TYPE_TYPE_MASK (0x03 << 5)
#define USB_BM_REQUEST_TYPE_RECIP_DEVICE (0x00 << 0)
#define USB_BM_REQUEST_TYPE_RECIP_INTERFACE (0x01 << 0)
#define USB_BM_REQUEST_TYPE_RECIP_ENDPOINT (0x02 << 0)
#define USB_BM_REQUEST_TYPE_RECIP_OTHER (0x03 << 0)
#define USB_BM_REQUEST_TYPE_RECIP_MASK (0x1f << 0)
/**
* @brief Bit masks belonging to the bRequest field of a setup packet
*/
#define USB_B_REQUEST_GET_STATUS 0x00
#define USB_B_REQUEST_CLEAR_FEATURE 0x01
#define USB_B_REQUEST_SET_FEATURE 0x03
#define USB_B_REQUEST_SET_ADDRESS 0x05
#define USB_B_REQUEST_GET_DESCRIPTOR 0x06
#define USB_B_REQUEST_SET_DESCRIPTOR 0x07
#define USB_B_REQUEST_GET_CONFIGURATION 0x08
#define USB_B_REQUEST_SET_CONFIGURATION 0x09
#define USB_B_REQUEST_GET_INTERFACE 0x0A
#define USB_B_REQUEST_SET_INTERFACE 0x0B
#define USB_B_REQUEST_SYNCH_FRAME 0x0C
/**
* @brief Bit masks belonging to the wValue field of a setup packet
*/
#define USB_W_VALUE_DT_DEVICE 0x01
#define USB_W_VALUE_DT_CONFIG 0x02
#define USB_W_VALUE_DT_STRING 0x03
#define USB_W_VALUE_DT_INTERFACE 0x04
#define USB_W_VALUE_DT_ENDPOINT 0x05
#define USB_W_VALUE_DT_DEVICE_QUALIFIER 0x06
#define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07
#define USB_W_VALUE_DT_INTERFACE_POWER 0x08
/**
* @brief Initializer for a SET_ADDRESS request
*
* Sets the address of a connected device
*/
#define USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt_ptr, addr) ({ \
(setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD |USB_BM_REQUEST_TYPE_RECIP_DEVICE; \
(setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS; \
(setup_pkt_ptr)->wValue = (addr); \
(setup_pkt_ptr)->wIndex = 0; \
(setup_pkt_ptr)->wLength = 0; \
})
/**
* @brief Initializer for a request to get a device's device descriptor
*/
#define USB_SETUP_PACKET_INIT_GET_DEVICE_DESC(setup_pkt_ptr) ({ \
(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_DEVICE << 8); \
(setup_pkt_ptr)->wIndex = 0; \
(setup_pkt_ptr)->wLength = 18; \
})
/**
* @brief Initializer for a request to get a device's current configuration number
*/
#define USB_SETUP_PACKET_INIT_GET_CONFIG(setup_pkt_ptr) ({ \
(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_CONFIGURATION; \
(setup_pkt_ptr)->wValue = 0; \
(setup_pkt_ptr)->wIndex = 0; \
(setup_pkt_ptr)->wLength = 1; \
})
/**
* @brief Initializer for a request to get one of the device's current configuration descriptor
*
* - desc_index indicates the configuration's index number
* - Number of bytes of the configuration descriptor to get
*/
#define USB_SETUP_PACKET_INIT_GET_CONFIG_DESC(setup_pkt_ptr, desc_index, 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_CONFIG << 8) | ((desc_index) & 0xFF); \
(setup_pkt_ptr)->wIndex = 0; \
(setup_pkt_ptr)->wLength = (desc_len); \
})
/**
* @brief Initializer for a request to set a device's current configuration number
*/
#define USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt_ptr, config_num) ({ \
(setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \
(setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION; \
(setup_pkt_ptr)->wValue = (config_num); \
(setup_pkt_ptr)->wIndex = 0; \
(setup_pkt_ptr)->wLength = 0; \
})
/**
* @brief Initializer for a request to set an interface's alternate setting
*/
#define USB_SETUP_PACKET_INIT_SET_INTERFACE(setup_pkt_ptr, intf_num, alt_setting_num) ({ \
(setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \
(setup_pkt_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \
(setup_pkt_ptr)->wValue = (alt_setting_num); \
(setup_pkt_ptr)->wIndex = (intf_num); \
(setup_pkt_ptr)->wLength = 0; \
})
// ---------------- Standard Descriptor --------------------
/**
* @brief Size of dummy USB standard descriptor
*/
#define USB_STANDARD_DESC_SIZE 2
/**
* @brief Dummy USB standard descriptor
*
* All USB standard descriptors start with these two bytes. Use this type traversing over descriptors
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
} USB_DESC_ATTR;
uint8_t val[USB_STANDARD_DESC_SIZE];
} usb_standard_desc_t;
_Static_assert(sizeof(usb_standard_desc_t) == USB_STANDARD_DESC_SIZE, "Size of usb_standard_desc_t incorrect");
// ------------------ Device Descriptor --------------------
/**
* @brief Size of a USB device descriptor in bytes
*/
#define USB_DEVICE_DESC_SIZE 18
/**
* @brief Structure representing a USB device descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} USB_DESC_ATTR;
uint8_t val[USB_DEVICE_DESC_SIZE];
} usb_device_desc_t;
_Static_assert(sizeof(usb_device_desc_t) == USB_DEVICE_DESC_SIZE, "Size of usb_device_desc_t incorrect");
/**
* @brief Possible base class values of the bDeviceClass field of a USB device descriptor
*/
#define USB_CLASS_PER_INTERFACE 0x00
#define USB_CLASS_AUDIO 0x01
#define USB_CLASS_COMM 0x02
#define USB_CLASS_HID 0x03
#define USB_CLASS_PHYSICAL 0x05
#define USB_CLASS_STILL_IMAGE 0x06
#define USB_CLASS_PRINTER 0x07
#define USB_CLASS_MASS_STORAGE 0x08
#define USB_CLASS_HUB 0x09
#define USB_CLASS_CDC_DATA 0x0a
#define USB_CLASS_CSCID 0x0b
#define USB_CLASS_CONTENT_SEC 0x0d
#define USB_CLASS_VIDEO 0x0e
#define USB_CLASS_WIRELESS_CONTROLLER 0xe0
#define USB_CLASS_PERSONAL_HEALTHCARE 0x0f
#define USB_CLASS_AUDIO_VIDEO 0x10
#define USB_CLASS_BILLBOARD 0x11
#define USB_CLASS_USB_TYPE_C_BRIDGE 0x12
#define USB_CLASS_MISC 0xef
#define USB_CLASS_APP_SPEC 0xfe
#define USB_CLASS_VENDOR_SPEC 0xff
/**
* @brief Vendor specific subclass code
*/
#define USB_SUBCLASS_VENDOR_SPEC 0xff
// -------------- Configuration Descriptor -----------------
/**
* @brief Size of a short USB configuration descriptor in bytes
*
* @note The size of a full USB configuration includes all the interface and endpoint
* descriptors of that configuration.
*/
#define USB_CONFIG_DESC_SIZE 9
/**
* @brief Structure representing a short USB configuration descriptor
*
* @note The full USB configuration includes all the interface and endpoint
* descriptors of that configuration.
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} USB_DESC_ATTR;
uint8_t val[USB_CONFIG_DESC_SIZE];
} usb_config_desc_t;
_Static_assert(sizeof(usb_config_desc_t) == USB_CONFIG_DESC_SIZE, "Size of usb_config_desc_t incorrect");
/**
* @brief Bit masks belonging to the bmAttributes field of a configuration descriptor
*/
#define USB_BM_ATTRIBUTES_ONE (1 << 7) //Must be set
#define USB_BM_ATTRIBUTES_SELFPOWER (1 << 6) //Self powered
#define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up
#define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered
// ---------- Interface Association Descriptor -------------
/**
* @brief Size of a USB interface association descriptor in bytes
*/
#define USB_IAD_DESC_SIZE 9
/**
* @brief Structure representing a USB interface association descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bFirstInterface;
uint8_t bInterfaceCount;
uint8_t bFunctionClass;
uint8_t bFunctionSubClass;
uint8_t bFunctionProtocol;
uint8_t iFunction;
} USB_DESC_ATTR;
uint8_t val[USB_IAD_DESC_SIZE];
} usb_iad_desc_t;
_Static_assert(sizeof(usb_iad_desc_t) == USB_IAD_DESC_SIZE, "Size of usb_iad_desc_t incorrect");
// ---------------- Interface Descriptor -------------------
/**
* @brief Size of a USB interface descriptor in bytes
*/
#define USB_INTF_DESC_SIZE 9
/**
* @brief Structure representing a USB interface descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} USB_DESC_ATTR;
uint8_t val[USB_INTF_DESC_SIZE];
} usb_intf_desc_t;
_Static_assert(sizeof(usb_intf_desc_t) == USB_INTF_DESC_SIZE, "Size of usb_intf_desc_t incorrect");
// ----------------- Endpoint Descriptor -------------------
/**
* @brief Size of a USB endpoint descriptor in bytes
*/
#define USB_EP_DESC_SIZE 7
/**
* @brief Structure representing a USB endpoint descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} USB_DESC_ATTR;
uint8_t val[USB_EP_DESC_SIZE];
} usb_ep_desc_t;
_Static_assert(sizeof(usb_ep_desc_t) == USB_EP_DESC_SIZE, "Size of usb_ep_desc_t incorrect");
/**
* @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor
*/
#define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK 0x0f
#define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80
/**
* @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor
*/
#define USB_BM_ATTRIBUTES_XFERTYPE_MASK 0x03
#define USB_BM_ATTRIBUTES_XFER_CONTROL (0 << 0)
#define USB_BM_ATTRIBUTES_XFER_ISOC (1 << 0)
#define USB_BM_ATTRIBUTES_XFER_BULK (2 << 0)
#define USB_BM_ATTRIBUTES_XFER_INT (3 << 0)
#define USB_BM_ATTRIBUTES_SYNCTYPE_MASK 0x0C /* in bmAttributes */
#define USB_BM_ATTRIBUTES_SYNC_NONE (0 << 2)
#define USB_BM_ATTRIBUTES_SYNC_ASYNC (1 << 2)
#define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE (2 << 2)
#define USB_BM_ATTRIBUTES_SYNC_SYNC (3 << 2)
#define USB_BM_ATTRIBUTES_USAGETYPE_MASK 0x30
#define USB_BM_ATTRIBUTES_USAGE_DATA (0 << 4)
#define USB_BM_ATTRIBUTES_USAGE_FEEDBACK (1 << 4)
#define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB (2 << 4)
/**
* @brief Macro helpers to get information about an endpoint from its descriptor
*/
#define USB_EP_DESC_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK))
#define USB_EP_DESC_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK)
#define USB_EP_DESC_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0)
#define USB_EP_DESC_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF)
// ------------------ String Descriptor --------------------
/**
* @brief Size of a short USB string descriptor in bytes
*/
#define USB_STR_DESC_SIZE 4
/**
* @brief Structure representing a USB string descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wData[1]; /* UTF-16LE encoded */
} USB_DESC_ATTR;
uint8_t val[USB_STR_DESC_SIZE];
} usb_str_desc_t;
_Static_assert(sizeof(usb_str_desc_t) == USB_STR_DESC_SIZE, "Size of usb_str_desc_t incorrect");
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "usb/usb_types_ch9.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------ Protocol Standard --------------------------------------------------
/**
* @brief USB Standard Speeds
*/
typedef enum {
USB_SPEED_LOW = 0, /**< USB Low Speed (1.5 Mbit/s) */
USB_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */
} usb_speed_t;
/**
* @brief The type of USB transfer
*
* @note The enum values need to match the bmAttributes field of an EP descriptor
*/
typedef enum {
USB_TRANSFER_TYPE_CTRL = 0,
USB_TRANSFER_TYPE_ISOCHRONOUS,
USB_TRANSFER_TYPE_BULK,
USB_TRANSFER_TYPE_INTR,
} usb_transfer_type_t;
// ------------------------------------------------- Device Related ----------------------------------------------------
/**
* @brief Handle of a USB Device connected to a USB Host
*/
typedef void * 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_device_info_t;
// ------------------------------------------------ Transfer Related ---------------------------------------------------
/**
* @brief The status of a particular transfer
*/
typedef enum {
USB_TRANSFER_STATUS_COMPLETED, /**< The transfer was successful (but may be short) */
USB_TRANSFER_STATUS_ERROR, /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */
USB_TRANSFER_STATUS_TIMED_OUT, /**< The transfer failed due to a time out */
USB_TRANSFER_STATUS_CANCELED, /**< The transfer was canceled */
USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */
USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */
USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */
} usb_transfer_status_t;
/**
* @brief Isochronous packet descriptor
*
* If the number of bytes in an Isochronous transfer is larger than the MPS of the endpoint, the transfer is split
* into multiple packets transmitted at the endpoint's specified interval. An array of Isochronous packet descriptors
* describes how an Isochronous transfer should be split into multiple packets.
*/
typedef struct {
int num_bytes; /**< Number of bytes to transmit/receive in the packet. IN packets should be integer multiple of MPS */
int actual_num_bytes; /**< Actual number of bytes transmitted/received in the packet */
usb_transfer_status_t status; /**< Status of the packet */
} usb_isoc_packet_desc_t;
/**
* @brief USB transfer structure
*
* This structure is used to represent a transfer from a software client to an endpoint over the USB bus. Some of the
* fields are made const on purpose as they are fixed on allocation. Users should call the appropriate USB Host Library
* function to allocate a USB transfer structure instead of allocating this structure themselves.
*
* The transfer type is inferred from the endpoint this transfer is sent to. Depending on the transfer type, users
* should note the following:
*
* - Bulk: This structure represents a single bulk transfer. If the number of bytes exceeds the endpoint's MPS, the
* transfer will be split into multiple MPS sized packets followed by a short packet.
* - Control: This structure represents a single control transfer. This first 8 bytes of the data_buffer must be filled
* with the setup packet (see usb_setup_packet_t). The num_bytes field should be the total size of the
* transfer (i.e., size of setup packet + wLength).
* - Interrupt: Represents an interrupt transfer. If num_bytes exceeds the MPS of the endpoint, the transfer will be
* split into multiple packets, and each packet is transferred at the endpoint's specified interval.
* - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer
* is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval
* of the endpoint.
*
* @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS
* @note This structure should be allocated via __insert_func_name__()
* @note Once the transfer has be submitted, users should not modify the structure until the transfer has completed
*/
typedef struct usb_transfer_s usb_transfer_t;
/**
* @brief USB transfer completion callback
*/
typedef void (*usb_transfer_cb_t)(usb_transfer_t *transfer);
struct usb_transfer_s{
uint8_t *const data_buffer; /**< Pointer to data buffer */
const size_t data_buffer_size; /**< Size of the data buffer in bytes */
int num_bytes; /**< Number of bytes to transfer.
Control transfers should include the size of the setup packet.
Isochronous transfer should be the total transfer size of all packets.
For non-control IN transfers, num_bytes should be an integer multiple of MPS. */
int actual_num_bytes; /**< Actual number of bytes transferred */
uint32_t flags; /**< Transfer flags */
usb_device_handle_t device_handle; /**< Device handle */
uint8_t bEndpointAddress; /**< Endpoint Address */
usb_transfer_status_t status; /**< Status of the transfer */
uint32_t timeout; /**< Timeout (in milliseconds) of the packet (currently not supported yet) */
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 */
};
#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
}
#endif

View File

@ -1,18 +1,41 @@
# USB Host Stack Maintainers Notes
This document is intended future maintainers of the ESP-IDF USB Host stack.
This document is intended for future maintainers of the ESP-IDF USB Host stack.
Note: The underlying DWC_OTG controller will sometimes be referred to as the Host Controller in this document.
Note: Some static functions within the stack need to be called within critical sections. Therefore, the USB Host stack will prefix all such function names with an underscore (e.g., `_some_static_func()`) will need to be called in critical sections)
The host driver is currently split into the following layers, ordered from the lowest (furthest away from the user) to the highest layer (closest to the user).
* USB Host lower layer in `usbh_ll.h`
* USB HAL in `usbh_hal.h` and `usbh_hal.c`
* Host Controller Driver in `hcd.c` and `hcd.h`
* USB Host Driver in `usb_host.h` and `usb_host.c`
# DWC_OTG
Details regarding the DWC_OTG peripheral are covered in the data book and programming guide. However, this section adds some extra notes regarding the hardware's behavior that are not specified / cannot be easily found in the data book or programming guide.
## Implicit interrupt on short packet on INTERRUPT transfer.
- An interrupt channel will trigger an interrupt one of its QTDs is a short packet (event if the QTD did not set the interrupt on complete bit). This implicit interrupt is used to notify the software that it should halt the channel to
stop the remainder of the interrupt transfer.
## Channel interrupt on port errors
- If there are one or more channels active, and a port error interrupt occurs (such as disconnection, over-current), the active channels will not have an interrupt. Each need to be manually disabled to obtain an interrupt.
## Reset
- When resetting, if a disconnect occurs while a reset condition is being held. The disconnect is not detected until the reset condition is released. Once released, a `USBH_HAL_PORT_EVENT_DISCONN` is generated.
- During a second reset (i.e., there is already an enabled device).
- Once the reset condition is asserted, a `USBH_HAL_PORT_EVENT_DISABLED` is generated
- Once released, a `USBH_HAL_PORT_EVENT_ENABLED` is generated again.
# USB Host Lower Layer
* `usbh_ll.h` abstracts away the basic register operation of the **DWC** OTG controller
* `usbh_ll.h` abstracts away the basic register operation of the DWC_OTG controller
* The LL provides register operations of the DWC OTG controller operating under Host Mode using scatter/gather internal DMA.
* For non-host mode specific register operations (i.e. global registers), the functions are prefixed with `usb_ll_...()`
* For host mode specific register operations, the functions are prefixed with `usbh_ll_...()`
@ -47,49 +70,52 @@ The HAL layer abstracts the DWC_OTG operating in Host Mode using Internal Scatte
# Host Controller Driver (HCD)
The HCD (Host Controller Driver) abstracts the DWC_OTG as N number of ports and an arbitrary number of pipes that can be routed through one of the ports to a device. However note that **the underlying hardware controller only has a single port, so technically only one port can ever be enabled**.
The HCD (Host Controller Driver) abstracts the DWC_OTG as N number of ports and an arbitrary number of pipes that can be routed through one of the ports to a device. However, note that **the underlying hardware controller only has a single port, so technically only one port can ever be enabled**.
- In other words, the HCD essentially implements a root hub (not fully behavioral compliant) that contains a single port.
- Pipes are "an association between an endpoint on a device and software on the host". URBs (USB Request Block) represent a USB transfer that can be enqueued into a pipe for transmission, and dequeued from a pipe when completed.
The HCD currently has the following limitations:
- HCD **does not** "present the root hub and its behavior according to the hub class definition". We currently don't have a hub driver yet, so the port commands in the driver do not fully represent an interface of a USB hub as described in 10.4 of the USB2.0 spec.
- HCD **does not "present the root hub and its behavior according to the hub class definition"**. We currently don't have a hub driver yet, so the port commands in the driver do not fully represent an interface of a USB hub as described in 10.4 of the USB2.0 spec.
- No more than 8 pipes can be allocated at any one time due to underlying Host Controllers 8 channel limit. In the future, we could make particular pipes share a single Host Controller channel.
- The HCD currently only supports Control and Bulk transfer types.
- If you are connecting to a device with a large MPS requirements (e.g., Isochronous transfers), you may need to call `hcd_port_set_fifo_bias()` to adjust the size of the internal FIFO
## HCD Port
- An HCD port can be as a simplified version of a port on the Root Hub of the host controller. However, the complexity of parsing Hub Requests is discarded in favor of port commands (`hcd_port_cmd_t`) as the current USB Host Stack does not support hubs yet.
- An HCD port can be thought of as a simplified version of a port on the Root Hub of the host controller. However, the complexity of parsing Hub Requests is discarded in favor of port commands (`hcd_port_cmd_t`) as the current USB Host Stack does not support hubs yet.
- A port must first initialized before it can be used. A port is identified by its handled of type `hcd_port_handle_t`
- The port can be manipulated using commands such as:
- Powering the port ON/OFF
- Issuing reset/resume signals
- The various host port events are represented in the `hcd_port_event_t` enumeration
- When a fatal error (such as a sudden disconnection or a port over current), the port will be put into the HCD_PORT_STATE_RECOVERY state. The port can be deinitialized from there, or recovered using `hcd_port_recover()`. All the pipes routed through the port will be made invalid.
- The FIFO bias of a port can be set using `hcd_port_set_fifo_bias()`. Biasing the FIFO will affect the permissible MPS sizes of pipes. For example, if the connected device has an IN endpoint with large MPS (e.g., 512 bytes), the FIFO should be biased as `HCD_PORT_FIFO_BIAS_RX`.
- When the following port events occur, the port will be put into the HCD_PORT_STATE_RECOVERY state. The port can be deinitialized from there, or recovered using `hcd_port_recover()`. All the pipes routed through the port must be freed before the port can be recovered.
- `HCD_PORT_EVENT_DISCONNECTION`
- `HCD_PORT_EVENT_ERROR`
- `HCD_PORT_EVENT_OVERCURRENT`
- The port's internal FIFOs (RX, Periodic TX, Non-periodic TX) must be after each port reset (a port reset seems to clear the FIFO sizing registers). The sizing of these FIFOs will affect the largest supported MPS of endpoints using that FIFO. For convenience, the HCD provides the `hcd_port_fifo_bias_t` enum that will set the FIFO sizes for you but biased towards a particular use case. For example, if the connected device has an IN endpoint with large MPS (e.g., 512 bytes), the FIFO should be biased as `HCD_PORT_FIFO_BIAS_RX`.
- The FIFO sizes will be set on port initialization (supplied in `hcd_port_config_t`)
- FIFOs can be resized after port reset using `hcd_port_set_fifo_bias()` but some restrictions apply (see API description).
## HCD Pipes
- URBs can be enqueued into a pipe. Pipes use a linked list internally, so there is in-theory no limit to the number of URBs that can be enqueued.
- Pipes can be opened to a particular endpoint based on a descriptor provided on allocation. If opening a default pipe, a `NULL` descriptor can be provided.
- URBs can be enqueued into a pipe. Pipes use a linked list internally, so there is (in theory) no limit to the number of URBs that can be enqueued.
- URBs need to be dequeued once they are completed.
- URBs need to have the transfer information (such as data buffer, transfer length in bytes) filled before they should be enqueued.
- URBs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify an URB object (and the URB's data buffer) until the URB is dequeued.
- URBs will be owned by the HCD until they are dequeued. Thus, users should not attempt to modify a URB object (and the URB's data buffer) until the URB is dequeued.
- The URB is defined in `usb_private.h` instead of `hcd.h` so that the same structure can shared throughout the entire Host stack. Each layer simply needs to pass the pointer of the URB to the next layer thus minimizing the amount of copying required.
## HCD SW Arch
The software arch for the HCD has the following properties and intentions:
- Some static functions need to be called in critical sections whilst others need not. Therefore, static functions prefixed by an underscore ((e.g., `_some_static_func()`) will need to be called in critical sections.
- Some static functions may be blocking. Those functions will have a note in their descriptions.
- The HCD communicates events entirely through callbacks and polling/handling functions. The client can choose what type of data structure they want to use in the callbacks to keep track of port and pipe events. Callbacks don't even need to be used, and the HCD should be able to operate entirely on a polling basis.
- The port and each pipe have to be treated as completely separate entities (wither their own handles and events). This allows client to group these entities however it sees fit. For example, the client can:
- The port and each pipe have to be treated as completely separate entities (with their own handles and events). This allows clients to group these entities however it sees fit. For example, the client can:
- Let a hub driver manage the port, and manipulate the port via its port handle
- Group pipes into interfaces and expose the interface as a single entity
- The HCD will not internally allocate any tasks. It is up to the client to decide how distribute workload (e.g., a single while loop polling a port and all its pipes vs or each pipe having its own task).
- The HCD uses an interrupt process things that require low latency such as processing pipe transfer requests. Where possible, processing is deferred to the `hcd_port_handle_event()` to reduce ISR workload.
- The HCD will not internally allocate any tasks. It is up to the client to decide how to distribute workload (e.g., a single while loop polling a port and all its pipes vs or each pipe having its own task).
- The HCD uses an interrupt to process things that require low latency such as processing pipe transfer requests. Where possible, processing is deferred to the `hcd_port_handle_event()` to reduce ISR workload.
### Events
@ -112,4 +138,4 @@ The HCD API is thread safe however the following limitations should be noted:
- Likewise, it is the client's responsibility to ensure that events and pipes are cleared before calling `hcd_port_deinit()`.
- `hcd_port_command()` is thread safe, but only one port command can be executed at any one time. Therefore HCD internally used a mutex to protect against concurrent commands.
- If multiple threads attempt to execute a command on the sample one, all but one of those threads will return with an invalid state error.
- HCD functions should not be called from critical sections and interrupts, as some functions (e.g., `hcd_port_command()` and `hcd_pipe_command()`) may be blocking.
- Blocking HCD functions should not be called from critical sections and interrupts (e.g., `hcd_port_command()` and `hcd_pipe_command()`).

View File

@ -1,16 +1,8 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
@ -23,7 +15,7 @@ extern "C" {
#include <sys/queue.h>
#include "esp_err.h"
#include "usb_private.h"
#include "usb.h"
#include "usb/usb_types_ch9.h"
// ------------------------------------------------- Macros & Types ----------------------------------------------------
@ -53,18 +45,14 @@ typedef enum {
*
* Active:
* - Pipe is able to transmit data. URBs can be enqueued.
* - Event if pipe has no URBs enqueued, it can still be in the active state.
* - Even if pipe has no URBs enqueued, it can still be in the active state.
* Halted:
* - An error has occurred on the pipe. URBs will no longer be executed.
* - Halt should be cleared using the clear command
* Invalid:
* - The underlying device that the pipe connects is not longer valid, thus making the pipe invalid.
* - Pending URBs should be dequeued and the pipe should be freed.
* - Halt should be cleared using the HCD_PIPE_CMD_CLEAR command
*/
typedef enum {
HCD_PIPE_STATE_ACTIVE, /**< The pipe is active */
HCD_PIPE_STATE_HALTED, /**< The pipe is halted */
HCD_PIPE_STATE_INVALID, /**< The pipe no longer exists and should be freed */
} hcd_pipe_state_t;
// ----------------------- Events --------------------------
@ -80,20 +68,16 @@ typedef enum {
HCD_PORT_EVENT_DISCONNECTION, /**< A device disconnection has been detected */
HCD_PORT_EVENT_ERROR, /**< A port error has been detected. Port is now HCD_PORT_STATE_RECOVERY */
HCD_PORT_EVENT_OVERCURRENT, /**< Overcurrent detected on the port. Port is now HCD_PORT_STATE_RECOVERY */
HCD_PORT_EVENT_SUDDEN_DISCONN, /**< The port has suddenly disconnected (i.e., there was an enabled device connected
to the port when the disconnection occurred. Port is now HCD_PORT_STATE_RECOVERY. */
} hcd_port_event_t;
/**
* @brief HCD pipe events
*
* @note Pipe error events will put the pipe into the HCD_PIPE_STATE_HALTED state
* @note The HCD_PIPE_EVENT_INVALID will put the pipe in the HCD_PIPE_STATE_INVALID state
*/
typedef enum {
HCD_PIPE_EVENT_NONE, /**< The pipe has no events (used to indicate no events when polling) */
HCD_PIPE_EVENT_URB_DONE, /**< The pipe has completed an URB. The URB can be dequeued */
HCD_PIPE_EVENT_INVALID, /**< The pipe is invalid because the underlying device is no longer valid */
HCD_PIPE_EVENT_ERROR_XFER, /**< Excessive (three consecutive) transaction errors (e.g., no ACK, bad CRC etc) */
HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL, /**< URB was not available */
HCD_PIPE_EVENT_ERROR_OVERFLOW, /**< Received more data than requested. Usually a Packet babble error
@ -111,9 +95,9 @@ typedef enum {
HCD_PORT_CMD_POWER_OFF, /**< Power OFF the port. If the port is enabled, this will cause a HCD_PORT_EVENT_SUDDEN_DISCONN event.
If the port is disabled, this will cause a HCD_PORT_EVENT_DISCONNECTION event. */
HCD_PORT_CMD_RESET, /**< Issue a reset on the port */
HCD_PORT_CMD_SUSPEND, /**< Suspend the port */
HCD_PORT_CMD_SUSPEND, /**< Suspend the port. All pipes must be halted */
HCD_PORT_CMD_RESUME, /**< Resume the port */
HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive). Any created pipes will receive a HCD_PIPE_EVENT_INVALID event */
HCD_PORT_CMD_DISABLE, /**< Disable the port (stops the SOFs or keep alive). All pipes must be halted. */
} hcd_port_cmd_t;
/**
@ -122,10 +106,9 @@ typedef enum {
* The pipe commands represent the list of pipe manipulations outlined in 10.5.2.2. of USB2.0 specification.
*/
typedef enum {
HCD_PIPE_CMD_ABORT, /**< Retire all scheduled URBs. Pipe's state remains unchanged */
HCD_PIPE_CMD_RESET, /**< Retire all scheduled URBs. Pipe's state moves to active */
HCD_PIPE_CMD_CLEAR, /**< Pipe's state moves from halted to active */
HCD_PIPE_CMD_HALT /**< Pipe's state moves to halted */
HCD_PIPE_CMD_HALT, /**< Halt an active pipe. The currently executing URB will be canceled. Enqueued URBs are left untouched */
HCD_PIPE_CMD_FLUSH, /**< Can only be called when halted. Will cause all enqueued URBs to be canceled */
HCD_PIPE_CMD_CLEAR, /**< Causes a halted pipe to become active again. Any enqueued URBs will being executing.*/
} hcd_pipe_cmd_t;
// -------------------- Object Types -----------------------
@ -171,6 +154,7 @@ typedef struct {
* @brief Port configuration structure
*/
typedef struct {
hcd_port_fifo_bias_t fifo_bias; /**< HCD port internal FIFO biasing */
hcd_port_callback_t callback; /**< HCD port event callback */
void *callback_arg; /**< User argument for HCD port callback */
void *context; /**< Context variable used to associate the port with upper layer object */
@ -185,7 +169,7 @@ typedef struct {
hcd_pipe_callback_t callback; /**< HCD pipe event ISR callback */
void *callback_arg; /**< User argument for HCD pipe callback */
void *context; /**< Context variable used to associate the pipe with upper layer object */
const usb_desc_ep_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */
const usb_ep_desc_t *ep_desc; /**< Pointer to endpoint descriptor of the pipe */
usb_speed_t dev_speed; /**< Speed of the device */
uint8_t dev_addr; /**< Device address of the pipe */
} hcd_pipe_config_t;
@ -257,11 +241,12 @@ esp_err_t hcd_port_deinit(hcd_port_handle_t port_hdl);
*
* Call this function to manipulate a port (e.g., powering it ON, sending a reset etc). The following conditions
* must be met when calling this function:
* - The port is in the correct state for the command (e.g., port must be suspend in order to use the resume command)
* - The port is in the correct state for the command (e.g., port must be suspended in order to use the resume command)
* - The port does not have any pending events
*
* @note This function is internally protected by a mutex. If multiple threads call this function, this function will
* can block.
* @note The function can block
* @note For some of the commands that involve a blocking delay (e.g., reset and resume), if the port's state changes
* unexpectedly (e.g., a disconnect during a resume), this function will return ESP_ERR_INVALID_RESPONSE.
*
@ -358,8 +343,7 @@ esp_err_t hcd_port_set_fifo_bias(hcd_port_handle_t port_hdl, hcd_port_fifo_bias_
* When allocating a pipe, the HCD will assess whether there are sufficient resources (i.e., bus time, and controller
* channels). If sufficient, the pipe will be allocated.
*
* @note Currently, Interrupt and Isochronous pipes are not supported yet
* @note The host port must be in the enabled state before a pipe can be allcoated
* @note The host port must be in the enabled state before a pipe can be allocated
*
* @param[in] port_hdl Handle of the port this pipe will be routed through
* @param[in] pipe_config Pipe configuration
@ -392,9 +376,9 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl);
*
* This function is intended to be called on default pipes during enumeration in order to update the pipe's maximum
* packet size. This function can only be called on a pipe that has met the following conditions:
* - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state)
* - Pipe is not currently processing a command
* - All URBs have been dequeued from the pipe
* - Pipe is not current processing a command
* - Pipe does not have any enqueued URBs
* - Port cannot be resetting
*
* @param pipe_hdl Pipe handle
* @param mps New Maximum Packet Size
@ -409,9 +393,9 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps);
*
* This function is intended to be called on default pipes during enumeration in order to update the pipe's device
* address. This function can only be called on a pipe that has met the following conditions:
* - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID state)
* - Pipe is not currently processing a command
* - All URBs have been dequeued from the pipe
* - Pipe is not current processing a command
* - Pipe does not have any enqueued URBs
* - Port cannot be resetting
*
* @param pipe_hdl Pipe handle
* @param dev_addr New device address
@ -421,6 +405,22 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps);
*/
esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr);
/**
* @brief Update a pipe's callback
*
* This function is intended to be called on default pipes at the end of enumeration to switch to a callback that
* handles the completion of regular control transfer.
* - Pipe is not current processing a command
* - Pipe does not have any enqueued URBs
* - Port cannot be resetting
*
* @param pipe_hdl Pipe handle
* @param callback Callback
* @param user_arg Callback argument
* @return esp_err_t
*/
esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg);
/**
* @brief Make a pipe persist through a run time reset
*
@ -454,19 +454,14 @@ hcd_pipe_state_t hcd_pipe_get_state(hcd_pipe_handle_t pipe_hdl);
/**
* @brief Execute a command on a particular pipe
*
* Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs etc). The following
* conditions must for a pipe command to be issued:
* - Pipe is still valid (i.e., not in the HCD_PIPE_STATE_INVALID)
* - No other thread/task processing a command on the pipe concurrently (will return)
* Pipe commands allow a pipe to be manipulated (such as clearing a halt, retiring all URBs etc)
*
* @note Some pipe commands will block until the pipe's current in-flight URB is complete. If the pipe's state
* changes unexpectedly, this function will return ESP_ERR_INVALID_RESPONSE
* @note This function can block
*
* @param pipe_hdl Pipe handle
* @param command Pipe command
* @retval ESP_OK: Command executed successfully
* @retval ESP_ERR_INVALID_STATE: The pipe is not in the correct state/condition too execute the command
* @retval ESP_ERR_INVALID_RESPONSE: The pipe's state changed unexpectedly
*/
esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command);
@ -490,6 +485,7 @@ hcd_pipe_event_t hcd_pipe_get_event(hcd_pipe_handle_t pipe_hdl);
* - The URB is properly initialized (data buffer and transfer length are set)
* - The URB must not already be enqueued
* - The pipe must be in the HCD_PIPE_STATE_ACTIVE state
* - The pipe cannot be executing a command
*
* @param pipe_hdl Pipe handle
* @param urb URB to enqueue
@ -514,7 +510,7 @@ urb_t *hcd_urb_dequeue(hcd_pipe_handle_t pipe_hdl);
* @brief Abort an enqueued URB
*
* This function will attempt to abort an URB that is already enqueued. If the URB has yet to be executed, it will be
* "cancelled" and can then be dequeued. If the URB is currenty in-flight or has already completed, the URB will not be
* "canceled" and can then be dequeued. If the URB is currently in-flight or has already completed, the URB will not be
* affected by this function.
*
* @param urb URB to abort

View File

@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include "esp_err.h"
#include "usb_private.h"
#include "usbh.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------------ Types --------------------------------------------------------
/**
* @brief Hub driver configuration
*/
typedef struct {
usb_notif_cb_t notif_cb; /**< Notification callback */
void *notif_cb_arg; /**< Notification callback argument */
} hub_config_t;
// ---------------------------------------------- Hub Driver Functions -------------------------------------------------
/**
* @brief Install Hub driver
*
* Entry:
* - USBH must already be installed
* Exit:
* - Install Hub driver memory resources
* - Initializes the HCD root port
*
* @param[in] hub_config Hub driver configuration
* @return esp_err_t
*/
esp_err_t hub_install(hub_config_t *hub_config);
/**
* @brief Uninstall Hub driver
*
* This must be called before uninstalling the USBH
* Entry:
* - Must have stopped the root port
* Exit:
* - HCD root port deinitialized
*
* @return esp_err_t
*/
esp_err_t hub_uninstall(void);
/**
* @brief Start the Hub driver's root port
*
* This will power the root port ON
*
* @return esp_err_t
*/
esp_err_t hub_root_start(void);
/**
* @brief Stops the Hub driver's root port
*
* This will power OFF the root port
*
* @return esp_err_t
*/
esp_err_t hub_root_stop(void);
/**
* @brief Hub driver's processing function
*
* Hub driver handling function that must be called repeatdly to process the Hub driver's events. If blocking, the
* caller can block on the notification callback of source USB_NOTIF_SOURCE_HUB to run this function.
*
* @return esp_err_t
*/
esp_err_t hub_process(void);
#ifdef __cplusplus
}
#endif

View File

@ -1,561 +0,0 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Note: This header file contains the types and macros belong/relate to the USB2.0 protocol and are HW implementation
agnostic. In other words, this header is only meant to be used in the HCD layer and above of the USB Host stack. For
types and macros that are HW implementation specific (i.e., HAL layer and below), add them to the
"hal/usb_types_private.h" header instead.
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
#define USB_CTRL_REQ_ATTR __attribute__((packed))
#define USB_DESC_ATTR __attribute__((packed))
// ------------------------------------------------ Common USB Types ---------------------------------------------------
// --------------------- Bus Related -----------------------
/**
* @brief USB Standard Speeds
*/
typedef enum {
USB_SPEED_LOW = 0, /**< USB Low Speed (1.5 Mbit/s) */
USB_SPEED_FULL, /**< USB Full Speed (12 Mbit/s) */
} usb_speed_t;
// ------------------ Transfer Related ---------------------
/**
* @brief The type of USB transfer
*
* @note The enum values need to match the bmAttributes field of an EP descriptor
*/
typedef enum {
USB_TRANSFER_TYPE_CTRL = 0,
USB_TRANSFER_TYPE_ISOCHRONOUS,
USB_TRANSFER_TYPE_BULK,
USB_TRANSFER_TYPE_INTR,
} usb_transfer_type_t;
/**
* @brief The status of a particular transfer
*/
typedef enum {
USB_TRANSFER_STATUS_COMPLETED, /**< The transfer was successful (but may be short) */
USB_TRANSFER_STATUS_ERROR, /**< The transfer failed because due to excessive errors (e.g. no response or CRC error) */
USB_TRANSFER_STATUS_TIMED_OUT, /**< The transfer failed due to a time out */
USB_TRANSFER_STATUS_CANCELED, /**< The transfer was canceled */
USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */
USB_TRANSFER_STATUS_NO_DEVICE, /**< The transfer failed because the device is no longer valid (e.g., disconnected */
USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */
USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */
} usb_transfer_status_t;
#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 */
/**
* @brief Isochronous packet descriptor
*
* If the number of bytes in an Isochronous transfer is larger than the MPS of the endpoint, the transfer is split
* into multiple packets transmitted at the endpoint's specified interval. An array of Isochronous packet descriptors
* describes how an Isochronous transfer should be split into multiple packets.
*/
typedef struct {
int num_bytes; /**< Number of bytes to transmit/receive in the packet */
int actual_num_bytes; /**< Actual number of bytes transmitted/received in the packet */
usb_transfer_status_t status; /**< Status of the packet */
} usb_isoc_packet_desc_t;
/**
* @brief USB transfer structure
*
* This structure is used to represent a transfer from a software client to an endopint over the USB bus. Some of the
* fields are made const on purpose as they are fixed on allocation. Users should call the appropriate USB Host Driver
* function to allocate a USB transfer structure instead of allocating this structure themselves.
*
* The transfer type is inferred from the endpoint this transfer is sent to. Depending on the transfer type, users
* should note the following:
*
* - Bulk: This structure represents a single bulk transfer. If the number of bytes exceeds the endpoint's MPS, the
* transfer will be split into multiple MPS sized packets followed by a short packet.
* - Control: This structure represents a single control transfer. This first 8 bytes of the data_buffer must be filled
* with the setup packet. The num_bytes field should exclude the size of the setup packet (i.e., set to 0 if
* the control transfer has no data stage).
* - Interrupt: Represents an interrupt transfer. If num_bytes exceeds the MPS of the endpoint, the transfer will be
* split into multiple packets, and each packet is transferred at the endpoint's specified interval.
* - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer
* is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval
* of the endpoint.
*
* @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS
* @note This structure should be allocated via __insert_func_name__()
* @note Once the transfer has be submitted, users should not modify the structure until the transfer has completed
*/
typedef struct usb_transfer_obj usb_transfer_t;
/**
* @brief USB transfer completion callback
*/
typedef void (*usb_transfer_cb_t)(usb_transfer_t *transfer);
struct usb_transfer_obj{
uint8_t *const data_buffer; /**< Pointer to data buffer */
const size_t data_buffer_size; /**< Size of the data buffer in bytes */
int num_bytes; /**< Number of bytes to transfer. Control transfers should exclude size of setup packet. IN transfers should be integer multiple of MPS */
int actual_num_bytes; /**< Actual number of bytes transferred */
uint32_t flags; /**< Transfer flags */
usb_transfer_status_t status; /**< Status of the transfer */
uint32_t timeout; /**< Timeout (in milliseconds) of the packet (currently not supported yet) */
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 */
};
// ---------------------------------------------------- Chapter 9 ------------------------------------------------------
/**
* @brief Descriptor types from USB2.0 specification table 9.5
*/
#define USB_B_DESCRIPTOR_TYPE_DEVICE 0x01
#define USB_B_DESCRIPTOR_TYPE_CONFIGURATION 0x02
#define USB_B_DESCRIPTOR_TYPE_STRING 0x03
#define USB_B_DESCRIPTOR_TYPE_INTERFACE 0x04
#define USB_B_DESCRIPTOR_TYPE_ENDPOINT 0x05
#define USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER 0x06
#define USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION 0x07
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER 0x08
/**
* @brief Descriptor types from USB 2.0 ECN
*/
#define USB_B_DESCRIPTOR_TYPE_OTG 0x09
#define USB_B_DESCRIPTOR_TYPE_DEBUG 0x0a
#define USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION 0x0b
/**
* @brief Descriptor types from Wireless USB spec
*/
#define USB_B_DESCRIPTOR_TYPE_SECURITY 0x0c
#define USB_B_DESCRIPTOR_TYPE_KEY 0x0d
#define USB_B_DESCRIPTOR_TYPE_ENCRYPTION_TYPE 0x0e
#define USB_B_DESCRIPTOR_TYPE_BOS 0x0f
#define USB_B_DESCRIPTOR_TYPE_DEVICE_CAPABILITY 0x10
#define USB_B_DESCRIPTOR_TYPE_WIRELESS_ENDPOINT_COMP 0x11
#define USB_B_DESCRIPTOR_TYPE_WIRE_ADAPTER 0x21
#define USB_B_DESCRIPTOR_TYPE_RPIPE 0x22
#define USB_B_DESCRIPTOR_TYPE_CS_RADIO_CONTROL 0x23
/**
* @brief Descriptor types from UAS specification
*/
#define USB_B_DESCRIPTOR_TYPE_PIPE_USAGE 0x24
// ------------------- Control Request ---------------------
/**
* @brief Size of a USB control transfer setup packet in bytes
*/
#define USB_CTRL_REQ_SIZE 8
/**
* @brief Structure representing a USB control transfer setup packet
*/
typedef union {
struct {
uint8_t bRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} USB_CTRL_REQ_ATTR;
uint8_t val[USB_CTRL_REQ_SIZE];
} usb_ctrl_req_t;
_Static_assert(sizeof(usb_ctrl_req_t) == USB_CTRL_REQ_SIZE, "Size of usb_ctrl_req_t incorrect");
/**
* @brief Bit masks belonging to the bRequestType field of a setup packet
*/
#define USB_B_REQUEST_TYPE_DIR_OUT (0X00 << 7)
#define USB_B_REQUEST_TYPE_DIR_IN (0x01 << 7)
#define USB_B_REQUEST_TYPE_TYPE_STANDARD (0x00 << 5)
#define USB_B_REQUEST_TYPE_TYPE_CLASS (0x01 << 5)
#define USB_B_REQUEST_TYPE_TYPE_VENDOR (0x02 << 5)
#define USB_B_REQUEST_TYPE_TYPE_RESERVED (0x03 << 5)
#define USB_B_REQUEST_TYPE_TYPE_MASK (0x03 << 5)
#define USB_B_REQUEST_TYPE_RECIP_DEVICE (0x00 << 0)
#define USB_B_REQUEST_TYPE_RECIP_INTERFACE (0x01 << 0)
#define USB_B_REQUEST_TYPE_RECIP_ENDPOINT (0x02 << 0)
#define USB_B_REQUEST_TYPE_RECIP_OTHER (0x03 << 0)
#define USB_B_REQUEST_TYPE_RECIP_MASK (0x1f << 0)
/**
* @brief Bit masks belonging to the bRequest field of a setup packet
*/
#define USB_B_REQUEST_GET_STATUS 0x00
#define USB_B_REQUEST_CLEAR_FEATURE 0x01
#define USB_B_REQUEST_SET_FEATURE 0x03
#define USB_B_REQUEST_SET_ADDRESS 0x05
#define USB_B_REQUEST_GET_DESCRIPTOR 0x06
#define USB_B_REQUEST_SET_DESCRIPTOR 0x07
#define USB_B_REQUEST_GET_CONFIGURATION 0x08
#define USB_B_REQUEST_SET_CONFIGURATION 0x09
#define USB_B_REQUEST_GET_INTERFACE 0x0A
#define USB_B_REQUEST_SET_INTERFACE 0x0B
#define USB_B_REQUEST_SYNCH_FRAME 0x0C
/**
* @brief Bit masks belonging to the wValue field of a setup packet
*/
#define USB_W_VALUE_DT_DEVICE 0x01
#define USB_W_VALUE_DT_CONFIG 0x02
#define USB_W_VALUE_DT_STRING 0x03
#define USB_W_VALUE_DT_INTERFACE 0x04
#define USB_W_VALUE_DT_ENDPOINT 0x05
#define USB_W_VALUE_DT_DEVICE_QUALIFIER 0x06
#define USB_W_VALUE_DT_OTHER_SPEED_CONFIG 0x07
#define USB_W_VALUE_DT_INTERFACE_POWER 0x08
/**
* @brief Initializer for a SET_ADDRESS request
*
* Sets the address of a connected device
*/
#define USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req_ptr, addr) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD |USB_B_REQUEST_TYPE_RECIP_DEVICE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_ADDRESS; \
(ctrl_req_ptr)->wValue = (addr); \
(ctrl_req_ptr)->wIndex = 0; \
(ctrl_req_ptr)->wLength = 0; \
})
/**
* @brief Initializer for a request to get a device's device descriptor
*/
#define USB_CTRL_REQ_INIT_GET_DEVICE_DESC(ctrl_req_ptr) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \
(ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_DEVICE << 8); \
(ctrl_req_ptr)->wIndex = 0; \
(ctrl_req_ptr)->wLength = 18; \
})
/**
* @brief Initializer for a request to get a device's current configuration number
*/
#define USB_CTRL_REQ_INIT_GET_CONFIG(ctrl_req_ptr) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_CONFIGURATION; \
(ctrl_req_ptr)->wValue = 0; \
(ctrl_req_ptr)->wIndex = 0; \
(ctrl_req_ptr)->wLength = 1; \
})
/**
* @brief Initializer for a request to get one of the device's current configuration descriptor
*
* - desc_index indicates the configuration's index number
* - Number of bytes of the configuration descriptor to get
*/
#define USB_CTRL_REQ_INIT_GET_CONFIG_DESC(ctrl_req_ptr, desc_index, desc_len) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_IN | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \
(ctrl_req_ptr)->wValue = (USB_W_VALUE_DT_CONFIG << 8) | ((desc_index) & 0xFF); \
(ctrl_req_ptr)->wIndex = 0; \
(ctrl_req_ptr)->wLength = (desc_len); \
})
/**
* @brief Initializer for a request to set a device's current configuration number
*/
#define USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req_ptr, config_num) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_DEVICE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_CONFIGURATION; \
(ctrl_req_ptr)->wValue = (config_num); \
(ctrl_req_ptr)->wIndex = 0; \
(ctrl_req_ptr)->wLength = 0; \
})
/**
* @brief Initializer for a request to set an interface's alternate setting
*/
#define USB_CTRL_REQ_INIT_SET_INTERFACE(ctrl_req_ptr, intf_num, alt_setting_num) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_STANDARD | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \
(ctrl_req_ptr)->bRequest = USB_B_REQUEST_SET_INTERFACE; \
(ctrl_req_ptr)->wValue = (alt_setting_num); \
(ctrl_req_ptr)->wIndex = (intf_num); \
(ctrl_req_ptr)->wLength = 0; \
})
// ---------------- Standard Descriptor --------------------
/**
* @brief Size of dummy USB standard descriptor
*/
#define USB_DESC_STANDARD_SIZE 2
/**
* @brief Dummy USB standard descriptor
*
* All USB standard descriptors start with these two bytes. Use this type traversing over descriptors
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_STANDARD_SIZE];
} usb_desc_standard_t;
_Static_assert(sizeof(usb_desc_standard_t) == USB_DESC_STANDARD_SIZE, "Size of usb_desc_standard_t incorrect");
// ------------------ Device Descriptor --------------------
/**
* @brief Size of a USB device descriptor in bytes
*/
#define USB_DESC_DEVICE_SIZE 18
/**
* @brief Structure representing a USB device descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_DEVICE_SIZE];
} usb_desc_device_t;
_Static_assert(sizeof(usb_desc_device_t) == USB_DESC_DEVICE_SIZE, "Size of usb_desc_device_t incorrect");
/**
* @brief Possible base class values of the bDeviceClass field of a USB device descriptor
*/
#define USB_CLASS_PER_INTERFACE 0x00
#define USB_CLASS_AUDIO 0x01
#define USB_CLASS_COMM 0x02
#define USB_CLASS_HID 0x03
#define USB_CLASS_PHYSICAL 0x05
#define USB_CLASS_STILL_IMAGE 0x06
#define USB_CLASS_PRINTER 0x07
#define USB_CLASS_MASS_STORAGE 0x08
#define USB_CLASS_HUB 0x09
#define USB_CLASS_CDC_DATA 0x0a
#define USB_CLASS_CSCID 0x0b
#define USB_CLASS_CONTENT_SEC 0x0d
#define USB_CLASS_VIDEO 0x0e
#define USB_CLASS_WIRELESS_CONTROLLER 0xe0
#define USB_CLASS_PERSONAL_HEALTHCARE 0x0f
#define USB_CLASS_AUDIO_VIDEO 0x10
#define USB_CLASS_BILLBOARD 0x11
#define USB_CLASS_USB_TYPE_C_BRIDGE 0x12
#define USB_CLASS_MISC 0xef
#define USB_CLASS_APP_SPEC 0xfe
#define USB_CLASS_VENDOR_SPEC 0xff
/**
* @brief Vendor specific subclass code
*/
#define USB_SUBCLASS_VENDOR_SPEC 0xff
// -------------- Configuration Descriptor -----------------
/**
* @brief Size of a short USB configuration descriptor in bytes
*
* @note The size of a full USB configuration includes all the interface and endpoint
* descriptors of that configuration.
*/
#define USB_DESC_CONFIG_SIZE 9
/**
* @brief Structure representing a short USB configuration descriptor
*
* @note The full USB configuration includes all the interface and endpoint
* descriptors of that configuration.
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_CONFIG_SIZE];
} usb_desc_config_t;
_Static_assert(sizeof(usb_desc_config_t) == USB_DESC_CONFIG_SIZE, "Size of usb_desc_config_t incorrect");
/**
* @brief Bit masks belonging to the bmAttributes field of a configuration descriptor
*/
#define USB_BM_ATTRIBUTES_ONE (1 << 7) //Must be set
#define USB_BM_ATTRIBUTES_SELFPOWER (1 << 6) //Self powered
#define USB_BM_ATTRIBUTES_WAKEUP (1 << 5) //Can wake-up
#define USB_BM_ATTRIBUTES_BATTERY (1 << 4) //Battery powered
// ---------- Interface Association Descriptor -------------
/**
* @brief Size of a USB interface association descriptor in bytes
*/
#define USB_DESC_INTF_ASSOC_SIZE 9
/**
* @brief Structure representing a USB interface association descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bFirstInterface;
uint8_t bInterfaceCount;
uint8_t bFunctionClass;
uint8_t bFunctionSubClass;
uint8_t bFunctionProtocol;
uint8_t iFunction;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_INTF_ASSOC_SIZE];
} usb_desc_iad_t;
_Static_assert(sizeof(usb_desc_iad_t) == USB_DESC_INTF_ASSOC_SIZE, "Size of usb_desc_iad_t incorrect");
// ---------------- Interface Descriptor -------------------
/**
* @brief Size of a USB interface descriptor in bytes
*/
#define USB_DESC_INTF_SIZE 9
/**
* @brief Structure representing a USB interface descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_INTF_SIZE];
} usb_desc_intf_t;
_Static_assert(sizeof(usb_desc_intf_t) == USB_DESC_INTF_SIZE, "Size of usb_desc_intf_t incorrect");
// ----------------- Endpoint Descriptor -------------------
/**
* @brief Size of a USB endpoint descriptor in bytes
*/
#define USB_DESC_EP_SIZE 7
/**
* @brief Structure representing a USB endpoint descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} USB_DESC_ATTR;
uint8_t val[USB_DESC_EP_SIZE];
} usb_desc_ep_t;
_Static_assert(sizeof(usb_desc_ep_t) == USB_DESC_EP_SIZE, "Size of usb_desc_ep_t incorrect");
/**
* @brief Bit masks belonging to the bEndpointAddress field of an endpoint descriptor
*/
#define USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK 0x0f
#define USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK 0x80
/**
* @brief Bit masks belonging to the bmAttributes field of an endpoint descriptor
*/
#define USB_BM_ATTRIBUTES_XFERTYPE_MASK 0x03
#define USB_BM_ATTRIBUTES_XFER_CONTROL (0 << 0)
#define USB_BM_ATTRIBUTES_XFER_ISOC (1 << 0)
#define USB_BM_ATTRIBUTES_XFER_BULK (2 << 0)
#define USB_BM_ATTRIBUTES_XFER_INT (3 << 0)
#define USB_BM_ATTRIBUTES_SYNCTYPE_MASK 0x0C /* in bmAttributes */
#define USB_BM_ATTRIBUTES_SYNC_NONE (0 << 2)
#define USB_BM_ATTRIBUTES_SYNC_ASYNC (1 << 2)
#define USB_BM_ATTRIBUTES_SYNC_ADAPTIVE (2 << 2)
#define USB_BM_ATTRIBUTES_SYNC_SYNC (3 << 2)
#define USB_BM_ATTRIBUTES_USAGETYPE_MASK 0x30
#define USB_BM_ATTRIBUTES_USAGE_DATA (0 << 4)
#define USB_BM_ATTRIBUTES_USAGE_FEEDBACK (1 << 4)
#define USB_BM_ATTRIBUTES_USAGE_IMPLICIT_FB (2 << 4)
/**
* @brief Macro helpers to get information about an endpoint from its descriptor
*/
#define USB_DESC_EP_GET_XFERTYPE(desc_ptr) ((usb_transfer_type_t) ((desc_ptr)->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK))
#define USB_DESC_EP_GET_EP_NUM(desc_ptr) ((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK)
#define USB_DESC_EP_GET_EP_DIR(desc_ptr) (((desc_ptr)->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) ? 1 : 0)
#define USB_DESC_EP_GET_MPS(desc_ptr) ((desc_ptr)->wMaxPacketSize & 0x7FF)
// ------------------ String Descriptor --------------------
/**
* @brief Size of a short USB string descriptor in bytes
*/
#define USB_DESC_STR_SIZE 4
/**
* @brief Structure representing a USB string descriptor
*/
typedef union {
struct {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wData[1]; /* UTF-16LE encoded */
} USB_DESC_ATTR;
uint8_t val[USB_DESC_STR_SIZE];
} usb_desc_str_t;
_Static_assert(sizeof(usb_desc_str_t) == USB_DESC_STR_SIZE, "Size of usb_desc_str_t incorrect");
#ifdef __cplusplus
}
#endif

View File

@ -1,34 +1,32 @@
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/queue.h>
#include "usb.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_types_stack.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------------ Types --------------------------------------------------------
typedef struct {
uint8_t *data_buffer;
size_t data_buffer_size;
int num_bytes;
int actual_num_bytes;
uint32_t flags;
usb_device_handle_t device_handle;
uint8_t bEndpointAddress;
usb_transfer_status_t status;
uint32_t timeout;
usb_transfer_cb_t callback;
@ -38,17 +36,49 @@ typedef struct {
} usb_transfer_dummy_t;
_Static_assert(sizeof(usb_transfer_dummy_t) == sizeof(usb_transfer_t), "usb_transfer_dummy_t does not match usb_transfer_t");
struct urb_obj{
TAILQ_ENTRY(urb_obj) tailq_entry;
//HCD context pointer and variables. Must be initialized to NULL and 0 respectively
struct urb_s{
TAILQ_ENTRY(urb_s) tailq_entry;
//HCD handler pointer and variables. Must be initialized to NULL and 0 respectively
void *hcd_ptr;
uint32_t hcd_var;
//Host Driver layer will add its fields here.
//Host Driver layer handler
void *usb_host_client; //Currently only used when submitted to shared pipes (i.e., Device default pipes)
size_t usb_host_header_size; //USB Host may need the data buffer to have a transparent header
//Public transfer structure. Must be last due to variable length array
usb_transfer_t transfer;
};
typedef struct urb_s urb_t;
typedef struct urb_obj urb_t;
typedef enum {
USB_NOTIF_SOURCE_USBH = 0x01,
USB_NOTIF_SOURCE_HUB = 0x02,
} usb_notif_source_t;
typedef bool (*usb_notif_cb_t)(usb_notif_source_t source, bool in_isr, void *context);
// --------------------------------------------------- Allocation ------------------------------------------------------
/**
* @brief Allocate a URB
*
* - Data buffer is allocated in DMA capable memory
* - The constant fields of the URB are also set
* - The data_buffer field of the URB is set to point to start of the allocated data buffer AFTER the header. To access
* the header, users need a negative offset from data_buffer.
*
* @param data_buffer_size Size of the URB's data buffer
* @param header_size Size of header to put in front of URB's data buffer
* @param num_isoc_packets Number of isochronous packet descriptors
* @return urb_t* URB object
*/
urb_t *urb_alloc(size_t data_buffer_size, size_t header_size, int num_isoc_packets);
/**
* @brief Free a URB
*
* @param urb URB object
*/
void urb_free(urb_t *urb);
#ifdef __cplusplus
}

View File

@ -0,0 +1,383 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "hcd.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_types_stack.h"
#ifdef __cplusplus
extern "C" {
#endif
// ------------------------------------------------------ Types --------------------------------------------------------
// ----------------------- 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_t;
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_event_t;
// ---------------------- Callbacks ------------------------
/**
* @brief Callback used to indicate completion of control transfers submitted usbh_dev_submit_ctrl_urb()
* @note This callback is called from within usbh_process()
*/
typedef void (*usbh_ctrl_xfer_cb_t)(usb_device_handle_t dev_hdl, urb_t *urb, void *arg);
/**
* @brief Callback used to indicate that the USBH has an event
*
* @note This callback is called from within usbh_process()
* @note On a USBH_EVENT_DEV_ALL_FREE event, the dev_hdl argument is set to NULL
*/
typedef void (*usbh_event_cb_t)(usb_device_handle_t dev_hdl, usbh_event_t usbh_event, void *arg);
/**
* @brief Callback used by the USBH to request actions from the Hub driver
* @note This callback is called from within usbh_process()
*/
typedef void (*usbh_hub_cb_t)(hcd_port_handle_t port_hdl, usbh_hub_event_t hub_event, void *arg);
// ----------------------- Objects -------------------------
/**
* @brief Configuration for an endpoint being allocated using usbh_ep_alloc()
*/
typedef struct {
const usb_ep_desc_t *ep_desc; /**< Endpoint descriptor */
hcd_pipe_callback_t pipe_cb; /**< Endpoint's pipe callback */
void *pipe_cb_arg; /**< Pipe callback argument */
void *context; /**< Pipe context */
} usbh_ep_config_t;
/**
* @brief USBH configuration used in usbh_install()
*/
typedef struct {
usb_notif_cb_t notif_cb; /**< Notification callback */
void *notif_cb_arg; /**< Notification callback argument */
usbh_ctrl_xfer_cb_t ctrl_xfer_cb; /**< Control transfer callback */
void *ctrl_xfer_cb_arg; /**< Control transfer callback argument */
usbh_event_cb_t event_cb; /**< USBH event callback */
void *event_cb_arg; /**< USBH event callback argument */
hcd_config_t hcd_config; /**< HCD configuration */
} usbh_config_t;
// ------------------------------------------------- USBH Functions ----------------------------------------------------
/**
* @brief Installs the USBH driver
*
* - This function will internally install the HCD
* - This must be called before calling any Hub driver functions
*
* @param usbh_config USBH driver configuration
* @return esp_err_t
*/
esp_err_t usbh_install(const usbh_config_t *usbh_config);
/**
* @brief Uninstall the USBH driver
*
* - This function will uninstall the HCD
* - The Hub driver must be uninstalled before calling this function
*
* @return esp_err_t
*/
esp_err_t usbh_uninstall(void);
/**
* @brief USBH processing function
*
* - USBH processing function that must be called repeatedly to process USBH events
* - If blocking, the caller can block until a USB_NOTIF_SOURCE_USBH notification is received before running this
* function
*
* @return esp_err_t
*/
esp_err_t usbh_process(void);
// ------------------------------------------------ Device Functions ---------------------------------------------------
// --------------------- Device Pool -----------------------
/**
* @brief Open a device by address
*
* A device must be opened before it can be used
*
* @param[in] dev_addr Device address
* @param[out] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl);
/**
* @brief CLose a device
*
* Device can be opened by calling usbh_dev_open()
*
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl);
/**
* @brief Mark that all devices should be freed at the next possible opportunity
*
* A device marked as free will not be freed until the last client using the device has called usbh_dev_close()
*
* @return esp_err_t
*/
esp_err_t usbh_dev_mark_all_free(void);
// ------------------- Single Device ----------------------
/**
* @brief Get a device's address
*
* @note Can be called without opening the device
*
* @param[in] dev_hdl Device handle
* @param[out] dev_addr Device's address
* @return esp_err_t
*/
esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr);
/**
* @brief Get a device's information
*
* @param[in] dev_hdl Device handle
* @param[out] dev_info Device information
* @return esp_err_t
*/
esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info);
/**
* @brief Get a device's device descriptor
*
* - The device descriptor is cached when the device is created by the Hub driver
*
* @param[in] dev_hdl Device handle
* @param[out] dev_desc_ret Device descriptor
* @return esp_err_t
*/
esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret);
/**
* @brief Get a device's active configuration descriptor
*
* Simply returns a reference to the internally cached configuration descriptor
*
* @param[in] dev_hdl Device handle
* @param config_desc_ret
* @return esp_err_t
*/
esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret);
/**
* @brief Submit a control transfer (URB) to a device
*
* @param[in] dev_hdl Device handle
* @param[in] urb URB
* @return esp_err_t
*/
esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb);
// ----------------------------------------------- Endpoint Functions -------------------------------------------------
/**
* @brief Allocate an endpoint on a device
*
* Clients that have opened a device must call this function to allocate all endpoints in an interface that is claimed.
* The pipe handle of the endpoint is returned so that clients can use and control the pipe directly.
*
* @note Default pipes are owned by the USBH. For control transfers, use usbh_dev_submit_ctrl_urb() instead
* @note Device must be opened by the client first
*
* @param[in] dev_hdl Device handle
* @param[in] ep_config
* @param[out] pipe_hdl_ret Pipe handle
* @return esp_err_t
*/
esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret);
/**
* @brief Free and endpoint on a device
*
* Free an endpoint previously opened by usbh_ep_alloc()
*
* @param[in] dev_hdl Device handle
* @param[in] bEndpointAddress Endpoint's address
* @return esp_err_t
*/
esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress);
/**
* @brief Get the context of an endpoint
*
* Get the context variable assigned to and endpoint on allocation.
*
* @param[in] dev_hdl Device handle
* @param[in] bEndpointAddress Endpoint's address
* @param[out] context_ret Context variable
* @return esp_err_t
*/
esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret);
// -------------------------------------------------- Hub Functions ----------------------------------------------------
// ------------------- Device Related ----------------------
/**
* @brief Indicates to USBH that the Hub driver is installed
*
* - The Hub driver must call this function in its installation to indicate the the USBH that it has been installed.
* - This should only be called after the USBH has already be installed
*
* @note Hub Driver only
* @param[in] hub_callback Hub 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);
/**
* @brief Indicates to USBH the start of enumeration for a device
*
* - The Hub driver calls this function before it starts enumerating a new device.
* - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration
* functions.
* - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration.
*
* @note Hub Driver only
* @param[in] port_hdl Handle of the port that the device is connected to
* @param[in] dev_speed Device's speed
* @param[out] new_dev_hdl Device's handle
* @param[out] default_pipe_hdl Device's default pipe handle
* @return esp_err_t
*/
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
*
* 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
* @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);
// ----------------- 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
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @param dev_addr
* @return esp_err_t
*/
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 active configuration descriptor
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @param config_desc_full
* @return esp_err_t
*/
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
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @param bConfigurationValue
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_fill_config_num(usb_device_handle_t dev_hdl, uint8_t bConfigurationValue);
/**
* @brief Indicate the device enumeration is completed
*
* This will all the device to be opened by clients, and also trigger a USBH_EVENT_DEV_NEW event.
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl);
/**
* @brief Indicate that device enumeration has failed
*
* This will cause the enumerating device's resources to be cleaned up
* The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued.
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl);
#ifdef __cplusplus
}
#endif

View File

@ -5,7 +5,8 @@ if(NOT "${target}" MATCHES "^esp32s[2-3]")
return()
endif()
idf_component_register(SRC_DIRS "common" "hcd"
PRIV_INCLUDE_DIRS "../private_include" "common" "hcd"
idf_component_register(
SRC_DIRS "common" "hcd" "usb_host"
PRIV_INCLUDE_DIRS "../private_include" "common" "hcd" "usb_host"
PRIV_REQUIRES cmock usb test_utils
)

View File

@ -15,7 +15,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "usb.h"
#include "usb/usb_types_ch9.h"
#include "test_usb_mock_classes.h"
// ---------------------------------------------------- MSC SCSI -------------------------------------------------------

View File

@ -21,7 +21,7 @@ Host stack.
#include <stdint.h>
#include <stdbool.h>
#include "usb.h"
#include "usb/usb_types_ch9.h"
#ifdef __cplusplus
extern "C" {
@ -94,8 +94,8 @@ Configuration Descriptor:
If you're using a flash driver with different endpoints, modify the endpoint descriptors below.
*/
static const usb_desc_ep_t mock_msc_scsi_bulk_out_ep_desc = {
.bLength = sizeof(usb_desc_ep_t),
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,
@ -103,8 +103,8 @@ static const usb_desc_ep_t mock_msc_scsi_bulk_out_ep_desc = {
.bInterval = 1,
};
static const usb_desc_ep_t mock_msc_scsi_bulk_in_ep_desc = {
.bLength = sizeof(usb_desc_ep_t),
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,
@ -123,12 +123,12 @@ static const usb_desc_ep_t mock_msc_scsi_bulk_in_ep_desc = {
#define MOCK_MSC_SCSI_BULK_IN_EP_ADDR 0x82
#define MOCK_MSC_SCSI_BULK_EP_MPS 64
#define MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \
(ctrl_req_ptr)->bRequestType = USB_B_REQUEST_TYPE_DIR_OUT | USB_B_REQUEST_TYPE_TYPE_CLASS | USB_B_REQUEST_TYPE_RECIP_INTERFACE; \
(ctrl_req_ptr)->bRequest = 0xFF; \
(ctrl_req_ptr)->wValue = 0; \
(ctrl_req_ptr)->wIndex = (intf_num); \
(ctrl_req_ptr)->wLength = 0; \
#define MOCK_MSC_SCSI_REQ_INIT_RESET(setup_pkt_ptr, intf_num) ({ \
(setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \
(setup_pkt_ptr)->bRequest = 0xFF; \
(setup_pkt_ptr)->wValue = 0; \
(setup_pkt_ptr)->wIndex = (intf_num); \
(setup_pkt_ptr)->wLength = 0; \
})
typedef struct __attribute__((packed)) {
@ -193,21 +193,67 @@ Note: The mock HID mouse tests require that USB low speed mouse be connected. Th
- Be implement the HID with standard report format used by mice
- It's configuration 1 should have the following endpoint
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 10
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x413c Dell Computer Corp.
idProduct 0x301a
bcdDevice 1.00
iManufacturer 1
iProduct 2
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0022
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 2 Mouse
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 46
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 10
If you're using another mice with different endpoints, modify the endpoint descriptor below
*/
static const usb_desc_ep_t mock_hid_mouse_in_ep_desc = {
.bLength = sizeof(usb_desc_ep_t),
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,
@ -215,6 +261,14 @@ static const usb_desc_ep_t mock_hid_mouse_in_ep_desc = {
.bInterval = 10, //Interval of 10ms
};
#define MOCK_HID_MOUSE_DEV_ID_VENDOR 0x413C
#define MOCK_HID_MOUSE_DEV_ID_PRODUCT 0x301A
#define MOCK_HID_MOUSE_DEV_DFLT_EP_MPS 8
#define MOCK_HID_MOUSE_INTF_NUMBER 0
#define MOCK_HID_MOUSE_INTF_ALT_SETTING 0
#define MOCK_HID_MOUSE_INTR_IN_EP_ADDR 0x81
#define MOCK_HID_MOUSE_INTR_IN_MPS 0x04
typedef union {
struct {
uint32_t left_button: 1;
@ -241,8 +295,8 @@ ISOC, transferring to a non-existent endpoint should work. The non-existent endp
#define MOCK_ISOC_EP_MPS 512
static const usb_desc_ep_t mock_isoc_out_ep_desc = {
.bLength = sizeof(usb_desc_ep_t),
static const usb_ep_desc_t mock_isoc_out_ep_desc = {
.bLength = sizeof(usb_ep_desc_t),
.bDescriptorType = USB_B_DESCRIPTOR_TYPE_ENDPOINT,
.bEndpointAddress = MOCK_ISOC_EP_NUM,
.bmAttributes = USB_BM_ATTRIBUTES_XFER_ISOC,

View File

@ -1,16 +1,8 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdbool.h>
@ -26,10 +18,10 @@
static void mock_msc_reset_req(hcd_pipe_handle_t default_pipe)
{
//Create URB
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t));
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer;
MOCK_MSC_SCSI_REQ_INIT_RESET(ctrl_req, MOCK_MSC_SCSI_INTF_NUMBER);
urb->transfer.num_bytes = 0;
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t));
usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)urb->transfer.data_buffer;
MOCK_MSC_SCSI_REQ_INIT_RESET(setup_pkt, MOCK_MSC_SCSI_INTF_NUMBER);
urb->transfer.num_bytes = sizeof(usb_setup_packet_t);
//Enqueue, wait, dequeue, and check URB
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);

View File

@ -1,16 +1,8 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
@ -26,7 +18,7 @@
#include "soc/usb_wrap_struct.h"
#include "hcd.h"
#include "usb_private.h"
#include "usb.h"
#include "usb/usb_types_ch9.h"
#include "test_hcd_common.h"
#define PORT_NUM 1
@ -174,6 +166,7 @@ hcd_port_handle_t test_hcd_setup(void)
TEST_ASSERT_EQUAL(ESP_OK, hcd_install(&hcd_config));
//Initialize a port
hcd_port_config_t port_config = {
.fifo_bias = HCD_PORT_FIFO_BIAS_BALANCED,
.callback = port_callback,
.callback_arg = (void *)port_evt_queue,
.context = (void *)port_evt_queue,
@ -237,7 +230,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled
test_hcd_force_conn_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_DISCONNECTED, hcd_port_get_state(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
//Power down the port
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_POWER_OFF));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_NOT_POWERED, hcd_port_get_state(port_hdl));
@ -245,7 +238,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled
// ---------------------------------------------- Pipe Setup/Tear-down -------------------------------------------------
hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_desc_ep_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed)
hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed)
{
//Create a queue for pipe callback to queue up pipe events
QueueHandle_t pipe_evt_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(pipe_event_msg_t));
@ -300,24 +293,24 @@ void test_hcd_free_urb(urb_t *urb)
uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe)
{
//We need to create a URB for the enumeration control transfers
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_ctrl_req_t) + 256);
usb_ctrl_req_t *ctrl_req = (usb_ctrl_req_t *)urb->transfer.data_buffer;
urb_t *urb = test_hcd_alloc_urb(0, sizeof(usb_setup_packet_t) + 256);
usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)urb->transfer.data_buffer;
//Get the device descriptor (note that device might only return 8 bytes)
USB_CTRL_REQ_INIT_GET_DEVICE_DESC(ctrl_req);
urb->transfer.num_bytes = sizeof(usb_desc_device_t);
USB_SETUP_PACKET_INIT_GET_DEVICE_DESC(setup_pkt);
urb->transfer.num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_desc_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
//Update the MPS of the default pipe
usb_desc_device_t *device_desc = (usb_desc_device_t *)(urb->transfer.data_buffer + sizeof(usb_ctrl_req_t));
usb_device_desc_t *device_desc = (usb_device_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_mps(default_pipe, device_desc->bMaxPacketSize0));
//Send a set address request
USB_CTRL_REQ_INIT_SET_ADDR(ctrl_req, ENUM_ADDR); //We only support one device for now so use address 1
urb->transfer.num_bytes = 0;
USB_SETUP_PACKET_INIT_SET_ADDR(setup_pkt, ENUM_ADDR); //We only support one device for now so use address 1
urb->transfer.num_bytes = sizeof(usb_setup_packet_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));
@ -327,8 +320,8 @@ uint8_t test_hcd_enum_device(hcd_pipe_handle_t default_pipe)
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_update_dev_addr(default_pipe, ENUM_ADDR));
//Send a set configuration request
USB_CTRL_REQ_INIT_SET_CONFIG(ctrl_req, ENUM_CONFIG);
urb->transfer.num_bytes = 0;
USB_SETUP_PACKET_INIT_SET_CONFIG(setup_pkt, ENUM_CONFIG);
urb->transfer.num_bytes = sizeof(usb_setup_packet_t);
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(urb, hcd_urb_dequeue(default_pipe));

View File

@ -1,22 +1,14 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "hcd.h"
#include "usb_private.h"
#include "usb.h"
#include "usb/usb_types_ch9.h"
#define URB_CONTEXT_VAL ((void *)0xDEADBEEF)
@ -109,7 +101,7 @@ void test_hcd_wait_for_disconn(hcd_port_handle_t port_hdl, bool already_disabled
* @param dev_speed Device speed of the pipe
* @return hcd_pipe_handle_t Pipe handle
*/
hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_desc_ep_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed);
hcd_pipe_handle_t test_hcd_pipe_alloc(hcd_port_handle_t port_hdl, const usb_ep_desc_t *ep_desc, uint8_t dev_addr, usb_speed_t dev_speed);
/**
* @brief Test the freeing of a pipe

View File

@ -22,7 +22,7 @@
#define TEST_DEV_ADDR 0
#define NUM_URBS 3
#define TRANSFER_MAX_BYTES 256
#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
#define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
/*
Test HCD control pipe URBs (normal completion and early abort)
@ -54,8 +54,8 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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 = URB_CONTEXT_VAL;
}
@ -68,20 +68,20 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]")
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
//Dequeue URBs
//Dequeue URBs, check, and print
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_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
//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);
usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType);
printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength);
}
//Print config desc
for (int i = 0; i < urb_list[0]->transfer.actual_num_bytes; i++) {
printf("%d\t0x%x\n", i, urb_list[0]->transfer.data_buffer[sizeof(usb_ctrl_req_t) + i]);
}
//Enqueue URBs again but abort them short after
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
@ -98,8 +98,11 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]")
//No need to check for URB pointer address as they may be out of order
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
//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(urb->transfer.context, URB_CONTEXT_VAL);
@ -119,8 +122,8 @@ TEST_CASE("Test HCD control pipe URBs", "[hcd][ignore]")
Test HCD control pipe STALL condition, abort, and clear
Purpose:
- Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_HALTED event)
- The HCD_PIPE_CMD_ABORT can retire all URBs
- Test that a control pipe can react to a STALL (i.e., a HCD_PIPE_EVENT_ERROR_STALL event)
- The HCD_PIPE_CMD_FLUSH can retire all URBs
- Pipe clear command can return the pipe to being active
Procedure:
@ -128,7 +131,7 @@ Procedure:
- Setup default pipe and allocate URBs
- Corrupt the first URB so that it will trigger a STALL, then enqueue all the URBs
- Check that a HCD_PIPE_EVENT_ERROR_STALL event is triggered
- Check that all URBs can be retired using HCD_PIPE_CMD_ABORT
- Check that all URBs can be retired using HCD_PIPE_CMD_FLUSH, a HCD_PIPE_EVENT_URB_DONE event should be generated
- Check that the STALL can be cleared by using HCD_PIPE_CMD_CLEAR
- Fix the corrupt first URB and retry the URBs
- Dequeue URBs
@ -146,12 +149,12 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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 = URB_CONTEXT_VAL;
}
//Corrupt the first URB so that it triggers a STALL
((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer)->bRequest = 0xAA;
((usb_setup_packet_t *)urb_list[0]->transfer.data_buffer)->bRequest = 0xAA;
//Enqueue URBs. A STALL should occur
int num_enqueued = 0;
@ -168,14 +171,18 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
//Call the pipe abort command to retire all URBs then dequeue them all
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_ABORT));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
for (int i = 0; i < num_enqueued; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_STALL || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
//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(URB_CONTEXT_VAL, urb->transfer.context);
@ -187,7 +194,7 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
printf("Retrying\n");
//Correct first URB then requeue
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[0]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)urb_list[0]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
@ -199,8 +206,13 @@ TEST_CASE("Test HCD control pipe STALL", "[hcd][ignore]")
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
//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);
usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType);
printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength);
}
//Free URB list and pipe
@ -224,9 +236,10 @@ Purpose:
Procedure:
- Setup HCD and wait for connection
- Setup default pipe and allocate URBs
- Enqqueue URBs but execute a HCD_PIPE_CMD_HALT command immediately after. Halt command should let on
the current going URB finish before actually halting the pipe.
- Un-halt the pipe a HCD_PIPE_CMD_HALT command. Enqueued URBs will be resumed
- Enqqueue URBs but execute a HCD_PIPE_CMD_HALT command immediately after.
- Halt command should immediately halt the current URB and generate a HCD_PIPE_EVENT_URB_DONE
- Other pending URBs should be untouched.
- Un-halt the pipe using a HCD_PIPE_CMD_CLEAR command. Enqueued URBs will be resumed
- Check that all URBs have completed successfully
- Dequeue URBs and teardown
*/
@ -242,8 +255,8 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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 = URB_CONTEXT_VAL;
}
@ -253,6 +266,7 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(default_pipe, urb_list[i]));
}
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
printf("Pipe halted\n");
@ -264,11 +278,20 @@ TEST_CASE("Test HCD control pipe runtime halt and clear", "[hcd][ignore]")
//Wait for each URB to be done, dequeue, and check results
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
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);
usb_config_desc_t *config_desc = (usb_config_desc_t *)(urb->transfer.data_buffer + sizeof(usb_setup_packet_t));
TEST_ASSERT_EQUAL(USB_B_DESCRIPTOR_TYPE_CONFIGURATION , config_desc->bDescriptorType);
printf("Config Desc wTotalLength %d\n", config_desc->wTotalLength);
} else {
//A failed transfer should 0 actual number of bytes transmitted
TEST_ASSERT_EQUAL(0, urb->transfer.actual_num_bytes);
}
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
}

View File

@ -1,16 +1,8 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"

View File

@ -1,16 +1,8 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
@ -65,7 +57,7 @@ TEST_CASE("Test HCD isochronous pipe URBs", "[hcd][ignore]")
//Initialize URBs
for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) {
urb_list[urb_idx] = test_hcd_alloc_urb(NUM_PACKETS_PER_URB, URB_DATA_BUFF_SIZE);
urb_list[urb_idx]->transfer.num_bytes = 0; //num_bytes is not used for ISOC
urb_list[urb_idx]->transfer.num_bytes = URB_DATA_BUFF_SIZE;
urb_list[urb_idx]->transfer.context = URB_CONTEXT_VAL;
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) {
urb_list[urb_idx]->transfer.isoc_packet_desc[pkt_idx].num_bytes = ISOC_PACKET_SIZE;

View File

@ -1,43 +1,40 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "unity.h"
#include "esp_rom_sys.h"
#include "test_utils.h"
#include "test_hcd_common.h"
#define TEST_DEV_ADDR 0
#define NUM_URBS 3
#define TRANSFER_MAX_BYTES 256
#define URB_DATA_BUFF_SIZE (sizeof(usb_ctrl_req_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
#define URB_DATA_BUFF_SIZE (sizeof(usb_setup_packet_t) + TRANSFER_MAX_BYTES) //256 is worst case size for configuration descriptors
#define POST_ENQUEUE_DELAY_US 10
/*
Test a port sudden disconnect and port recovery
Purpose: Test that when sudden disconnection happens on an HCD port, the port will
- Generate the HCD_PORT_EVENT_SUDDEN_DISCONN and be put into the HCD_PORT_STATE_RECOVERY state
- Ongoing URBs and pipes are handled correctly
- Pipes can be halted and flushed after a port error
Procedure:
- Setup the HCD and a port
- Trigger a port connection
- Create a default pipe
- Start transfers but immediately trigger a disconnect
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle the event.
- Check that default pipe received a HCD_PIPE_EVENT_INVALID event. Pipe state should be invalid. Dequeue URBs
- Start transfers but trigger a disconnect after a short delay
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event.
- Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error.
- Check that the default pipe can be halted, a HCD_PIPE_EVENT_URB_DONE event should be generated
- Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated
- Check that all URBs can be dequeued.
- Free default pipe
- Recover the port
- Trigger connection and disconnection again (to make sure the port works post recovery)
@ -56,8 +53,8 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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;
}
@ -66,29 +63,35 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
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_hcd_force_conn_state(false, 0);
//Disconnect event should have occurred. Handle the event
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl));
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));
printf("Sudden disconnect\n");
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
TEST_ASSERT_EQUAL(hcd_pipe_get_state(default_pipe), HCD_PIPE_STATE_INVALID);
//We should be able to halt then flush the pipe
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_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
printf("Pipe halted and flushed\n");
//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_NO_DEVICE);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
//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);
@ -113,17 +116,20 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
Test port suspend and resume with active pipes
Purpose:
- Test port suspend and resume commands work correctly whilst there are active pipes with ongoing transfers
- When suspending, the pipes should be allowed to finish their current ongoing transfer before the bus is suspended.
- When resuming, pipes with pending transfer should be started after the bus is resumed.
- Test port suspend and resume procedure
- When suspending, the pipes should be halted before suspending the port. Any pending transfers should remain pending
- 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 but suspend the port immediately
- Start transfers
- Halt the default pipe after a short delay
- Suspend the port
- Resume the port
- Check that all the URBs have completed successfully
- Check that all the URBs have either completed successfully or been canceled by the pipe halt
- Cleanup URBs and default pipe
- Trigger disconnection and teardown
*/
@ -139,8 +145,8 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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;
}
@ -149,6 +155,13 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
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);
//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));
printf("Suspended\n");
@ -158,13 +171,23 @@ 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_EQUAL(urb->transfer.status, USB_TRANSFER_STATUS_COMPLETED);
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
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);
}
@ -179,18 +202,20 @@ TEST_CASE("Test HCD port suspend and resume", "[hcd][ignore]")
}
/*
Test HCD port disable with active pipes
Test HCD port disable and disconnection
Purpose:
- Test that the port disable command works correctly with active pipes
- Pipes should be to finish their current ongoing transfer before port is disabled
- After disabling the port, all pipes should become invalid.
- Test that the port disable command works correctly
- Check that port can only be disabled when pipes have been halted
- Check that a disconnection after port disable still triggers a HCD_PORT_EVENT_DISCONNECTION event
Procedure:
- Setup HCD, a default pipe, and multiple URBs
- Start transfers but immediately disable the port
- Check pipe received invalid event
- Check that transfer are either done or not executed
- Start transfers
- Halt the default pipe after a short delay
- Check that port can be disabled
- Flush the default pipe and cleanup the default pipe
- Check that a disconnection still works after disable
- Teardown
*/
TEST_CASE("Test HCD port disable", "[hcd][ignore]")
@ -205,8 +230,8 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]")
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 = TRANSFER_MAX_BYTES;
USB_CTRL_REQ_INIT_GET_CONFIG_DESC((usb_ctrl_req_t *)urb_list[i]->transfer.data_buffer, 0, TRANSFER_MAX_BYTES);
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;
}
@ -215,25 +240,32 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]")
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);
//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));
//Check that port can be disabled
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_command(port_hdl, HCD_PORT_CMD_DISABLE));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_DISABLED, hcd_port_get_state(port_hdl));
printf("Disabled\n");
//Pipe should have received (zero or more HCD_PIPE_EVENT_URB_DONE) followed by a HCD_PIPE_EVENT_INVALID (MUST OCCUR)
int num_pipe_events = test_hcd_get_num_pipe_events(default_pipe);
for (int i = 0; i < num_pipe_events - 1; i++) {
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
}
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_INVALID);
//Flush pipe
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
//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_NO_DEVICE);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
TEST_ASSERT_GREATER_THAN(0, urb->transfer.actual_num_bytes);
//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);
@ -244,7 +276,8 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]")
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(default_pipe);
//Cleanup
//Trigger a disconnection and cleanup
test_hcd_wait_for_disconn(port_hdl, true);
test_hcd_teardown(port_hdl);
}
@ -296,8 +329,8 @@ TEST_CASE("Test HCD port command bailout", "[hcd][ignore]")
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_RESPONSE, hcd_port_command(port_hdl, HCD_PORT_CMD_RESUME));
//Check that concurrent task triggered a sudden disconnection
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_SUDDEN_DISCONN);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_SUDDEN_DISCONN, hcd_port_handle_event(port_hdl));
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));
//Cleanup task and semaphore

View File

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
typedef struct {
int num_ctrl_xfer_to_send;
uint16_t idVendor;
uint16_t idProduct;
} ctrl_client_test_param_t;
void ctrl_client_async_seq_task(void *arg);

View File

@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "ctrl_client.h"
#include "usb/usb_host.h"
#include "unity.h"
#include "test_utils.h"
/*
Implementation of a control transfer client used for USB Host Tests.
- Implemented using sequential call patterns, meaning:
- The entire client is contained within a single task
- All API calls and callbacks are run sequentially
- No critical sections required since everything is sequential
- The control transfer client will:
- Register itself as a client
- Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device
- Allocate multiple transfer objects
- Submit a number of control transfers (get configuration descriptor requests)
- Free transfer objects
- Close the device
- Deregister control client
*/
#define MAX(x,y) (((x) >= (y)) ? (x) : (y))
#define CTRL_CLIENT_MAX_EVENT_MSGS 5
#define NUM_TRANSFER_OBJ 3
#define MAX_TRANSFER_BYTES 256
const char *CTRL_CLIENT_TAG = "Ctrl Client";
typedef enum {
TEST_STAGE_WAIT_CONN,
TEST_STAGE_DEV_OPEN,
TEST_STAGE_CTRL_XFER,
TEST_STAGE_CTRL_XFER_WAIT,
TEST_STAGE_DEV_CLOSE,
} test_stage_t;
typedef struct {
ctrl_client_test_param_t test_param;
test_stage_t cur_stage;
test_stage_t next_stage;
uint8_t num_xfer_done;
uint8_t num_xfer_sent;
uint8_t dev_addr_to_open;
usb_host_client_handle_t client_hdl;
usb_device_handle_t dev_hdl;
const usb_config_desc_t *config_desc_cached;
} ctrl_client_obj_t;
static void ctrl_transfer_cb(usb_transfer_t *transfer)
{
ctrl_client_obj_t *ctrl_obj = (ctrl_client_obj_t *)transfer->context;
//Check the completed control transfer
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status);
TEST_ASSERT_EQUAL(ctrl_obj->config_desc_cached->wTotalLength, transfer->actual_num_bytes - sizeof(usb_setup_packet_t));
ctrl_obj->num_xfer_done++;
if (ctrl_obj->num_xfer_sent < ctrl_obj->test_param.num_ctrl_xfer_to_send) {
ctrl_obj->next_stage = TEST_STAGE_CTRL_XFER;
} else if (ctrl_obj->num_xfer_done == ctrl_obj->test_param.num_ctrl_xfer_to_send) {
ctrl_obj->next_stage = TEST_STAGE_DEV_CLOSE;
}
}
static void ctrl_client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg)
{
ctrl_client_obj_t *ctrl_obj = (ctrl_client_obj_t *)arg;
switch (event_msg->event) {
case USB_HOST_CLIENT_EVENT_NEW_DEV:
TEST_ASSERT_EQUAL(TEST_STAGE_WAIT_CONN, ctrl_obj->cur_stage);
ctrl_obj->next_stage = TEST_STAGE_DEV_OPEN;
ctrl_obj->dev_addr_to_open = event_msg->new_dev.address;
break;
default:
abort(); //Should never occur in this test
break;
}
}
void ctrl_client_async_seq_task(void *arg)
{
ctrl_client_obj_t ctrl_obj = {0};
memcpy(&ctrl_obj.test_param, arg, sizeof(ctrl_client_test_param_t));
ctrl_obj.cur_stage = TEST_STAGE_WAIT_CONN;
ctrl_obj.next_stage = TEST_STAGE_WAIT_CONN;
//Register client
usb_host_client_config_t client_config = {
.client_event_callback = ctrl_client_event_cb,
.callback_arg = (void *)&ctrl_obj,
.max_num_event_msg = CTRL_CLIENT_MAX_EVENT_MSGS,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &ctrl_obj.client_hdl));
//Allocate transfers
usb_transfer_t *ctrl_xfer[NUM_TRANSFER_OBJ] = {NULL};
for (int i = 0; i < NUM_TRANSFER_OBJ; i++) {
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(sizeof(usb_setup_packet_t) + MAX_TRANSFER_BYTES, 0, &ctrl_xfer[i]));
ctrl_xfer[i]->callback = ctrl_transfer_cb;
ctrl_xfer[i]->context = (void *)&ctrl_obj;
}
//Wait to be started by main thread
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
ESP_LOGD(CTRL_CLIENT_TAG, "Starting");
bool exit_loop = false;
bool skip_event_handling = false;
while (!exit_loop) {
if (!skip_event_handling) {
TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_handle_events(ctrl_obj.client_hdl, portMAX_DELAY));
}
skip_event_handling = false;
if (ctrl_obj.cur_stage == ctrl_obj.next_stage) {
continue;
}
ctrl_obj.cur_stage = ctrl_obj.next_stage;
switch (ctrl_obj.next_stage) {
case TEST_STAGE_DEV_OPEN: {
ESP_LOGD(CTRL_CLIENT_TAG, "Open");
//Open the device
TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_open(ctrl_obj.client_hdl, ctrl_obj.dev_addr_to_open, &ctrl_obj.dev_hdl));
//Target our transfers to the device
for (int i = 0; i < NUM_TRANSFER_OBJ; i++) {
ctrl_xfer[i]->device_handle = ctrl_obj.dev_hdl;
}
//Check the VID/PID of the opened device
const usb_device_desc_t *device_desc;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(ctrl_obj.dev_hdl, &device_desc));
TEST_ASSERT_EQUAL(ctrl_obj.test_param.idVendor, device_desc->idVendor);
TEST_ASSERT_EQUAL(ctrl_obj.test_param.idProduct, device_desc->idProduct);
//Cache the active configuration descriptor for later comparison
TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_active_config_descriptor(ctrl_obj.dev_hdl, &ctrl_obj.config_desc_cached));
ctrl_obj.next_stage = TEST_STAGE_CTRL_XFER;
skip_event_handling = true;
break;
}
case TEST_STAGE_CTRL_XFER: {
ESP_LOGD(CTRL_CLIENT_TAG, "Transfer");
//Send a control transfer to get the device's configuration descriptor
usb_transfer_t *transfer = ctrl_xfer[ctrl_obj.num_xfer_sent % NUM_TRANSFER_OBJ];
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC((usb_setup_packet_t *)transfer->data_buffer, 0, MAX_TRANSFER_BYTES);
transfer->num_bytes = sizeof(usb_setup_packet_t) + MAX_TRANSFER_BYTES;
transfer->bEndpointAddress = 0;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit_control(ctrl_obj.client_hdl, transfer));
ctrl_obj.num_xfer_sent++;
ctrl_obj.next_stage = TEST_STAGE_CTRL_XFER_WAIT;
skip_event_handling = true;
break;
}
case TEST_STAGE_CTRL_XFER_WAIT: {
//Nothing to do but wait
break;
}
case TEST_STAGE_DEV_CLOSE: {
ESP_LOGD(CTRL_CLIENT_TAG, "Close");
TEST_ASSERT_EQUAL(ESP_OK, usb_host_device_close(ctrl_obj.client_hdl, ctrl_obj.dev_hdl));
exit_loop = true;
break;
}
default:
abort();
break;
}
}
//Free transfers and deregister client
for (int i = 0; i < NUM_TRANSFER_OBJ; i++) {
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(ctrl_xfer[i]));
}
TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(ctrl_obj.client_hdl));
ESP_LOGD(CTRL_CLIENT_TAG, "Done");
vTaskDelete(NULL);
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
typedef struct {
int num_sectors_to_read;
int num_sectors_per_xfer;
uint32_t msc_scsi_xfer_tag;
uint16_t idVendor;
uint16_t idProduct;
} msc_client_test_param_t;
void msc_client_async_seq_task(void *arg);

View File

@ -0,0 +1,242 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "test_usb_mock_classes.h"
#include "msc_client.h"
#include "usb/usb_host.h"
#include "unity.h"
#include "test_utils.h"
/*
Implementation of an MSC client used for USB Host Tests
- Implemented using sequential call patterns, meaning:
- The entire client is contained within a single task
- All API calls and callbacks are run sequentially
- No critical sections required since everything is sequential
- The MSC client will:
- Register itself as a client
- Receive USB_HOST_CLIENT_EVENT_NEW_DEV event message, and open the device
- Allocate IN and OUT transfer objects for MSC SCSI transfers
- Iterate through multiple MSC SCSI block reads
- Free transfer objects
- Close device
- Deregister MSC client
*/
#define MAX(x,y) (((x) >= (y)) ? (x) : (y))
#define MSC_CLIENT_MAX_EVENT_MSGS 5
const char *MSC_CLIENT_TAG = "MSC Client";
typedef enum {
TEST_STAGE_WAIT_CONN,
TEST_STAGE_DEV_OPEN,
TEST_STAGE_MSC_RESET,
TEST_STAGE_MSC_CBW,
TEST_STAGE_MSC_DATA,
TEST_STAGE_MSC_CSW,
TEST_STAGE_DEV_CLOSE,
} test_stage_t;
typedef struct {
msc_client_test_param_t test_param;
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;
int num_sectors_read;
} msc_client_obj_t;
static void msc_transfer_cb(usb_transfer_t *transfer)
{
msc_client_obj_t *msc_obj = (msc_client_obj_t *)transfer->context;
switch (msc_obj->cur_stage) {
case TEST_STAGE_MSC_RESET: {
//Check MSC SCSI interface reset
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status);
TEST_ASSERT_EQUAL(transfer->num_bytes, transfer->actual_num_bytes);
msc_obj->next_stage = TEST_STAGE_MSC_CBW;
break;
}
case TEST_STAGE_MSC_CBW: {
//Check MSC SCSI CBW transfer
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status);
TEST_ASSERT_EQUAL(sizeof(mock_msc_bulk_cbw_t), transfer->actual_num_bytes);
msc_obj->next_stage = TEST_STAGE_MSC_DATA;
break;
}
case TEST_STAGE_MSC_DATA: {
//Check MSC SCSI data IN transfer
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status);
TEST_ASSERT_EQUAL(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj->test_param.num_sectors_per_xfer, transfer->actual_num_bytes);
msc_obj->next_stage = TEST_STAGE_MSC_CSW;
break;
}
case TEST_STAGE_MSC_CSW: {
//Check MSC SCSI CSW transfer
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, transfer->status);
TEST_ASSERT_EQUAL(true, mock_msc_scsi_check_csw((mock_msc_bulk_csw_t *)transfer->data_buffer, msc_obj->test_param.msc_scsi_xfer_tag));
msc_obj->num_sectors_read += msc_obj->test_param.num_sectors_per_xfer;
if (msc_obj->num_sectors_read < msc_obj->test_param.num_sectors_to_read) {
msc_obj->next_stage = TEST_STAGE_MSC_CBW;
} else {
msc_obj->next_stage = TEST_STAGE_DEV_CLOSE;
}
break;
}
default: {
abort();
break;
}
}
}
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_seq_task(void *arg)
{
msc_client_obj_t msc_obj;
memcpy(&msc_obj.test_param, arg, sizeof(msc_client_test_param_t));
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;
msc_obj.num_sectors_read = 0;
//Register client
usb_host_client_config_t client_config = {
.client_event_callback = msc_client_event_cb,
.callback_arg = (void *)&msc_obj,
.max_num_event_msg = MSC_CLIENT_MAX_EVENT_MSGS,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_register(&client_config, &msc_obj.client_hdl));
//Allocate transfers
usb_transfer_t *xfer_out = NULL; //Must be large enough to contain CBW and MSC reset control transfer
usb_transfer_t *xfer_in = NULL; //Must be large enough to contain CSW and Data
size_t out_worst_case_size = MAX(sizeof(mock_msc_bulk_cbw_t), sizeof(usb_setup_packet_t));
size_t in_worst_case_size = usb_host_round_up_to_mps(MAX(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj.test_param.num_sectors_per_xfer, sizeof(mock_msc_bulk_csw_t)), MOCK_MSC_SCSI_BULK_EP_MPS);
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(out_worst_case_size, 0, &xfer_out));
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_alloc(in_worst_case_size, 0, &xfer_in));
xfer_out->callback = msc_transfer_cb;
xfer_in->callback = msc_transfer_cb;
xfer_out->context = (void *)&msc_obj;
xfer_in->context = (void *)&msc_obj;
//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;
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_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));
//Target our transfers to the device
xfer_out->device_handle = msc_obj.dev_hdl;
xfer_in->device_handle = msc_obj.dev_hdl;
//Check the VID/PID of the opened device
const usb_device_desc_t *device_desc;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(msc_obj.dev_hdl, &device_desc));
TEST_ASSERT_EQUAL(msc_obj.test_param.idVendor, device_desc->idVendor);
TEST_ASSERT_EQUAL(msc_obj.test_param.idProduct, device_desc->idProduct);
//Claim the MSC interface
TEST_ASSERT_EQUAL(ESP_OK, usb_host_interface_claim(msc_obj.client_hdl, msc_obj.dev_hdl, MOCK_MSC_SCSI_INTF_NUMBER, MOCK_MSC_SCSI_INTF_ALT_SETTING));
msc_obj.next_stage = TEST_STAGE_MSC_RESET;
skip_event_handling = true; //Need to execute TEST_STAGE_MSC_RESET
break;
}
case TEST_STAGE_MSC_RESET: {
ESP_LOGD(MSC_CLIENT_TAG, "MSC Reset");
//Send an MSC SCSI interface reset
MOCK_MSC_SCSI_REQ_INIT_RESET((usb_setup_packet_t *)xfer_out->data_buffer, MOCK_MSC_SCSI_INTF_NUMBER);
xfer_out->num_bytes = sizeof(usb_setup_packet_t);
xfer_out->bEndpointAddress = 0;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit_control(msc_obj.client_hdl, xfer_out));
//Next stage set from transfer callback
break;
}
case TEST_STAGE_MSC_CBW: {
ESP_LOGD(MSC_CLIENT_TAG, "CBW");
mock_msc_scsi_init_cbw((mock_msc_bulk_cbw_t *)xfer_out->data_buffer, true, msc_obj.next_stage, msc_obj.test_param.num_sectors_per_xfer, msc_obj.test_param.msc_scsi_xfer_tag);
xfer_out->num_bytes = sizeof(mock_msc_bulk_cbw_t);
xfer_out->bEndpointAddress = MOCK_MSC_SCSI_BULK_OUT_EP_ADDR;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_out));
//Next stage set from transfer callback
break;
}
case TEST_STAGE_MSC_DATA: {
ESP_LOGD(MSC_CLIENT_TAG, "Data");
xfer_in->num_bytes = usb_host_round_up_to_mps(MOCK_MSC_SCSI_SECTOR_SIZE * msc_obj.test_param.num_sectors_per_xfer, MOCK_MSC_SCSI_BULK_EP_MPS);
xfer_in->bEndpointAddress = MOCK_MSC_SCSI_BULK_IN_EP_ADDR;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in));
//Next stage set from transfer callback
break;
}
case TEST_STAGE_MSC_CSW: {
ESP_LOGD(MSC_CLIENT_TAG, "CSW");
xfer_in->num_bytes = usb_host_round_up_to_mps(sizeof(mock_msc_bulk_csw_t), MOCK_MSC_SCSI_BULK_EP_MPS);
xfer_in->bEndpointAddress = MOCK_MSC_SCSI_BULK_IN_EP_ADDR;
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_submit(xfer_in));
//Next stage set from transfer callback
break;
}
case TEST_STAGE_DEV_CLOSE: {
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;
break;
}
default:
abort();
break;
}
}
//Free transfers and deregister the client
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_out));
TEST_ASSERT_EQUAL(ESP_OK, usb_host_transfer_free(xfer_in));
TEST_ASSERT_EQUAL(ESP_OK, usb_host_client_deregister(msc_obj.client_hdl));
ESP_LOGD(MSC_CLIENT_TAG, "Done");
vTaskDelete(NULL);
}

View File

@ -0,0 +1,157 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_intr_alloc.h"
#include "test_usb_mock_classes.h"
#include "msc_client.h"
#include "ctrl_client.h"
#include "usb/usb_host.h"
#include "unity.h"
#include "test_utils.h"
#define TEST_MSC_NUM_SECTORS_TOTAL 10
#define TEST_MSC_NUM_SECTORS_PER_XFER 2
#define TEST_MSC_SCSI_TAG 0xDEADBEEF
#define TEST_CTRL_NUM_TRANSFERS 30
// --------------------------------------------------- Test Cases ------------------------------------------------------
/*
Test USB Host Asynchronous API single client
Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class)
Purpose:
- Test that USB Host Asynchronous API works correctly with a single client
- Test that a client can be created
- Test that client can operate concurrently in a separate thread
- Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
Procedure:
- Install USB Host Library
- Create a task to run an MSC client
- Start the MSC client task. It will execute a bunch of MSC SCSI sector reads
- Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event
- Free all devices
- Uninstall USB Host Library
*/
TEST_CASE("Test USB Host async (single client)", "[usb_host][ignore]")
{
//Install USB Host
usb_host_config_t host_config = {
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
printf("Installed\n");
//Create task to run client that communicates with MSC SCSI interface
msc_client_test_param_t params = {
.num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL,
.num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER,
.msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG,
.idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
.idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
};
TaskHandle_t task_hdl;
xTaskCreatePinnedToCore(msc_client_async_seq_task, "async", 4096, (void *)&params, 2, &task_hdl, 0);
//Start the task
xTaskNotifyGive(task_hdl);
while (1) {
//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_OK, usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
break;
}
}
//Short delay to allow task to be cleaned up
vTaskDelay(10);
//Clean up USB Host
ESP_ERROR_CHECK(usb_host_uninstall());
}
/*
Test USB Host Asynchronous API with multiple clients
Requires: This test requires an MSC SCSI device to be attached (see the MSC mock class)
Purpose:
- Test the USB Host Asynchronous API works correctly with multiple clients
- Test that multiple clients can be created
- Test that multiple clients can operate concurrently in separate threads
- Test that the main thread is able to detect library events (such as USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
Procedure:
- Install USB Host Library
- Create separate tasks to run an MSC client and Ctrl Client
- MSC Client will execute a bunch of MSC SCSI sector reads
- Ctrl Client will execute a bunch of control transfers
- Wait for the host library event handler to report a USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS event
- Free all devices
- Uninstall USB Host Library
*/
TEST_CASE("Test USB Host async (multi client)", "[usb_host][ignore]")
{
//Install USB Host
usb_host_config_t host_config = {
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
printf("Installed\n");
//Create task to run the MSC client
msc_client_test_param_t msc_params = {
.num_sectors_to_read = TEST_MSC_NUM_SECTORS_TOTAL,
.num_sectors_per_xfer = TEST_MSC_NUM_SECTORS_PER_XFER,
.msc_scsi_xfer_tag = TEST_MSC_SCSI_TAG,
.idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
.idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
};
TaskHandle_t msc_task_hdl;
xTaskCreatePinnedToCore(msc_client_async_seq_task, "msc", 4096, (void *)&msc_params, 2, &msc_task_hdl, 0);
//Create task a control transfer client
ctrl_client_test_param_t ctrl_params = {
.num_ctrl_xfer_to_send = TEST_CTRL_NUM_TRANSFERS,
.idVendor = MOCK_MSC_SCSI_DEV_ID_VENDOR,
.idProduct = MOCK_MSC_SCSI_DEV_ID_PRODUCT,
};
TaskHandle_t ctrl_task_hdl;
xTaskCreatePinnedToCore(ctrl_client_async_seq_task, "ctrl", 4096, (void *)&ctrl_params, 2, &ctrl_task_hdl, 0);
//Start both tasks
xTaskNotifyGive(msc_task_hdl);
xTaskNotifyGive(ctrl_task_hdl);
while (1) {
//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_OK, usb_host_device_free_all());
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
break;
}
}
//Short delay to allow task to be cleaned up
vTaskDelay(10);
//Clean up USB Host
ESP_ERROR_CHECK(usb_host_uninstall());
}

View File

@ -0,0 +1,439 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "unity.h"
#include "test_utils.h"
#include "usb/usb_host.h"
/*
Tests that check the configuration descriptor parsing functions provided in usb_host.h work by parsing a fixed
configuration descriptor. The fixed configuration descriptor used in this test is provided below (both in textual and
byte format), and is of a UVC device. Thus the configuration descriptor has a good set of scenarios that can be tested
(such as multiple interfaces, alternate settings, class specific descriptors, default endpoint only interfaces etc).
*/
/*
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0185
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Association:
bLength 8
bDescriptorType 11
bFirstInterface 0
bInterfaceCount 2
bFunctionClass 14 Video
bFunctionSubClass 3 Video Interface Collection
bFunctionProtocol 0
iFunction 5
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 14 Video
bInterfaceSubClass 1 Video Control
bInterfaceProtocol 0
iInterface 5
VideoControl Interface Descriptor:
bLength 13
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdUVC 1.00
wTotalLength 0x004f
dwClockFrequency 15.000000MHz
bInCollection 1
baInterfaceNr( 0) 1
VideoControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 4
wTerminalType 0x0101 USB Streaming
bAssocTerminal 0
bSourceID 3
iTerminal 0
VideoControl Interface Descriptor:
bLength 28
bDescriptorType 36
bDescriptorSubtype 6 (EXTENSION_UNIT)
bUnitID 3
guidExtensionCode {4cf18db6-abd0-495c-9876-1fa3942ff9fa}
bNumControl 24
bNrPins 1
baSourceID( 0) 2
bControlSize 3
bmControls( 0) 0xff
bmControls( 1) 0xff
bmControls( 2) 0xff
iExtension 0
VideoControl Interface Descriptor:
bLength 18
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 1
wTerminalType 0x0201 Camera Sensor
bAssocTerminal 0
iTerminal 0
wObjectiveFocalLengthMin 0
wObjectiveFocalLengthMax 0
wOcularFocalLength 0
bControlSize 3
bmControls 0x0000000e
Auto-Exposure Mode
Auto-Exposure Priority
Exposure Time (Absolute)
VideoControl Interface Descriptor:
bLength 11
bDescriptorType 36
bDescriptorSubtype 5 (PROCESSING_UNIT)
Warning: Descriptor too short
bUnitID 2
bSourceID 1
wMaxMultiplier 0
bControlSize 2
bmControls 0x0000177f
Brightness
Contrast
Hue
Saturation
Sharpness
Gamma
White Balance Temperature
Backlight Compensation
Gain
Power Line Frequency
White Balance Temperature, Auto
iProcessing 0
bmVideoStandards 0x62
NTSC - 525/60
PAL - 525/60
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 6
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 14 Video
bInterfaceSubClass 2 Video Streaming
bInterfaceProtocol 0
iInterface 0
VideoStreaming Interface Descriptor:
bLength 15
bDescriptorType 36
bDescriptorSubtype 1 (INPUT_HEADER)
bNumFormats 2
wTotalLength 0x00f7
bEndPointAddress 129
bmInfo 0
bTerminalLink 4
bStillCaptureMethod 0
bTriggerSupport 0
bTriggerUsage 0
bControlSize 1
bmaControls( 0) 0
bmaControls( 1) 0
VideoStreaming Interface Descriptor:
bLength 27
bDescriptorType 36
bDescriptorSubtype 4 (FORMAT_UNCOMPRESSED)
bFormatIndex 1
bNumFrameDescriptors 2
guidFormat {85f6cc1d-0c9f-44f5-9ce0-97c7dd8c98ab}
bBitsPerPixel 16
bDefaultFrameIndex 1
bAspectRatioX 0
bAspectRatioY 0
bmInterlaceFlags 0x00
Interlaced stream or variable: No
Fields per frame: 2 fields
Field 1 first: No
Field pattern: Field 1 only
bCopyProtect 0
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)
bFrameIndex 1
bmCapabilities 0x00
Still image unsupported
wWidth 480
wHeight 320
dwMinBitRate 12288000
dwMaxBitRate 12288000
dwMaxVideoFrameBufferSize 307200
dwDefaultFrameInterval 2000000
bFrameIntervalType 1
dwFrameInterval( 0) 2000000
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)
bFrameIndex 2
bmCapabilities 0x00
Still image unsupported
wWidth 640
wHeight 480
dwMinBitRate 73728000
dwMaxBitRate 73728000
dwMaxVideoFrameBufferSize 614400
dwDefaultFrameInterval 666666
bFrameIntervalType 1
dwFrameInterval( 0) 666666
VideoStreaming Interface Descriptor:
bLength 11
bDescriptorType 36
bDescriptorSubtype 6 (FORMAT_MJPEG)
bFormatIndex 2
bNumFrameDescriptors 4
bFlags 0
Fixed-size samples: No
bDefaultFrameIndex 1
bAspectRatioX 0
bAspectRatioY 0
bmInterlaceFlags 0x00
Interlaced stream or variable: No
Fields per frame: 1 fields
Field 1 first: No
Field pattern: Field 1 only
bCopyProtect 0
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 7 (FRAME_MJPEG)
bFrameIndex 1
bmCapabilities 0x00
Still image unsupported
wWidth 640
wHeight 480
dwMinBitRate 36864000
dwMaxBitRate 36864000
dwMaxVideoFrameBufferSize 307789
dwDefaultFrameInterval 666666
bFrameIntervalType 1
dwFrameInterval( 0) 666666
VideoStreaming Interface Descriptor:
bLength 38
bDescriptorType 36
bDescriptorSubtype 7 (FRAME_MJPEG)
bFrameIndex 2
bmCapabilities 0x00
Still image unsupported
wWidth 480
wHeight 320
dwMinBitRate 6144000
dwMaxBitRate 18432000
dwMaxVideoFrameBufferSize 154189
dwDefaultFrameInterval 666666
bFrameIntervalType 3
dwFrameInterval( 0) 666666
dwFrameInterval( 1) 1000000
dwFrameInterval( 2) 2000000
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 7 (FRAME_MJPEG)
bFrameIndex 3
bmCapabilities 0x00
Still image unsupported
wWidth 352
wHeight 288
dwMinBitRate 12165120
dwMaxBitRate 12165120
dwMaxVideoFrameBufferSize 101965
dwDefaultFrameInterval 666666
bFrameIntervalType 1
dwFrameInterval( 0) 666666
VideoStreaming Interface Descriptor:
bLength 30
bDescriptorType 36
bDescriptorSubtype 7 (FRAME_MJPEG)
bFrameIndex 4
bmCapabilities 0x00
Still image unsupported
wWidth 320
wHeight 240
dwMinBitRate 9216000
dwMaxBitRate 9216000
dwMaxVideoFrameBufferSize 77389
dwDefaultFrameInterval 666666
bFrameIntervalType 1
dwFrameInterval( 0) 666666
VideoStreaming Interface Descriptor:
bLength 6
bDescriptorType 36
bDescriptorSubtype 13 (COLORFORMAT)
bColorPrimaries 1 (BT.709,sRGB)
bTransferCharacteristics 1 (BT.709)
bMatrixCoefficients 4 (SMPTE 170M (BT.601))
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 14 Video
bInterfaceSubClass 2 Video Streaming
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 5
Transfer Type Isochronous
Synch Type Asynchronous
Usage Type Data
wMaxPacketSize 0x03bc 1x 956 bytes
bInterval 1
*/
static uint8_t config_desc_bytes [] = {
0x09, 0x02, 0x85, 0x01, 0x02, 0x01, 0x00, 0x80, 0xFA, 0x08, 0x0B, 0x00, 0x02, 0x0E, 0x03, 0x00, 0x05, 0x09, 0x04,
0x00, 0x00, 0x01, 0x0E, 0x01, 0x00, 0x05, 0x0D, 0x24, 0x01, 0x00, 0x01, 0x4F, 0x00, 0xC0, 0xE1, 0xE4, 0x00, 0x01,
0x01, 0x09, 0x24, 0x03, 0x04, 0x01, 0x01, 0x00, 0x03, 0x00, 0x1C, 0x24, 0x06, 0x03, 0xB6, 0x8D, 0xF1, 0x4C, 0xD0,
0xAB, 0x5C, 0x49, 0x76, 0x98, 0xFA, 0xF9, 0x2F, 0x94, 0xA3, 0x1F, 0x18, 0x01, 0x02, 0x03, 0xFF, 0xFF, 0xFF, 0x00,
0x12, 0x24, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0E, 0x00, 0x00, 0x0B,
0x24, 0x05, 0x02, 0x01, 0x00, 0x00, 0x02, 0x7F, 0x17, 0x00, 0x07, 0x05, 0x83, 0x03, 0x10, 0x00, 0x06, 0x05, 0x25,
0x03, 0x80, 0x00, 0x09, 0x04, 0x01, 0x00, 0x00, 0x0E, 0x02, 0x00, 0x00, 0x0F, 0x24, 0x01, 0x02, 0xF7, 0x00, 0x81,
0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x1B, 0x24, 0x04, 0x01, 0x02, 0x1D, 0xCC, 0xF6, 0x85, 0x9F, 0x0C,
0xF5, 0x44, 0xE0, 0x9C, 0xAB, 0x98, 0x8C, 0xDD, 0xC7, 0x97, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x24, 0x05,
0x01, 0x00, 0xE0, 0x01, 0x40, 0x01, 0x00, 0x80, 0xBB, 0x00, 0x00, 0x80, 0xBB, 0x00, 0x00, 0xB0, 0x04, 0x00, 0x80,
0x84, 0x1E, 0x00, 0x01, 0x80, 0x84, 0x1E, 0x00, 0x1E, 0x24, 0x05, 0x02, 0x00, 0x80, 0x02, 0xE0, 0x01, 0x00, 0x00,
0x65, 0x04, 0x00, 0x00, 0x65, 0x04, 0x00, 0x60, 0x09, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00,
0x0B, 0x24, 0x06, 0x02, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x24, 0x07, 0x01, 0x00, 0x80, 0x02, 0xE0,
0x01, 0x00, 0x80, 0x32, 0x02, 0x00, 0x80, 0x32, 0x02, 0x4D, 0xB2, 0x04, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A,
0x2C, 0x0A, 0x00, 0x26, 0x24, 0x07, 0x02, 0x00, 0xE0, 0x01, 0x40, 0x01, 0x00, 0xC0, 0x5D, 0x00, 0x00, 0x40, 0x19,
0x01, 0x4D, 0x5A, 0x02, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x03, 0x2A, 0x2C, 0x0A, 0x00, 0x40, 0x42, 0x0F, 0x00, 0x80,
0x84, 0x1E, 0x00, 0x1E, 0x24, 0x07, 0x03, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0xA0, 0xB9, 0x00, 0x00, 0xA0, 0xB9,
0x00, 0x4D, 0x8E, 0x01, 0x00, 0x2A, 0x2C, 0x0A, 0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00, 0x1E, 0x24, 0x07, 0x04, 0x00,
0x40, 0x01, 0xF0, 0x00, 0x00, 0xA0, 0x8C, 0x00, 0x00, 0xA0, 0x8C, 0x00, 0x4D, 0x2E, 0x01, 0x00, 0x2A, 0x2C, 0x0A,
0x00, 0x01, 0x2A, 0x2C, 0x0A, 0x00, 0x06, 0x24, 0x0D, 0x01, 0x01, 0x04, 0x09, 0x04, 0x01, 0x01, 0x01, 0x0E, 0x02,
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0xBC, 0x03, 0x01,
};
_Static_assert(sizeof(config_desc_bytes) == 0x0185, "Configuration Descriptor size does not match");
#define TEST_NUM_INTF_DESC 3 //Total number of interface descriptors (including alternate)
// --------------------- Sub-Test 1 ------------------------
/*
Test if we can walk the configuration descriptor to find each interface descriptor
*/
static void test_walk_desc(const usb_config_desc_t *config_desc)
{
int offset = 0;
const usb_standard_desc_t *cur_desc = (usb_standard_desc_t *)config_desc;
for (int i = 0; i < TEST_NUM_INTF_DESC; i++) {
cur_desc = usb_host_parse_next_descriptor_of_type(cur_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset);
TEST_ASSERT_NOT_EQUAL(NULL, cur_desc);
}
//Attempting to look for another interface descriptor should result in NULL
cur_desc = usb_host_parse_next_descriptor_of_type(cur_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset);
TEST_ASSERT_EQUAL(NULL, cur_desc);
}
/*
Test if the count of number of alternate descriptors is correct
*/
static void test_alt_intf_desc_count(const usb_config_desc_t *config_desc)
{
//bInterface 0 has no alternate interfaces
TEST_ASSERT_EQUAL(0, usb_host_parse_interface_number_of_alternate(config_desc, 0));
//bInterface 1 has 1 alternate interface
TEST_ASSERT_EQUAL(1, usb_host_parse_interface_number_of_alternate(config_desc, 1));
//Non existent bInterface 2 should return -1
TEST_ASSERT_EQUAL(-1, usb_host_parse_interface_number_of_alternate(config_desc, 2));
}
static void test_parse_intf_and_ep(const usb_config_desc_t *config_desc)
{
int offset_intf = 0;
//Get bInterfaceNumber 0 (index 0)
const usb_intf_desc_t *intf_desc = usb_host_parse_interface(config_desc, 0, 0, &offset_intf);
TEST_ASSERT_NOT_EQUAL(NULL, intf_desc);
//Should only have one endpoint
int offset_ep = offset_intf;
const usb_ep_desc_t *ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep);
TEST_ASSERT_NOT_EQUAL(NULL, ep_desc);
TEST_ASSERT_EQUAL(0x83, ep_desc->bEndpointAddress);
offset_ep = offset_intf;
ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 1, config_desc->wTotalLength, &offset_ep);
TEST_ASSERT_EQUAL(NULL, ep_desc);
//Get bInterfaceNumber 1 alternate setting 0
offset_intf = 0;
intf_desc = usb_host_parse_interface(config_desc, 1, 0, &offset_intf);
TEST_ASSERT_NOT_EQUAL(NULL, intf_desc);
//Should have no endpoints
offset_ep = offset_intf;
ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep);
TEST_ASSERT_EQUAL(NULL, ep_desc);
//Get bInterfaceNumber 1 alternate setting 1
offset_intf = 0;
intf_desc = usb_host_parse_interface(config_desc, 1, 1, &offset_intf);
TEST_ASSERT_NOT_EQUAL(NULL, intf_desc);
//Should only have one endpoint
offset_ep = offset_intf;
ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 0, config_desc->wTotalLength, &offset_ep);
TEST_ASSERT_NOT_EQUAL(NULL, ep_desc);
TEST_ASSERT_EQUAL(0x81, ep_desc->bEndpointAddress);
offset_ep = offset_intf;
ep_desc = usb_host_parse_endpoint_by_index(intf_desc, 1, config_desc->wTotalLength, &offset_ep);
TEST_ASSERT_EQUAL(NULL, ep_desc);
}
static void test_parse_ep_by_address(const usb_config_desc_t *config_desc)
{
int offset_ep = 0;
//Get bInterface 0 bAlternateSetting 0 EP 0x83
const usb_ep_desc_t *ep_desc = usb_host_parse_endpoint_by_address(config_desc, 0, 0, 0x83, &offset_ep);
TEST_ASSERT_NOT_EQUAL(NULL, ep_desc);
TEST_ASSERT_EQUAL(0x83, ep_desc->bEndpointAddress);
//Getting same EP address under different interface should return NULL
offset_ep = 0;
ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 0, 0x83, &offset_ep);
TEST_ASSERT_EQUAL(NULL, ep_desc);
//Get bInterface 1 bAlternateSetting 1 EP 0x81
offset_ep = 0;
ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 1, 0x81, &offset_ep);
TEST_ASSERT_NOT_EQUAL(NULL, ep_desc);
TEST_ASSERT_EQUAL(0x81, ep_desc->bEndpointAddress);
//Getting same EP address under different interface should return NULL
offset_ep = 0;
ep_desc = usb_host_parse_endpoint_by_address(config_desc, 1, 0, 0x81, &offset_ep);
TEST_ASSERT_EQUAL(NULL, ep_desc);
}
TEST_CASE("Test USB Host descriptor parsing", "[usb_host][ignore]")
{
const usb_config_desc_t *config_desc = (const usb_config_desc_t *)config_desc_bytes;
test_walk_desc(config_desc);
test_alt_intf_desc_count(config_desc);
test_parse_intf_and_ep(config_desc);
test_parse_ep_by_address(config_desc);
}

1270
components/usb/usb_host.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include "usb/usb_host_misc.h"
#include "usb/usb_types_ch9.h"
// ---------------------------------------- Configuration Descriptor Parsing -------------------------------------------
const usb_standard_desc_t *usb_host_parse_next_descriptor(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, int *offset)
{
assert(cur_desc != NULL && offset != NULL);
if (*offset >= wTotalLength) {
return NULL; //We have traversed the entire configuration descriptor
}
if (*offset + cur_desc->bLength >= wTotalLength) {
return NULL; //Next descriptor is out of bounds
}
//Return the next descriptor, update offset
const usb_standard_desc_t *ret_desc = (const usb_standard_desc_t *)(((uint32_t)cur_desc) + cur_desc->bLength);
*offset += cur_desc->bLength;
return ret_desc;
}
const usb_standard_desc_t *usb_host_parse_next_descriptor_of_type(const usb_standard_desc_t *cur_desc, uint16_t wTotalLength, uint8_t bDescriptorType, int *offset)
{
assert(cur_desc != NULL && offset != NULL);
int offset_temp = *offset; //We only want to update offset if we've actually found a descriptor
//Keep stepping over descriptors until we find one of bDescriptorType or until we go out of bounds
const usb_standard_desc_t *ret_desc = usb_host_parse_next_descriptor(cur_desc, wTotalLength, &offset_temp);
while (ret_desc != NULL) {
if (ret_desc->bDescriptorType == bDescriptorType) {
break;
}
ret_desc = usb_host_parse_next_descriptor(ret_desc, wTotalLength, &offset_temp);
}
if (ret_desc != NULL) {
//We've found a descriptor. Update the offset
*offset = offset_temp;
}
return ret_desc;
}
int usb_host_parse_interface_number_of_alternate(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber)
{
assert(config_desc != NULL);
int offset = 0;
//Find the first interface descriptor of bInterfaceNumber
const usb_intf_desc_t *first_intf_desc = usb_host_parse_interface(config_desc, bInterfaceNumber, 0, &offset);
if (first_intf_desc == NULL) {
return -1; //bInterfaceNumber not found
}
int num_alt_setting = 0;
const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)first_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset);
while (next_intf_desc != NULL) {
if (next_intf_desc->bInterfaceNumber != bInterfaceNumber) {
break;
}
num_alt_setting++;
next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset);
}
return num_alt_setting;
}
const usb_intf_desc_t *usb_host_parse_interface(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, int *offset)
{
assert(config_desc != NULL);
if (bInterfaceNumber >= config_desc->bNumInterfaces) {
return NULL; //bInterfaceNumber is out of range
}
//Walk to first interface descriptor of bInterfaceNumber
int offset_temp = 0;
const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)config_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp);
while (next_intf_desc != NULL) {
if (next_intf_desc->bInterfaceNumber == bInterfaceNumber) {
break; //We found the first interface descriptor with matching bInterfaceNumber
}
next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp);
}
if (next_intf_desc == NULL) {
return NULL; //Couldn't find a interface with bInterfaceNumber
}
//Keep walking until an interface descriptor matching bInterfaceNumber and bAlternateSetting is found
while (next_intf_desc != NULL) {
if (next_intf_desc->bInterfaceNumber == bInterfaceNumber + 1) {
//We've walked past our target bInterfaceNumber
next_intf_desc = NULL;
break;
}
if (next_intf_desc->bAlternateSetting == bAlternateSetting) {
//We've found our target interface descriptor
break;
}
//Get the next interface descriptor
next_intf_desc = (const usb_intf_desc_t *)usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_intf_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset_temp);
}
if (next_intf_desc != NULL && offset != NULL) {
*offset = offset_temp;
}
return next_intf_desc;
}
const usb_ep_desc_t *usb_host_parse_endpoint_by_index(const usb_intf_desc_t *intf_desc, int index, uint16_t wTotalLength, int *offset)
{
assert(intf_desc != NULL && offset != NULL);
if (index >= intf_desc->bNumEndpoints) {
return NULL; //Index is out of range
}
//Walk to the Nth endpoint descriptor we find
int offset_temp = *offset;
bool ep_found = true;
const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)intf_desc;
for (int i = 0; i <= index; i++) {
next_desc = usb_host_parse_next_descriptor_of_type((const usb_standard_desc_t *)next_desc, wTotalLength, USB_B_DESCRIPTOR_TYPE_ENDPOINT, &offset_temp);
if (next_desc == NULL) {
ep_found = false;
break;
}
}
if (ep_found) {
*offset = offset_temp;
return (const usb_ep_desc_t *)next_desc;
}
return NULL;
}
const usb_ep_desc_t *usb_host_parse_endpoint_by_address(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, uint8_t bEndpointAddress, int *offset)
{
assert(config_desc != NULL);
//Find the interface descriptor
int offset_intf;
const usb_intf_desc_t *intf_desc = usb_host_parse_interface(config_desc, bInterfaceNumber, bAlternateSetting, &offset_intf);
if (intf_desc == NULL) {
return NULL;
}
//Walk endpoint descriptors until one matching bEndpointAddress is found
int offset_ep;
bool ep_found = false;
const usb_ep_desc_t *ep_desc = NULL;
for (int index = 0; index < intf_desc->bNumEndpoints; index++) {
offset_ep = offset_intf;
ep_desc = usb_host_parse_endpoint_by_index(intf_desc, index, config_desc->wTotalLength, &offset_ep);
if (ep_desc == NULL) {
break;
}
if (ep_desc->bEndpointAddress == bEndpointAddress) {
ep_found = true;
break;
}
}
if (ep_found && offset != NULL) {
*offset = offset_ep;
}
return ep_desc;
}
// ------------------------------------------------------ Misc ---------------------------------------------------------

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_heap_caps.h"
#include "usb_private.h"
#include "usb/usb_types_ch9.h"
urb_t *urb_alloc(size_t data_buffer_size, size_t header_size, int num_isoc_packets)
{
urb_t *urb = heap_caps_calloc(1, sizeof(urb_t) + (sizeof(usb_isoc_packet_desc_t) * num_isoc_packets), MALLOC_CAP_DEFAULT);
uint8_t *data_buffer = heap_caps_malloc(data_buffer_size + header_size, MALLOC_CAP_DMA);
if (urb == NULL || data_buffer == NULL) {
goto err;
}
urb->usb_host_header_size = header_size; //Indicate that this URB's data_buffer has a header in front of it.
//Case as dummy transfer to write to initialize const fields
usb_transfer_dummy_t *dummy_transfer = (usb_transfer_dummy_t *)&urb->transfer;
dummy_transfer->data_buffer = (uint8_t *)(data_buffer + header_size);
dummy_transfer->data_buffer_size = data_buffer_size;
dummy_transfer->num_isoc_packets = num_isoc_packets;
return urb;
err:
heap_caps_free(urb);
heap_caps_free(data_buffer);
return NULL;
}
void urb_free(urb_t *urb)
{
if (urb == NULL) {
return;
}
heap_caps_free((uint8_t *)(urb->transfer.data_buffer - urb->usb_host_header_size));
heap_caps_free(urb);
}

909
components/usb/usbh.c Normal file
View File

@ -0,0 +1,909 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "hcd.h"
#include "usbh.h"
#include "usb/usb_host_misc.h"
#include "usb/usb_types_ch9.h"
//Device action flags. Listed in the order they should handled in. Some actions are mutually exclusive
#define DEV_FLAG_ACTION_SEND_GONE_EVENT 0x01 //Send a USB_HOST_CLIENT_EVENT_DEV_GONE event
#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_FREE 0x10 //Free the device object
#define DEV_FLAG_ACTION_PORT_DISABLE 0x20
#define DEV_FLAG_ACTION_SEND_NEW 0x40 //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 EP_NUM_MIN 1
#define EP_NUM_MAX 16
typedef struct device_s device_t;
struct device_s {
//Dynamic members require a critical section
struct {
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 val;
} flags;
int num_ctrl_xfers_inflight;
usb_device_state_t state;
uint32_t ref_count;
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
} dynamic;
//Constant members do no change after device allocation and enumeration thus do not require a critical section
struct {
hcd_pipe_handle_t default_pipe;
hcd_port_handle_t port_hdl;
uint8_t address;
usb_speed_t speed;
const usb_device_desc_t *desc;
uint32_t enum_todo_flags;
} constant;
};
typedef struct {
//Dynamic members require a critical section
struct {
TAILQ_HEAD(tailhead_devs, device_s) devs_idle_tailq; //Tailq of all enum and configured devices
TAILQ_HEAD(tailhead_devs_cb, device_s) devs_pending_tailq; //Tailq of devices that need to have their cb called
uint8_t num_device; //Number of enumerated devices
} dynamic;
//Constant members do no change after installation thus do not require a critical section
struct {
usb_notif_cb_t notif_cb;
void *notif_cb_arg;
usbh_hub_cb_t hub_cb;
void *hub_cb_arg;
usbh_event_cb_t event_cb;
void *event_cb_arg;
usbh_ctrl_xfer_cb_t ctrl_xfer_cb;
void *ctrl_xfer_cb_arg;
} constant;
} usbh_t;
static usbh_t *p_usbh_obj = NULL;
static portMUX_TYPE usbh_lock = portMUX_INITIALIZER_UNLOCKED;
const char *USBH_TAG = "USBH";
#define USBH_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&usbh_lock)
#define USBH_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&usbh_lock)
#define USBH_ENTER_CRITICAL() portENTER_CRITICAL(&usbh_lock)
#define USBH_EXIT_CRITICAL() portEXIT_CRITICAL(&usbh_lock)
#define USBH_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&usbh_lock)
#define USBH_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&usbh_lock)
#define USBH_CHECK(cond, ret_val) ({ \
if (!(cond)) { \
return (ret_val); \
} \
})
#define USBH_CHECK_FROM_CRIT(cond, ret_val) ({ \
if (!(cond)) { \
USBH_EXIT_CRITICAL(); \
return ret_val; \
} \
})
// --------------------------------------------------- Allocation ------------------------------------------------------
static esp_err_t device_alloc(hcd_port_handle_t port_hdl, usb_speed_t speed, device_t **dev_obj_ret)
{
esp_err_t ret;
device_t *dev_obj = heap_caps_calloc(1, sizeof(device_t), MALLOC_CAP_DEFAULT);
usb_device_desc_t *dev_desc = heap_caps_calloc(1, sizeof(usb_device_desc_t), MALLOC_CAP_DEFAULT);
if (dev_obj == NULL || dev_desc == NULL) {
ret = ESP_ERR_NO_MEM;
goto err;
}
//Allocate default pipe. We set the pipe callback to NULL for now
hcd_pipe_config_t pipe_config = {
.callback = NULL,
.callback_arg = NULL,
.context = (void *)dev_obj,
.ep_desc = NULL, //No endpoint descriptor means we're allocating a default pipe
.dev_speed = speed,
.dev_addr = 0,
};
hcd_pipe_handle_t default_pipe_hdl;
ret = hcd_pipe_alloc(port_hdl, &pipe_config, &default_pipe_hdl);
if (ret != ESP_OK) {
goto err;
}
//Initialize device object
dev_obj->dynamic.state = USB_DEVICE_STATE_DEFAULT;
dev_obj->constant.default_pipe = default_pipe_hdl;
dev_obj->constant.port_hdl = port_hdl;
//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;
err:
heap_caps_free(dev_desc);
heap_caps_free(dev_obj);
return ret;
}
static void device_free(device_t *dev_obj)
{
if (dev_obj == NULL) {
return;
}
//Configuration must be freed
assert(dev_obj->dynamic.config_desc == NULL);
ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe));
heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc);
heap_caps_free(dev_obj);
}
// -------------------------------------------------- Event Related ----------------------------------------------------
static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags)
{
if (action_flags == 0) {
return false;
}
bool call_notif_cb;
//Check if device is already on the callback list
if (!dev_obj->dynamic.flags.in_pending_list) {
//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.flags.in_pending_list = 1;
call_notif_cb = true;
} else {
call_notif_cb = false;
}
return call_notif_cb;
}
static bool default_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
{
uint32_t action_flags;
device_t *dev_obj = (device_t *)user_arg;
switch (pipe_event) {
case HCD_PIPE_EVENT_URB_DONE:
//A control transfer completed on the default pipe. We need to dequeue it
action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE;
break;
case HCD_PIPE_EVENT_ERROR_XFER:
case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL:
case HCD_PIPE_EVENT_ERROR_OVERFLOW:
//The default pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again
action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH |
DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE |
DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR;
if (in_isr) {
ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 Error", dev_obj->constant.address);
} else {
ESP_LOGE(USBH_TAG, "Dev %d EP 0 Error", dev_obj->constant.address);
}
break;
case HCD_PIPE_EVENT_ERROR_STALL:
//The default pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again
action_flags = DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE | DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR;
if (in_isr) {
ESP_EARLY_LOGE(USBH_TAG, "Dev %d EP 0 STALL", dev_obj->constant.address);
} else {
ESP_LOGE(USBH_TAG, "Dev %d EP 0 STALL", dev_obj->constant.address);
}
break;
default:
action_flags = 0;
break;
}
USBH_ENTER_CRITICAL_SAFE();
bool call_notif_cb = _dev_set_actions(dev_obj, action_flags);
USBH_EXIT_CRITICAL_SAFE();
bool yield = false;
if (call_notif_cb) {
yield = p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, in_isr, p_usbh_obj->constant.notif_cb_arg);
}
return yield;
}
static bool handle_dev_free(device_t *dev_obj)
{
USBH_ENTER_CRITICAL();
//Remove the device object for it's containing list
if (dev_obj->dynamic.flags.in_pending_list) {
dev_obj->dynamic.flags.in_pending_list = 0;
TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_pending_tailq, dev_obj, dynamic.tailq_entry);
} else {
TAILQ_REMOVE(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry);
}
p_usbh_obj->dynamic.num_device--;
bool all_free = (p_usbh_obj->dynamic.num_device == 0);
USBH_EXIT_CRITICAL();
heap_caps_free(dev_obj->dynamic.config_desc);
dev_obj->dynamic.config_desc = NULL;
device_free(dev_obj);
return all_free;
}
// ------------------------------------------------- USBH Functions ----------------------------------------------------
esp_err_t usbh_install(const usbh_config_t *usbh_config)
{
USBH_CHECK(usbh_config != NULL, ESP_ERR_INVALID_ARG);
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(p_usbh_obj == NULL, ESP_ERR_INVALID_STATE);
USBH_EXIT_CRITICAL();
usbh_t *usbh_obj = heap_caps_calloc(1, sizeof(usbh_t), MALLOC_CAP_DEFAULT);
if (usbh_obj == NULL) {
return ESP_ERR_NO_MEM;
}
esp_err_t ret;
//Install HCD
ret = hcd_install(&usbh_config->hcd_config);
if (ret != ESP_OK) {
goto hcd_install_err;
}
//Initialize usbh object
TAILQ_INIT(&usbh_obj->dynamic.devs_idle_tailq);
TAILQ_INIT(&usbh_obj->dynamic.devs_pending_tailq);
usbh_obj->constant.notif_cb = usbh_config->notif_cb;
usbh_obj->constant.notif_cb_arg = usbh_config->notif_cb_arg;
usbh_obj->constant.event_cb = usbh_config->event_cb;
usbh_obj->constant.event_cb_arg = usbh_config->event_cb_arg;
usbh_obj->constant.ctrl_xfer_cb = usbh_config->ctrl_xfer_cb;
usbh_obj->constant.ctrl_xfer_cb_arg = usbh_config->ctrl_xfer_cb_arg;
//Assign usbh object pointer
USBH_ENTER_CRITICAL();
if (p_usbh_obj != NULL) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto assign_err;
}
p_usbh_obj = usbh_obj;
USBH_EXIT_CRITICAL();
ret = ESP_OK;
return ret;
assign_err:
ESP_ERROR_CHECK(hcd_uninstall());
hcd_install_err:
heap_caps_free(usbh_obj);
return ret;
}
esp_err_t usbh_uninstall(void)
{
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE);
//Check that USBH is in a state to be uninstalled
USBH_CHECK_FROM_CRIT(p_usbh_obj->dynamic.num_device == 0, ESP_ERR_INVALID_STATE);
usbh_t *usbh_obj = p_usbh_obj;
p_usbh_obj = NULL;
USBH_EXIT_CRITICAL();
//Uninstall HCD
ESP_ERROR_CHECK(hcd_uninstall());
heap_caps_free(usbh_obj);
return ESP_OK;
}
esp_err_t usbh_process(void)
{
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE);
//Keep clearing devices with events
while (!TAILQ_EMPTY(&p_usbh_obj->dynamic.devs_pending_tailq)){
//Move the device back into the idle device list,
device_t *dev_obj = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq);
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;
dev_obj->dynamic.flags.in_pending_list = 0;
USBH_EXIT_CRITICAL();
ESP_LOGD(USBH_TAG, "Processing actions 0x%x", action_flags);
//Sanity check. If the device is being freed, there must not be any other action flags set
assert(!(action_flags & DEV_FLAG_ACTION_FREE) || action_flags == DEV_FLAG_ACTION_FREE);
if (action_flags & DEV_FLAG_ACTION_SEND_GONE_EVENT) {
//Flush the default pipe. Then do an event gone
ESP_LOGE(USBH_TAG, "Device %d gone", dev_obj->constant.address);
p_usbh_obj->constant.event_cb((usb_device_handle_t)dev_obj, USBH_EVENT_DEV_GONE, p_usbh_obj->constant.event_cb_arg);
}
if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH) {
ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_HALT));
ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_FLUSH));
}
if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE) {
//Empty URBs from default pipe and trigger a control transfer callback
ESP_LOGD(USBH_TAG, "Default pipe device %d", dev_obj->constant.address);
int num_urbs = 0;
urb_t *urb = hcd_urb_dequeue(dev_obj->constant.default_pipe);
while (urb != NULL) {
num_urbs++;
p_usbh_obj->constant.ctrl_xfer_cb((usb_device_handle_t)dev_obj, urb, p_usbh_obj->constant.ctrl_xfer_cb_arg);
urb = hcd_urb_dequeue(dev_obj->constant.default_pipe);
}
USBH_ENTER_CRITICAL();
dev_obj->dynamic.num_ctrl_xfers_inflight -= num_urbs;
USBH_EXIT_CRITICAL();
}
if (action_flags & DEV_FLAG_ACTION_DEFAULT_PIPE_CLEAR) {
//We allow the pipe command to fail just in case the pipe becomes invalid mid command
hcd_pipe_command(dev_obj->constant.default_pipe, HCD_PIPE_CMD_CLEAR);
}
/*
Note: We make these action flags mutually exclusive in case they happen in rapid succession. They are handled
in the order of precedence
For example
- 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) {
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);
}
} 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);
} 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);
}
USBH_ENTER_CRITICAL();
}
USBH_EXIT_CRITICAL();
return ESP_OK;
}
// ------------------------------------------------ Device Functions ---------------------------------------------------
// --------------------- Device Pool -----------------------
esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl)
{
USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG);
esp_err_t ret;
USBH_ENTER_CRITICAL();
//Go through the device lists to find the device with the specified address
device_t *found_dev_obj = NULL;
device_t *dev_obj;
TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) {
if (dev_obj->constant.address == dev_addr) {
found_dev_obj = dev_obj;
goto exit;
}
}
TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) {
if (dev_obj->constant.address == dev_addr) {
found_dev_obj = dev_obj;
goto exit;
}
}
exit:
if (found_dev_obj != NULL) {
//The device is not in a state to be referenced
if (dev_obj->dynamic.flags.is_gone || dev_obj->dynamic.flags.waiting_port_disable || dev_obj->dynamic.flags.waiting_free) {
ret = ESP_ERR_INVALID_STATE;
} else {
dev_obj->dynamic.ref_count++;
*dev_hdl = (usb_device_handle_t)found_dev_obj;
ret = ESP_OK;
}
} else {
ret = ESP_ERR_NOT_FOUND;
}
USBH_EXIT_CRITICAL();
return ret;
}
esp_err_t usbh_dev_close(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();
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);
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);
} 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;
call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_PORT_DISABLE);
}
//Else, there's nothing to do. Leave the device allocated
}
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);
}
return ESP_OK;
}
esp_err_t usbh_dev_mark_all_free(void)
{
USBH_ENTER_CRITICAL();
/*
Go through the device list and mark each device as waiting to be closed. If the device is not opened at all, we can
disable it immediately.
Note: We manually traverse the list because we need to add/remove items while traversing
*/
bool call_notif_cb = false;
for (int i = 0; i < 2; i++) {
device_t *dev_obj_cur;
device_t *dev_obj_next;
//Go through pending list first as it's more efficient
if (i == 0) {
dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_pending_tailq);
} else {
dev_obj_cur = TAILQ_FIRST(&p_usbh_obj->dynamic.devs_idle_tailq);
}
while (dev_obj_cur != NULL) {
assert(!dev_obj_cur->dynamic.flags.waiting_close); //Sanity check
//Keep a copy of the next item first in case we remove the current item
dev_obj_next = TAILQ_NEXT(dev_obj_cur, dynamic.tailq_entry);
if (dev_obj_cur->dynamic.ref_count == 0 && !dev_obj_cur->dynamic.flags.is_gone) {
//Device is not opened as is not gone, so we can disable it now
dev_obj_cur->dynamic.flags.waiting_port_disable = 1;
call_notif_cb |= _dev_set_actions(dev_obj_cur, DEV_FLAG_ACTION_PORT_DISABLE);
} else {
//Device is still opened. Just mark it as waiting to be closed
dev_obj_cur->dynamic.flags.waiting_close = 1;
}
dev_obj_cur = dev_obj_next;
}
}
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);
}
return ESP_OK;
}
// ------------------- Single Device ----------------------
esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr)
{
USBH_CHECK(dev_hdl != NULL && dev_addr != NULL, ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(dev_obj->constant.address > 0, ESP_ERR_INVALID_STATE);
*dev_addr = dev_obj->constant.address;
USBH_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info)
{
USBH_CHECK(dev_hdl != NULL && dev_info != 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 || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED, ESP_ERR_INVALID_STATE);
dev_info->speed = dev_obj->constant.speed;
dev_info->dev_addr = dev_obj->constant.address;
dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0;
if (dev_obj->dynamic.config_desc == NULL) {
dev_info->bConfigurationValue = 0;
} else {
dev_info->bConfigurationValue = dev_obj->dynamic.config_desc->bConfigurationValue;
}
USBH_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t **dev_desc_ret)
{
USBH_CHECK(dev_hdl != NULL && dev_desc_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);
USBH_EXIT_CRITICAL();
*dev_desc_ret = dev_obj->constant.desc;
return ESP_OK;
}
esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret)
{
USBH_CHECK(dev_hdl != NULL && config_desc_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);
*config_desc_ret = dev_obj->dynamic.config_desc;
USBH_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb)
{
USBH_CHECK(dev_hdl != NULL && urb != 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);
//Increment the control transfer count first
dev_obj->dynamic.num_ctrl_xfers_inflight++;
USBH_EXIT_CRITICAL();
esp_err_t ret;
if (hcd_pipe_get_state(dev_obj->constant.default_pipe) != HCD_PIPE_STATE_ACTIVE) {
ret = ESP_ERR_INVALID_STATE;
goto hcd_err;
}
ret = hcd_urb_enqueue(dev_obj->constant.default_pipe, urb);
if (ret != ESP_OK) {
goto hcd_err;
}
ret = ESP_OK;
return ret;
hcd_err:
USBH_ENTER_CRITICAL();
dev_obj->dynamic.num_ctrl_xfers_inflight--;
USBH_EXIT_CRITICAL();
return ret;
}
// ----------------------------------------------- Interface Functions -------------------------------------------------
esp_err_t usbh_ep_alloc(usb_device_handle_t dev_hdl, usbh_ep_config_t *ep_config, hcd_pipe_handle_t *pipe_hdl_ret)
{
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,
.callback_arg = ep_config->pipe_cb_arg,
.context = ep_config->context,
.ep_desc = ep_config->ep_desc,
.dev_speed = dev_obj->constant.speed,
.dev_addr = dev_obj->constant.address,
};
hcd_pipe_handle_t pipe_hdl;
ret = hcd_pipe_alloc(dev_obj->constant.port_hdl, &pipe_config, &pipe_hdl);
if (ret != ESP_OK) {
goto pipe_alloc_err;
}
USBH_ENTER_CRITICAL();
//Check that endpoint has not be allocated yet
bool is_in = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
uint8_t addr = ep_config->ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK;
//Assign the pipe handle
bool assigned = false;
if (is_in && dev_obj->dynamic.ep_in[addr - 1] == NULL) { //Is an IN EP
dev_obj->dynamic.ep_in[addr - 1] = pipe_hdl;
assigned = true;
} else {
dev_obj->dynamic.ep_out[addr - 1] = pipe_hdl;
assigned = true;
}
dev_obj->dynamic.ref_count--; //Restore ref_count
USBH_EXIT_CRITICAL();
if (!assigned) {
ret = ESP_ERR_INVALID_STATE;
goto assign_err;
}
//Write back output
*pipe_hdl_ret = pipe_hdl;
ret = ESP_OK;
return ret;
assign_err:
ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl));
pipe_alloc_err:
return ret;
}
esp_err_t usbh_ep_free(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress)
{
USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
USBH_ENTER_CRITICAL();
//Un-assign the pipe handle from the endpoint
bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK;
hcd_pipe_handle_t pipe_hdl;
if (is_in) {
USBH_CHECK_FROM_CRIT(dev_obj->dynamic.ep_in[addr - 1] != NULL, ESP_ERR_INVALID_STATE);
pipe_hdl = dev_obj->dynamic.ep_in[addr - 1];
dev_obj->dynamic.ep_in[addr - 1] = NULL;
} else {
USBH_CHECK_FROM_CRIT(dev_obj->dynamic.ep_out[addr - 1] != NULL, ESP_ERR_INVALID_STATE);
pipe_hdl = dev_obj->dynamic.ep_out[addr - 1];
dev_obj->dynamic.ep_out[addr - 1] = NULL;
}
USBH_EXIT_CRITICAL();
ESP_ERROR_CHECK(hcd_pipe_free(pipe_hdl));
return ESP_OK;
}
esp_err_t usbh_ep_get_context(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, void **context_ret)
{
bool is_in = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK;
USBH_CHECK(dev_hdl != NULL &&
addr >= EP_NUM_MIN && //Control endpoints are owned by the USBH
addr <= EP_NUM_MAX &&
context_ret != NULL,
ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
USBH_ENTER_CRITICAL();
//Get the endpoint's corresponding pipe
hcd_pipe_handle_t pipe_hdl;
if (is_in) {
pipe_hdl = dev_obj->dynamic.ep_in[addr - 1];
} else {
pipe_hdl = dev_obj->dynamic.ep_out[addr - 1];
}
esp_err_t ret;
if (pipe_hdl == NULL) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_NOT_FOUND;
goto exit;
}
//Return the context of the pipe
void *context = hcd_pipe_get_context(pipe_hdl);
*context_ret = context;
USBH_EXIT_CRITICAL();
ret = ESP_OK;
exit:
return ret;
}
// -------------------------------------------------- Hub Functions ----------------------------------------------------
// ------------------- Device Related ----------------------
esp_err_t usbh_hub_is_installed(usbh_hub_cb_t hub_callback, void *callback_arg)
{
USBH_CHECK(hub_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_EXIT_CRITICAL();
return ESP_OK;
}
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)
{
//Note: Parent device handle can be NULL if it's connected to the root hub
USBH_CHECK(new_dev_hdl != NULL, ESP_ERR_INVALID_ARG);
esp_err_t ret;
device_t *dev_obj;
ret = device_alloc(port_hdl, dev_speed, &dev_obj);
if (ret != ESP_OK) {
return ret;
}
//Write-back device handle
*new_dev_hdl = (usb_device_handle_t)dev_obj;
*default_pipe_hdl = dev_obj->constant.default_pipe;
ret = ESP_OK;
return ret;
}
esp_err_t usbh_hub_mark_dev_gone(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();
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;
call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_FREE);
} else {
call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_SEND_GONE_EVENT |
DEV_FLAG_ACTION_DEFAULT_PIPE_FLUSH |
DEV_FLAG_ACTION_DEFAULT_PIPE_DEQUEUE);
}
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);
}
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)
{
USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG);
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;
}
esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc)
{
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;
}
esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full)
{
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;
}
//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;
}
USBH_ENTER_CRITICAL();
assert(dev_obj->dynamic.config_desc == NULL);
dev_obj->dynamic.config_desc = config_desc;
USBH_EXIT_CRITICAL();
//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;
}
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
USBH_ENTER_CRITICAL();
dev_obj->dynamic.state = USB_DEVICE_STATE_CONFIGURED;
//Add the device to list of devices, then trigger a device event
TAILQ_INSERT_TAIL(&p_usbh_obj->dynamic.devs_idle_tailq, dev_obj, dynamic.tailq_entry); //Add it to the idle device list first
p_usbh_obj->dynamic.num_device++;
bool call_notif_cb = _dev_set_actions(dev_obj, DEV_FLAG_ACTION_SEND_NEW);
USBH_EXIT_CRITICAL();
//Update the default pipe callback
ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, default_pipe_callback, (void *)dev_obj));
//Call the notification callback
if (call_notif_cb) {
p_usbh_obj->constant.notif_cb(USB_NOTIF_SOURCE_USBH, false, p_usbh_obj->constant.notif_cb_arg);
}
return ESP_OK;
}
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;
USBH_ENTER_CRITICAL();
usb_config_desc_t *config_desc = dev_obj->dynamic.config_desc;
dev_obj->dynamic.config_desc = NULL;
USBH_EXIT_CRITICAL();
if (config_desc) {
heap_caps_free(config_desc);
}
device_free(dev_obj);
return ESP_OK;
}