esp-idf/components/usb/usbh.c

1303 wiersze
50 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2022 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 "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "hcd.h"
#include "usbh.h"
#include "usb/usb_helpers.h"
#include "usb/usb_types_ch9.h"
#define EP_NUM_MIN 1 // The smallest possible non-default endpoint number
#define EP_NUM_MAX 16 // The largest possible non-default endpoint number
#define NUM_NON_DEFAULT_EP ((EP_NUM_MAX - 1) * 2) // The total number of non-default endpoints a device can have.
// Device action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within usbh_process(). Some actions are mutually exclusive
typedef enum {
DEV_ACTION_EPn_HALT_FLUSH = (1 << 0), // Halt all non-default endpoints then flush them (called after a device gone is gone)
DEV_ACTION_EP0_FLUSH = (1 << 1), // Retire all URBS submitted to EP0
DEV_ACTION_EP0_DEQUEUE = (1 << 2), // Dequeue all URBs from EP0
DEV_ACTION_EP0_CLEAR = (1 << 3), // Move EP0 to the the active state
DEV_ACTION_PROP_GONE_EVT = (1 << 4), // Propagate a USBH_EVENT_DEV_GONE event
DEV_ACTION_FREE_AND_RECOVER = (1 << 5), // Free the device object, but send a USBH_HUB_REQ_PORT_RECOVER request afterwards.
DEV_ACTION_FREE = (1 << 6), // Free the device object
DEV_ACTION_PORT_DISABLE = (1 << 7), // Request the hub driver to disable the port of the device
DEV_ACTION_PROP_NEW = (1 << 8), // Propagate a USBH_EVENT_DEV_NEW event
} dev_action_t;
typedef struct device_s device_t;
typedef struct {
struct {
usbh_ep_cb_t ep_cb;
void *ep_cb_arg;
hcd_pipe_handle_t pipe_hdl;
device_t *dev; // Pointer to the device object that this endpoint is contained in
const usb_ep_desc_t *ep_desc; // This just stores a pointer endpoint descriptor inside the device's "config_desc"
} constant;
} endpoint_t;
struct device_s {
// Dynamic members require a critical section
struct {
TAILQ_ENTRY(device_s) tailq_entry;
union {
struct {
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 reserved27: 27;
};
uint32_t val;
} flags;
uint32_t action_flags;
int num_ctrl_xfers_inflight;
usb_device_state_t state;
uint32_t ref_count;
} dynamic;
// Mux protected members must be protected by the USBH mux_lock when accessed
struct {
/*
- Endpoint object pointers for each possible non-default endpoint
- All OUT EPs are listed before IN EPs (i.e., EP_NUM_MIN OUT ... EP_NUM_MAX OUT ... EP_NUM_MIN IN ... EP_NUM_MAX)
*/
endpoint_t *endpoints[NUM_NON_DEFAULT_EP];
} mux_protected;
// Constant members do not 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;
const usb_config_desc_t *config_desc;
const usb_str_desc_t *str_desc_manu;
const usb_str_desc_t *str_desc_product;
const usb_str_desc_t *str_desc_ser_num;
} constant;
};
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
} dynamic;
// Mux protected members must be protected by the USBH mux_lock when accessed
struct {
uint8_t num_device; // Number of enumerated devices
} mux_protected;
// Constant members do no change after installation thus do not require a critical section
struct {
usb_proc_req_cb_t proc_req_cb;
void *proc_req_cb_arg;
usbh_hub_req_cb_t hub_req_cb;
void *hub_req_cb_arg;
usbh_event_cb_t event_cb;
void *event_cb_arg;
usbh_ctrl_xfer_cb_t ctrl_xfer_cb;
void *ctrl_xfer_cb_arg;
SemaphoreHandle_t mux_lock;
} 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; \
} \
})
// ------------------------------------------------- Forward Declare ---------------------------------------------------
static bool ep0_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr);
static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr);
static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags);
// ----------------------------------------------------- Helpers -------------------------------------------------------
static inline bool check_ep_addr(uint8_t bEndpointAddress)
{
/*
Check that the bEndpointAddress is valid
- Must be <= EP_NUM_MAX (e.g., 16)
- Must be >= EP_NUM_MIN (e.g., 1).
- EP0 is the owned/managed by USBH, thus must never by directly addressed by users (see USB 2.0 section 10.5.1.2)
*/
uint8_t addr = bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK;
return (addr >= EP_NUM_MIN) && (addr <= EP_NUM_MAX);
}
static endpoint_t *get_ep_from_addr(device_t *dev_obj, uint8_t bEndpointAddress)
{
/*
CALLER IS RESPONSIBLE FOR TAKING THE mux_lock
*/
// Calculate index to the device's endpoint object list
int index;
// EP_NUM_MIN should map to an index of 0
index = (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) - EP_NUM_MIN;
assert(index >= 0); // Endpoint address is not supported
if (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
// OUT EPs are listed before IN EPs, so add an offset
index += (EP_NUM_MAX - EP_NUM_MIN);
}
return dev_obj->mux_protected.endpoints[index];
}
static inline void set_ep_from_addr(device_t *dev_obj, uint8_t bEndpointAddress, endpoint_t *ep_obj)
{
/*
CALLER IS RESPONSIBLE FOR TAKING THE mux_lock
*/
// Calculate index to the device's endpoint object list
int index;
// EP_NUM_MIN should map to an index of 0
index = (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) - EP_NUM_MIN;
assert(index >= 0); // Endpoint address is not supported
if (bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
// OUT EPs are listed before IN EPs, so add an offset
index += (EP_NUM_MAX - EP_NUM_MIN);
}
dev_obj->mux_protected.endpoints[index] = ep_obj;
}
static bool urb_check_args(urb_t *urb)
{
if (urb->transfer.callback == NULL) {
ESP_LOGE(USBH_TAG, "usb_transfer_t callback is NULL");
return false;
}
if (urb->transfer.num_bytes > urb->transfer.data_buffer_size) {
ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes > data_buffer_size");
return false;
}
return true;
}
static bool transfer_check_usb_compliance(usb_transfer_t *transfer, usb_transfer_type_t type, int mps, bool is_in)
{
if (type == USB_TRANSFER_TYPE_CTRL) {
// Check that num_bytes and wLength are set correctly
usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)transfer->data_buffer;
if (transfer->num_bytes != sizeof(usb_setup_packet_t) + setup_pkt->wLength) {
ESP_LOGE(USBH_TAG, "usb_transfer_t num_bytes and usb_setup_packet_t wLength mismatch");
return false;
}
} else if (type == USB_TRANSFER_TYPE_ISOCHRONOUS) {
// Check that there is at least one isochronous packet descriptor
if (transfer->num_isoc_packets <= 0) {
ESP_LOGE(USBH_TAG, "usb_transfer_t num_isoc_packets is 0");
return false;
}
// Check that sum of all packet lengths add up to transfer length
// If IN, check that each packet length is integer multiple of MPS
int total_num_bytes = 0;
bool mod_mps_all_zero = true;
for (int i = 0; i < transfer->num_isoc_packets; i++) {
total_num_bytes += transfer->isoc_packet_desc[i].num_bytes;
if (transfer->isoc_packet_desc[i].num_bytes % mps != 0) {
mod_mps_all_zero = false;
}
}
if (transfer->num_bytes != total_num_bytes) {
ESP_LOGE(USBH_TAG, "ISOC transfer num_bytes != num_bytes of all packets");
return false;
}
if (is_in && !mod_mps_all_zero) {
ESP_LOGE(USBH_TAG, "ISOC IN num_bytes not integer multiple of MPS");
return false;
}
} else {
// Check that IN transfers are integer multiple of MPS
if (is_in && (transfer->num_bytes % mps != 0)) {
ESP_LOGE(USBH_TAG, "IN transfer num_bytes not integer multiple of MPS");
return false;
}
}
return true;
}
// --------------------------------------------------- Allocation ------------------------------------------------------
static esp_err_t endpoint_alloc(device_t *dev_obj, const usb_ep_desc_t *ep_desc, usbh_ep_config_t *ep_config, endpoint_t **ep_obj_ret)
{
esp_err_t ret;
endpoint_t *ep_obj;
hcd_pipe_handle_t pipe_hdl;
ep_obj = heap_caps_calloc(1, sizeof(endpoint_t), MALLOC_CAP_DEFAULT);
if (ep_obj == NULL) {
return ESP_ERR_NO_MEM;
}
// Allocate the EP's underlying pipe
hcd_pipe_config_t pipe_config = {
.callback = epN_pipe_callback,
.callback_arg = (void *)ep_obj,
.context = ep_config->context,
.ep_desc = ep_desc,
.dev_speed = dev_obj->constant.speed,
.dev_addr = dev_obj->constant.address,
};
ret = hcd_pipe_alloc(dev_obj->constant.port_hdl, &pipe_config, &pipe_hdl);
if (ret != ESP_OK) {
goto pipe_err;
}
// Initialize the endpoint object
ep_obj->constant.pipe_hdl = pipe_hdl;
ep_obj->constant.ep_cb = ep_config->ep_cb;
ep_obj->constant.ep_cb_arg = ep_config->ep_cb_arg;
ep_obj->constant.dev = dev_obj;
ep_obj->constant.ep_desc = ep_desc;
// Return the endpoint object
*ep_obj_ret = ep_obj;
ret = ESP_OK;
return ret;
pipe_err:
heap_caps_free(ep_obj);
return ret;
}
static void endpoint_free(endpoint_t *ep_obj)
{
if (ep_obj == NULL) {
return;
}
// Deallocate the EP's underlying pipe
ESP_ERROR_CHECK(hcd_pipe_free(ep_obj->constant.pipe_hdl));
// Free the heap object
heap_caps_free(ep_obj);
}
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 a pipe for EP0. 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 pipe for EP0
.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_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 might not have been allocated (in case of early enumeration failure)
if (dev_obj->constant.config_desc) {
heap_caps_free((usb_config_desc_t *)dev_obj->constant.config_desc);
}
// String descriptors might not have been allocated (in case of early enumeration failure)
if (dev_obj->constant.str_desc_manu) {
heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_manu);
}
if (dev_obj->constant.str_desc_product) {
heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_product);
}
if (dev_obj->constant.str_desc_ser_num) {
heap_caps_free((usb_str_desc_t *)dev_obj->constant.str_desc_ser_num);
}
heap_caps_free((usb_device_desc_t *)dev_obj->constant.desc);
ESP_ERROR_CHECK(hcd_pipe_free(dev_obj->constant.default_pipe));
heap_caps_free(dev_obj);
}
// ---------------------------------------------------- Callbacks ------------------------------------------------------
static bool ep0_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 EP0's pipe . We need to dequeue it
action_flags = DEV_ACTION_EP0_DEQUEUE;
break;
case HCD_PIPE_EVENT_ERROR_XFER:
case HCD_PIPE_EVENT_ERROR_URB_NOT_AVAIL:
case HCD_PIPE_EVENT_ERROR_OVERFLOW:
// EP0's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again
action_flags = DEV_ACTION_EP0_FLUSH |
DEV_ACTION_EP0_DEQUEUE |
DEV_ACTION_EP0_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:
// EP0's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again
action_flags = DEV_ACTION_EP0_DEQUEUE | DEV_ACTION_EP0_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_proc_req_cb = _dev_set_actions(dev_obj, action_flags);
USBH_EXIT_CRITICAL_SAFE();
bool yield = false;
if (call_proc_req_cb) {
yield = p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, in_isr, p_usbh_obj->constant.proc_req_cb_arg);
}
return yield;
}
static bool epN_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
{
endpoint_t *ep_obj = (endpoint_t *)user_arg;
return ep_obj->constant.ep_cb((usbh_ep_handle_t)ep_obj,
(usbh_ep_event_t)pipe_event,
ep_obj->constant.ep_cb_arg,
in_isr);
}
// -------------------------------------------------- Event Related ----------------------------------------------------
static bool _dev_set_actions(device_t *dev_obj, uint32_t action_flags)
{
if (action_flags == 0) {
return false;
}
bool call_proc_req_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.action_flags |= action_flags;
dev_obj->dynamic.flags.in_pending_list = 1;
call_proc_req_cb = true;
} else {
call_proc_req_cb = false;
}
return call_proc_req_cb;
}
static inline void handle_epn_halt_flush(device_t *dev_obj)
{
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
// Halt then flush all non-default EPs
for (int i = 0; i < NUM_NON_DEFAULT_EP; i++) {
if (dev_obj->mux_protected.endpoints[i] != NULL) {
ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.endpoints[i]->constant.pipe_hdl, HCD_PIPE_CMD_HALT));
ESP_ERROR_CHECK(hcd_pipe_command(dev_obj->mux_protected.endpoints[i]->constant.pipe_hdl, HCD_PIPE_CMD_FLUSH));
}
}
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
}
static inline void handle_ep0_flush(device_t *dev_obj)
{
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));
}
static inline void handle_ep0_dequeue(device_t *dev_obj)
{
// Empty URBs from EP0's pipe and call the 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();
}
static inline void handle_ep0_clear(device_t *dev_obj)
{
// 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);
}
static inline void handle_prop_gone_evt(device_t *dev_obj)
{
// Flush EP0's pipe. Then propagate a USBH_EVENT_DEV_GONE event
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);
}
static void handle_free_and_recover(device_t *dev_obj, bool recover_port)
{
// Cache a copy of the port handle as we are about to free the device object
bool all_free;
hcd_port_handle_t port_hdl = dev_obj->constant.port_hdl;
ESP_LOGD(USBH_TAG, "Freeing device %d", dev_obj->constant.address);
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
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);
}
USBH_EXIT_CRITICAL();
p_usbh_obj->mux_protected.num_device--;
all_free = (p_usbh_obj->mux_protected.num_device == 0);
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
device_free(dev_obj);
// If all devices have been freed, propagate a USBH_EVENT_DEV_ALL_FREE event
if (all_free) {
ESP_LOGD(USBH_TAG, "Device all free");
p_usbh_obj->constant.event_cb((usb_device_handle_t)NULL, USBH_EVENT_DEV_ALL_FREE, p_usbh_obj->constant.event_cb_arg);
}
// Check if we need to recover the device's port
if (recover_port) {
p_usbh_obj->constant.hub_req_cb(port_hdl, USBH_HUB_REQ_PORT_RECOVER, p_usbh_obj->constant.hub_req_cb_arg);
}
}
static inline void handle_port_disable(device_t *dev_obj)
{
// 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_req_cb(dev_obj->constant.port_hdl, USBH_HUB_REQ_PORT_DISABLE, p_usbh_obj->constant.hub_req_cb_arg);
}
static inline void handle_prop_new_evt(device_t *dev_obj)
{
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 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();
esp_err_t ret;
usbh_t *usbh_obj = heap_caps_calloc(1, sizeof(usbh_t), MALLOC_CAP_DEFAULT);
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex();
if (usbh_obj == NULL || mux_lock == NULL) {
ret = ESP_ERR_NO_MEM;
goto err;
}
// Initialize USBH object
TAILQ_INIT(&usbh_obj->dynamic.devs_idle_tailq);
TAILQ_INIT(&usbh_obj->dynamic.devs_pending_tailq);
usbh_obj->constant.proc_req_cb = usbh_config->proc_req_cb;
usbh_obj->constant.proc_req_cb_arg = usbh_config->proc_req_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;
usbh_obj->constant.mux_lock = mux_lock;
// Assign USBH object pointer
USBH_ENTER_CRITICAL();
if (p_usbh_obj != NULL) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto err;
}
p_usbh_obj = usbh_obj;
USBH_EXIT_CRITICAL();
ret = ESP_OK;
return ret;
err:
if (mux_lock != NULL) {
vSemaphoreDelete(mux_lock);
}
heap_caps_free(usbh_obj);
return ret;
}
esp_err_t usbh_uninstall(void)
{
// Check that USBH is in a state to be uninstalled
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE);
usbh_t *usbh_obj = p_usbh_obj;
USBH_EXIT_CRITICAL();
esp_err_t ret;
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(usbh_obj->constant.mux_lock, portMAX_DELAY);
if (p_usbh_obj->mux_protected.num_device > 0) {
// There are still devices allocated. Can't uninstall right now.
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// Check again if we can uninstall
USBH_ENTER_CRITICAL();
assert(p_usbh_obj == usbh_obj);
p_usbh_obj = NULL;
USBH_EXIT_CRITICAL();
xSemaphoreGive(usbh_obj->constant.mux_lock);
// Free resources
vSemaphoreDelete(usbh_obj->constant.mux_lock);
heap_caps_free(usbh_obj);
ret = ESP_OK;
return ret;
exit:
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
return ret;
}
esp_err_t usbh_process(void)
{
USBH_ENTER_CRITICAL();
USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE);
// Keep processing until all device's with pending events have been handled
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.action_flags;
dev_obj->dynamic.action_flags = 0;
dev_obj->dynamic.flags.in_pending_list = 0;
/* ---------------------------------------------------------------------
Exit critical section to handle device action flags in their listed order
--------------------------------------------------------------------- */
USBH_EXIT_CRITICAL();
ESP_LOGD(USBH_TAG, "Processing actions 0x%"PRIx32"", action_flags);
// Sanity check. If the device is being freed, there must not be any other action flags set
assert(!(action_flags & DEV_ACTION_FREE) || action_flags == DEV_ACTION_FREE);
if (action_flags & DEV_ACTION_EPn_HALT_FLUSH) {
handle_epn_halt_flush(dev_obj);
}
if (action_flags & DEV_ACTION_EP0_FLUSH) {
handle_ep0_flush(dev_obj);
}
if (action_flags & DEV_ACTION_EP0_DEQUEUE) {
handle_ep0_dequeue(dev_obj);
}
if (action_flags & DEV_ACTION_EP0_CLEAR) {
handle_ep0_clear(dev_obj);
}
if (action_flags & DEV_ACTION_PROP_GONE_EVT) {
handle_prop_gone_evt(dev_obj);
}
/*
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_ACTION_FREE_AND_RECOVER) {
handle_free_and_recover(dev_obj, true);
} else if (action_flags & DEV_ACTION_FREE) {
handle_free_and_recover(dev_obj, false);
} else if (action_flags & DEV_ACTION_PORT_DISABLE) {
handle_port_disable(dev_obj);
} else if (action_flags & DEV_ACTION_PROP_NEW) {
handle_prop_new_evt(dev_obj);
}
USBH_ENTER_CRITICAL();
/* ---------------------------------------------------------------------
Re-enter critical sections. All device action flags should have been handled.
--------------------------------------------------------------------- */
}
USBH_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t usbh_num_devs(int *num_devs_ret)
{
USBH_CHECK(num_devs_ret != NULL, ESP_ERR_INVALID_ARG);
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
*num_devs_ret = p_usbh_obj->mux_protected.num_device;
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
return ESP_OK;
}
// ------------------------------------------------ Device Functions ---------------------------------------------------
// --------------------- Device Pool -----------------------
esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret)
{
USBH_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG);
USBH_ENTER_CRITICAL();
int num_filled = 0;
device_t *dev_obj;
// Fill list with devices from idle tailq
TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_idle_tailq, dynamic.tailq_entry) {
if (num_filled < list_len) {
dev_addr_list[num_filled] = dev_obj->constant.address;
num_filled++;
} else {
break;
}
}
// Fill list with devices from pending tailq
TAILQ_FOREACH(dev_obj, &p_usbh_obj->dynamic.devs_pending_tailq, dynamic.tailq_entry) {
if (num_filled < list_len) {
dev_addr_list[num_filled] = dev_obj->constant.address;
num_filled++;
} else {
break;
}
}
USBH_EXIT_CRITICAL();
// Write back number of devices filled
*num_dev_ret = num_filled;
return ESP_OK;
}
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_pending_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();
dev_obj->dynamic.ref_count--;
bool call_proc_req_cb = false;
if (dev_obj->dynamic.ref_count == 0) {
// Sanity check.
assert(dev_obj->dynamic.num_ctrl_xfers_inflight == 0); // There cannot be any control transfer in-flight
assert(!dev_obj->dynamic.flags.waiting_free); // This can only be set when ref count reaches 0
if (dev_obj->dynamic.flags.is_gone) {
// Device is already gone so it's port is already disabled. Trigger the USBH process to free the device
dev_obj->dynamic.flags.waiting_free = 1;
call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it
} else if (dev_obj->dynamic.flags.waiting_close) {
// Device is still connected but is no longer needed. Trigger the USBH process to request device's port be disabled
dev_obj->dynamic.flags.waiting_port_disable = 1;
call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PORT_DISABLE);
}
// Else, there's nothing to do. Leave the device allocated
}
USBH_EXIT_CRITICAL();
if (call_proc_req_cb) {
p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_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_proc_req_cb = false;
bool wait_for_free = 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_proc_req_cb |= _dev_set_actions(dev_obj_cur, DEV_ACTION_PORT_DISABLE);
} else {
// Device is still opened. Just mark it as waiting to be closed
dev_obj_cur->dynamic.flags.waiting_close = 1;
}
wait_for_free = true; // As long as there is still a device, we need to wait for an event indicating it is freed
dev_obj_cur = dev_obj_next;
}
}
USBH_EXIT_CRITICAL();
if (call_proc_req_cb) {
p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_cb_arg);
}
return (wait_for_free) ? ESP_ERR_NOT_FINISHED : 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;
esp_err_t ret;
// Device must be configured, or not attached (if it suddenly disconnected)
USBH_ENTER_CRITICAL();
if (!(dev_obj->dynamic.state == USB_DEVICE_STATE_CONFIGURED || dev_obj->dynamic.state == USB_DEVICE_STATE_NOT_ATTACHED)) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// Critical section for the dynamic members
dev_info->speed = dev_obj->constant.speed;
dev_info->dev_addr = dev_obj->constant.address;
dev_info->bMaxPacketSize0 = dev_obj->constant.desc->bMaxPacketSize0;
USBH_EXIT_CRITICAL();
assert(dev_obj->constant.config_desc);
dev_info->bConfigurationValue = dev_obj->constant.config_desc->bConfigurationValue;
// String descriptors are allowed to be NULL as not all devices support them
dev_info->str_desc_manufacturer = dev_obj->constant.str_desc_manu;
dev_info->str_desc_product = dev_obj->constant.str_desc_product;
dev_info->str_desc_serial_num = dev_obj->constant.str_desc_ser_num;
ret = ESP_OK;
exit:
return ret;
}
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;
esp_err_t ret;
// Device must be in the configured state
USBH_ENTER_CRITICAL();
if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
USBH_EXIT_CRITICAL();
assert(dev_obj->constant.config_desc);
*config_desc_ret = dev_obj->constant.config_desc;
ret = ESP_OK;
exit:
return ret;
}
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_CHECK(urb_check_args(urb), ESP_ERR_INVALID_ARG);
bool xfer_is_in = ((usb_setup_packet_t *)urb->transfer.data_buffer)->bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN;
USBH_CHECK(transfer_check_usb_compliance(&(urb->transfer), USB_TRANSFER_TYPE_CTRL, dev_obj->constant.desc->bMaxPacketSize0, xfer_is_in), ESP_ERR_INVALID_ARG);
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, usbh_ep_handle_t *ep_hdl_ret)
{
USBH_CHECK(dev_hdl != NULL && ep_config != NULL && ep_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
uint8_t bEndpointAddress = ep_config->bEndpointAddress;
USBH_CHECK(check_ep_addr(bEndpointAddress), ESP_ERR_INVALID_ARG);
esp_err_t ret;
device_t *dev_obj = (device_t *)dev_hdl;
endpoint_t *ep_obj;
// Find the endpoint descriptor from the device's current configuration descriptor
const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_address(dev_obj->constant.config_desc, ep_config->bInterfaceNumber, ep_config->bAlternateSetting, ep_config->bEndpointAddress, NULL);
if (ep_desc == NULL) {
return ESP_ERR_NOT_FOUND;
}
// Allocate the endpoint object
ret = endpoint_alloc(dev_obj, ep_desc, ep_config, &ep_obj);
if (ret != ESP_OK) {
goto alloc_err;
}
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
USBH_ENTER_CRITICAL();
// Check the device's state before we assign the a pipe to the allocated endpoint
if (dev_obj->dynamic.state != USB_DEVICE_STATE_CONFIGURED) {
USBH_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto dev_state_err;
}
USBH_EXIT_CRITICAL();
// Check if the endpoint has already been allocated
if (get_ep_from_addr(dev_obj, bEndpointAddress) == NULL) {
set_ep_from_addr(dev_obj, bEndpointAddress, ep_obj);
// Write back the endpoint handle
*ep_hdl_ret = (usbh_ep_handle_t)ep_obj;
ret = ESP_OK;
} else {
// Endpoint is already allocated
ret = ESP_ERR_INVALID_STATE;
}
dev_state_err:
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
// If the endpoint was not assigned, free it
if (ret != ESP_OK) {
endpoint_free(ep_obj);
}
alloc_err:
return ret;
}
esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl)
{
USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG);
esp_err_t ret;
endpoint_t *ep_obj = (endpoint_t *)ep_hdl;
device_t *dev_obj = (device_t *)ep_obj->constant.dev;
uint8_t bEndpointAddress = ep_obj->constant.ep_desc->bEndpointAddress;
// Todo: Check that the EP's underlying pipe is halted before allowing the EP to be freed (IDF-7273)
// Check that the the EP's underlying pipe has no more in-flight URBs
if (hcd_pipe_get_num_urbs(ep_obj->constant.pipe_hdl) != 0) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
// Check if the endpoint was allocated on this device
if (ep_obj == get_ep_from_addr(dev_obj, bEndpointAddress)) {
// Clear the endpoint from the device's endpoint object list
set_ep_from_addr(dev_obj, bEndpointAddress, NULL);
ret = ESP_OK;
} else {
ret = ESP_ERR_NOT_FOUND;
}
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
// Finally, we free the endpoint object
if (ret == ESP_OK) {
endpoint_free(ep_obj);
}
exit:
return ret;
}
esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret)
{
USBH_CHECK(dev_hdl != NULL && ep_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
USBH_CHECK(check_ep_addr(bEndpointAddress), ESP_ERR_INVALID_ARG);
esp_err_t ret;
device_t *dev_obj = (device_t *)dev_hdl;
endpoint_t *ep_obj;
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
ep_obj = get_ep_from_addr(dev_obj, bEndpointAddress);
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
if (ep_obj) {
*ep_hdl_ret = (usbh_ep_handle_t)ep_obj;
ret = ESP_OK;
} else {
ret = ESP_ERR_NOT_FOUND;
}
return ret;
}
esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb)
{
USBH_CHECK(ep_hdl != NULL && urb != NULL, ESP_ERR_INVALID_ARG);
/*
Todo: Here would be a good place to check that the URB is filled correctly according to the USB 2.0 specification.
This is currently done by the USB host library layer, but is more appropriate here.
*/
endpoint_t *ep_obj = (endpoint_t *)ep_hdl;
// Check that the EP's underlying pipe is in the active state before submitting the URB
if (hcd_pipe_get_state(ep_obj->constant.pipe_hdl) != HCD_PIPE_STATE_ACTIVE) {
return ESP_ERR_INVALID_STATE;
}
// Enqueue the URB to the EP's underlying pipe
return hcd_urb_enqueue(ep_obj->constant.pipe_hdl, urb);
}
esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret)
{
USBH_CHECK(ep_hdl != NULL && urb_ret != NULL, ESP_ERR_INVALID_ARG);
endpoint_t *ep_obj = (endpoint_t *)ep_hdl;
// Enqueue the URB to the EP's underlying pipe
*urb_ret = hcd_urb_dequeue(ep_obj->constant.pipe_hdl);
return ESP_OK;
}
esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command)
{
USBH_CHECK(ep_hdl != NULL, ESP_ERR_INVALID_ARG);
endpoint_t *ep_obj = (endpoint_t *)ep_hdl;
// Send the command to the EP's underlying pipe
return hcd_pipe_command(ep_obj->constant.pipe_hdl, (hcd_pipe_cmd_t)command);
}
void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl)
{
assert(ep_hdl);
endpoint_t *ep_obj = (endpoint_t *)ep_hdl;
return hcd_pipe_get_context(ep_obj->constant.pipe_hdl);
}
// -------------------------------------------------- Hub Functions ----------------------------------------------------
// ------------------- Device Related ----------------------
esp_err_t usbh_hub_is_installed(usbh_hub_req_cb_t hub_req_callback, void *callback_arg)
{
USBH_CHECK(hub_req_callback != NULL, ESP_ERR_INVALID_ARG);
USBH_ENTER_CRITICAL();
// Check that USBH is already installed
USBH_CHECK_FROM_CRIT(p_usbh_obj != NULL, ESP_ERR_INVALID_STATE);
// Check that Hub has not be installed yet
USBH_CHECK_FROM_CRIT(p_usbh_obj->constant.hub_req_cb == NULL, ESP_ERR_INVALID_STATE);
p_usbh_obj->constant.hub_req_cb = hub_req_callback;
p_usbh_obj->constant.hub_req_cb_arg = callback_arg;
USBH_EXIT_CRITICAL();
return ESP_OK;
}
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_pass_event(usb_device_handle_t dev_hdl, usbh_hub_event_t hub_event)
{
USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
bool call_proc_req_cb;
switch (hub_event) {
case USBH_HUB_EVENT_PORT_ERROR: {
USBH_ENTER_CRITICAL();
dev_obj->dynamic.flags.is_gone = 1;
// Check if the device can be freed now
if (dev_obj->dynamic.ref_count == 0) {
dev_obj->dynamic.flags.waiting_free = 1;
// Device is already waiting free so none of it's EP's will be in use. Can free immediately.
call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE_AND_RECOVER); // Port error occurred so we need to recover it
} else {
call_proc_req_cb = _dev_set_actions(dev_obj,
DEV_ACTION_EPn_HALT_FLUSH |
DEV_ACTION_EP0_FLUSH |
DEV_ACTION_EP0_DEQUEUE |
DEV_ACTION_PROP_GONE_EVT);
}
USBH_EXIT_CRITICAL();
break;
}
case USBH_HUB_EVENT_PORT_DISABLED: {
USBH_ENTER_CRITICAL();
assert(dev_obj->dynamic.ref_count == 0); // At this stage, the device should have been closed by all users
dev_obj->dynamic.flags.waiting_free = 1;
call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_FREE);
USBH_EXIT_CRITICAL();
break;
}
default:
return ESP_ERR_INVALID_ARG;
}
if (call_proc_req_cb) {
p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_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();
dev_obj->dynamic.state = USB_DEVICE_STATE_ADDRESS;
USBH_EXIT_CRITICAL();
// We can modify the info members outside the critical section
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
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;
// 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) {
return ESP_ERR_NO_MEM;
}
// Copy the configuration descriptor
memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength);
// Assign the config desc to the device object
assert(dev_obj->constant.config_desc == NULL);
dev_obj->constant.config_desc = config_desc;
return ESP_OK;
}
esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select)
{
USBH_CHECK(dev_hdl != NULL && str_desc != NULL && (select >= 0 && select < 3), ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
// Allocate memory to store the manufacturer string descriptor
usb_str_desc_t *str_desc_fill = heap_caps_malloc(str_desc->bLength, MALLOC_CAP_DEFAULT);
if (str_desc_fill == NULL) {
return ESP_ERR_NO_MEM;
}
// Copy the string descriptor
memcpy(str_desc_fill, str_desc, str_desc->bLength);
// Assign filled string descriptor to the device object
switch (select) {
case 0:
assert(dev_obj->constant.str_desc_manu == NULL);
dev_obj->constant.str_desc_manu = str_desc_fill;
break;
case 1:
assert(dev_obj->constant.str_desc_product == NULL);
dev_obj->constant.str_desc_product = str_desc_fill;
break;
default: // 2
assert(dev_obj->constant.str_desc_ser_num == NULL);
dev_obj->constant.str_desc_ser_num = str_desc_fill;
break;
}
return ESP_OK;
}
esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl)
{
USBH_CHECK(dev_hdl != NULL, ESP_ERR_INVALID_ARG);
device_t *dev_obj = (device_t *)dev_hdl;
// We need to take the mux_lock to access mux_protected members
xSemaphoreTake(p_usbh_obj->constant.mux_lock, portMAX_DELAY);
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
bool call_proc_req_cb = _dev_set_actions(dev_obj, DEV_ACTION_PROP_NEW);
USBH_EXIT_CRITICAL();
p_usbh_obj->mux_protected.num_device++;
xSemaphoreGive(p_usbh_obj->constant.mux_lock);
// Update the EP0's underlying pipe's callback
ESP_ERROR_CHECK(hcd_pipe_update_callback(dev_obj->constant.default_pipe, ep0_pipe_callback, (void *)dev_obj));
// Call the processing request callback
if (call_proc_req_cb) {
p_usbh_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_USBH, false, p_usbh_obj->constant.proc_req_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;
device_free(dev_obj);
return ESP_OK;
}