Files
esp-idf/components/usb/usb_host.c
Myk Melez aa669fa25d fix(usb_host): Give semaphore on attempted close of non-opened device
If you call *usb_host_device_close()* for a device that isn't open, the function exits early,
without giving back the semaphore it took, which causes any other call that tries to take
that semaphore to hang indefinitely.

Strangely, there's redundant handling of this condition, with two checks in a row that both handle
the case where `_check_client_opened_device(client_obj, dev_addr)` returns `false`:

```c
    HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_NOT_FOUND);
    if (!_check_client_opened_device(client_obj, dev_addr)) {
        // Client never opened this device
        ret = ESP_ERR_INVALID_STATE;
        HOST_EXIT_CRITICAL();
        goto exit;
    }
…
exit:
    xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
    return ret;
```

The first line is the one that exits early, as HOST_CHECK_FROM_CRIT returns its second parameter
if its first parameter is false, without giving back the semaphore (although it does exit
the critical section).

The subsequent block handles the exact same case, except that it ensures the semaphore is given
back before returning. Currently, this block is never reached.

Perhaps the first check was added, then someone noticed the issue and added the second check,
but they forgot to remove the first one.

In any case, this PR removes the first check, so the second check can properly handle this case
by giving back the semaphore before returning.

This bug appears to have been present in the initial commit of the USB Host library to the ESP-IDF
repo: accbaee57c

Of course, if you never try to close a non-opened device, then you won't encounter it!
Unfortunately, I have some code that tried to do that, which is how I found the issue.
2025-03-24 21:58:34 +01:00

1622 lines
58 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
Warning: The USB Host Library API is still a beta version and may be subject to change
*/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_private/critical_section.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "hub.h"
#include "enum.h"
#include "usbh.h"
#include "hcd.h"
#include "esp_private/usb_phy.h"
#include "usb/usb_host.h"
DEFINE_CRIT_SECTION_LOCK_STATIC(host_lock);
#define HOST_ENTER_CRITICAL_ISR() esp_os_enter_critical_isr(&host_lock)
#define HOST_EXIT_CRITICAL_ISR() esp_os_exit_critical_isr(&host_lock)
#define HOST_ENTER_CRITICAL() esp_os_enter_critical(&host_lock)
#define HOST_EXIT_CRITICAL() esp_os_exit_critical(&host_lock)
#define HOST_ENTER_CRITICAL_SAFE() esp_os_enter_critical_safe(&host_lock)
#define HOST_EXIT_CRITICAL_SAFE() esp_os_exit_critical_safe(&host_lock)
#define HOST_CHECK(cond, ret_val) ({ \
if (!(cond)) { \
return (ret_val); \
} \
})
#define HOST_CHECK_FROM_CRIT(cond, ret_val) ({ \
if (!(cond)) { \
HOST_EXIT_CRITICAL(); \
return ret_val; \
} \
})
#define PROCESS_REQUEST_PENDING_FLAG_USBH (1 << 0)
#define PROCESS_REQUEST_PENDING_FLAG_HUB (1 << 1)
#define PROCESS_REQUEST_PENDING_FLAG_ENUM (1 << 2)
#define SHORT_DESC_REQ_LEN 8
#define CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
typedef struct ep_wrapper_s ep_wrapper_t;
typedef struct interface_s interface_t;
typedef struct client_s client_t;
struct ep_wrapper_s {
// Dynamic members require a critical section
struct {
TAILQ_ENTRY(ep_wrapper_s) tailq_entry;
union {
struct {
uint32_t pending: 1;
uint32_t reserved31: 31;
};
} flags;
uint32_t num_urb_inflight;
usbh_ep_event_t last_event;
} dynamic;
// Constant members do no change after claiming the interface thus do not require a critical section
struct {
usbh_ep_handle_t ep_hdl;
interface_t *intf_obj;
} constant;
};
struct interface_s {
// Dynamic members require a critical section
struct {
TAILQ_ENTRY(interface_s) tailq_entry;
} mux_protected;
// Constant members do no change after claiming the interface thus do not require a critical section
struct {
const usb_intf_desc_t *intf_desc;
usb_device_handle_t dev_hdl;
client_t *client_obj;
ep_wrapper_t *endpoints[0];
} constant;
};
struct client_s {
// Dynamic members require a critical section
struct {
TAILQ_ENTRY(client_s) tailq_entry;
TAILQ_HEAD(tailhead_pending_ep, ep_wrapper_s) pending_ep_tailq;
TAILQ_HEAD(tailhead_idle_ep, ep_wrapper_s) idle_ep_tailq;
TAILQ_HEAD(tailhead_done_ctrl_xfers, urb_s) done_ctrl_xfer_tailq;
union {
struct {
uint32_t handling_events: 1;
uint32_t taking_mux: 1;
uint32_t reserved6: 6;
uint32_t num_intf_claimed: 8;
uint32_t reserved16: 16;
};
uint32_t val;
} flags;
uint32_t num_done_ctrl_xfer;
uint32_t opened_dev_addr_map[4];
} dynamic;
// Mux protected members must be protected by host library the mux_lock when accessed
struct {
TAILQ_HEAD(tailhead_interfaces, interface_s) interface_tailq;
} mux_protected;
// Constant members do no change after registration thus do not require a critical section
struct {
SemaphoreHandle_t event_sem;
usb_host_client_event_cb_t event_callback;
void *callback_arg;
QueueHandle_t event_msg_queue;
} constant;
};
typedef struct {
// Dynamic members require a critical section
struct {
// Access to these should be done in a critical section
uint32_t process_pending_flags;
uint32_t lib_event_flags;
union {
struct {
uint32_t handling_events: 1;
uint32_t reserved7: 7;
uint32_t num_clients: 8;
uint32_t reserved16: 16;
};
uint32_t val;
} flags;
} dynamic;
// Mux protected members must be protected by host library the mux_lock when accessed
struct {
TAILQ_HEAD(tailhead_clients, client_s) client_tailq; // List of all clients registered
} mux_protected;
// Constant members do no change after installation thus do not require a critical section
struct {
SemaphoreHandle_t event_sem;
SemaphoreHandle_t mux_lock;
usb_phy_handle_t phy_handle; // Will be NULL if host library is installed with skip_phy_setup
void *enum_client; // Pointer to Enum driver (acting as a client). Used to reroute completed USBH control transfers
void *hub_client; // Pointer to External Hub driver (acting as a client). Used to reroute completed USBH control transfers. NULL, when External Hub Driver not available.
} constant;
} host_lib_t;
static host_lib_t *p_host_lib_obj = NULL;
const char *USB_HOST_TAG = "USB HOST";
// ----------------------------------------------------- Helpers -------------------------------------------------------
static inline void _record_client_opened_device(client_t *client_obj, uint8_t dev_addr)
{
assert(dev_addr != 0 && dev_addr <= 127);
client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] |= (uint32_t)(1 << (dev_addr % 32));
}
static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev_addr)
{
assert(dev_addr != 0 && dev_addr <= 127);
client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] &= ~(uint32_t)(1 << (dev_addr % 32));
}
static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr)
{
bool ret;
assert(dev_addr <= 127);
if (dev_addr != 0) {
ret = client_obj->dynamic.opened_dev_addr_map[dev_addr / 32] & (uint32_t)(1 << (dev_addr % 32));
} else {
ret = false;
}
return ret;
}
static bool _unblock_client(client_t *client_obj, bool in_isr)
{
bool yield;
HOST_EXIT_CRITICAL_SAFE();
if (in_isr) {
BaseType_t xTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(client_obj->constant.event_sem, &xTaskWoken);
yield = (xTaskWoken == pdTRUE);
} else {
xSemaphoreGive(client_obj->constant.event_sem);
yield = false;
}
HOST_ENTER_CRITICAL_SAFE();
return yield;
}
static bool _unblock_lib(bool in_isr)
{
bool yield;
HOST_EXIT_CRITICAL_SAFE();
if (in_isr) {
BaseType_t xTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(p_host_lib_obj->constant.event_sem, &xTaskWoken);
yield = (xTaskWoken == pdTRUE);
} else {
xSemaphoreGive(p_host_lib_obj->constant.event_sem);
yield = false;
}
HOST_ENTER_CRITICAL_SAFE();
return yield;
}
static inline bool _is_internal_client(void *client)
{
if (p_host_lib_obj->constant.enum_client && (client == p_host_lib_obj->constant.enum_client)) {
return true;
}
#if ENABLE_USB_HUBS
if (p_host_lib_obj->constant.hub_client && (client == p_host_lib_obj->constant.hub_client)) {
return true;
}
#endif // ENABLE_USB_HUBS
return false;
}
static void send_event_msg_to_clients(const usb_host_client_event_msg_t *event_msg, bool send_to_all, uint8_t opened_dev_addr)
{
// Lock client list
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
// Send event message to relevant or all clients
client_t *client_obj;
TAILQ_FOREACH(client_obj, &p_host_lib_obj->mux_protected.client_tailq, dynamic.tailq_entry) {
if (!send_to_all) {
// Check if client opened the device
HOST_ENTER_CRITICAL();
bool send = _check_client_opened_device(client_obj, opened_dev_addr);
HOST_EXIT_CRITICAL();
if (!send) {
continue;
}
}
// Send the event message
if (xQueueSend(client_obj->constant.event_msg_queue, event_msg, 0) == pdTRUE) {
HOST_ENTER_CRITICAL();
_unblock_client(client_obj, false);
HOST_EXIT_CRITICAL();
} else {
ESP_LOGE(USB_HOST_TAG, "Client event message queue full");
}
}
// Unlock client list
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
}
// ---------------------------------------------------- Callbacks ------------------------------------------------------
// ------------------- Library Related ---------------------
static bool proc_req_callback(usb_proc_req_source_t source, bool in_isr, void *arg)
{
HOST_ENTER_CRITICAL_SAFE();
// Store the processing request source
switch (source) {
case USB_PROC_REQ_SOURCE_USBH:
p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_USBH;
break;
case USB_PROC_REQ_SOURCE_HUB:
p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_HUB;
break;
case USB_PROC_REQ_SOURCE_ENUM:
p_host_lib_obj->dynamic.process_pending_flags |= PROCESS_REQUEST_PENDING_FLAG_ENUM;
break;
}
bool yield = _unblock_lib(in_isr);
HOST_EXIT_CRITICAL_SAFE();
return yield;
}
static void usbh_event_callback(usbh_event_data_t *event_data, void *arg)
{
switch (event_data->event) {
case USBH_EVENT_CTRL_XFER: {
assert(event_data->ctrl_xfer_data.urb != NULL);
assert(event_data->ctrl_xfer_data.urb->usb_host_client != NULL);
// Redistribute completed control transfers to the clients that submitted them
if (_is_internal_client(event_data->ctrl_xfer_data.urb->usb_host_client)) {
// Simply call the transfer callback
event_data->ctrl_xfer_data.urb->transfer.callback(&event_data->ctrl_xfer_data.urb->transfer);
} else {
client_t *client_obj = (client_t *)event_data->ctrl_xfer_data.urb->usb_host_client;
HOST_ENTER_CRITICAL();
TAILQ_INSERT_TAIL(&client_obj->dynamic.done_ctrl_xfer_tailq, event_data->ctrl_xfer_data.urb, tailq_entry);
client_obj->dynamic.num_done_ctrl_xfer++;
_unblock_client(client_obj, false);
HOST_EXIT_CRITICAL();
}
break;
}
case USBH_EVENT_NEW_DEV: {
// Internal client
hub_notify_new_dev(event_data->new_dev_data.dev_addr);
// External clients
// Prepare a NEW_DEV client event message, the send it to all clients
usb_host_client_event_msg_t event_msg = {
.event = USB_HOST_CLIENT_EVENT_NEW_DEV,
.new_dev.address = event_data->new_dev_data.dev_addr,
};
send_event_msg_to_clients(&event_msg, true, 0);
break;
}
case USBH_EVENT_DEV_GONE: {
// Internal client
hub_notify_dev_gone(event_data->new_dev_data.dev_addr);
// External clients
// Prepare event msg, send only to clients that have opened the device
usb_host_client_event_msg_t event_msg = {
.event = USB_HOST_CLIENT_EVENT_DEV_GONE,
.dev_gone.dev_hdl = event_data->dev_gone_data.dev_hdl,
};
send_event_msg_to_clients(&event_msg, false, event_data->dev_gone_data.dev_addr);
break;
}
case USBH_EVENT_DEV_FREE: {
// Let the Hub driver know that the device is free and its port can be recycled
// Port could be absent, no need to verify
hub_port_recycle(event_data->dev_free_data.parent_dev_hdl,
event_data->dev_free_data.port_num,
event_data->dev_free_data.dev_uid);
break;
}
case USBH_EVENT_ALL_FREE: {
// Notify the lib handler that all devices are free
HOST_ENTER_CRITICAL();
p_host_lib_obj->dynamic.lib_event_flags |= USB_HOST_LIB_EVENT_FLAGS_ALL_FREE;
_unblock_lib(false);
HOST_EXIT_CRITICAL();
break;
}
default:
abort(); // Should never occur
break;
}
}
static void hub_event_callback(hub_event_data_t *event_data, void *arg)
{
switch (event_data->event) {
case HUB_EVENT_CONNECTED:
// Start enumeration process
enum_start(event_data->connected.uid);
break;
case HUB_EVENT_RESET_COMPLETED:
ESP_ERROR_CHECK(enum_proceed(event_data->reset_completed.uid));
break;
case HUB_EVENT_DISCONNECTED:
// Cancel enumeration process
enum_cancel(event_data->disconnected.uid);
// We allow this to fail in case the device object was already freed
usbh_devs_remove(event_data->disconnected.uid);
break;
default:
abort(); // Should never occur
break;
}
}
static void enum_event_callback(enum_event_data_t *event_data, void *arg)
{
enum_event_t event = event_data->event;
switch (event) {
case ENUM_EVENT_STARTED:
// Enumeration process started
break;
case ENUM_EVENT_RESET_REQUIRED:
// Device may be gone, don't need to verify result
hub_port_reset(event_data->reset_req.parent_dev_hdl, event_data->reset_req.parent_port_num);
break;
case ENUM_EVENT_COMPLETED:
// Notify port that device completed enumeration
hub_port_active(event_data->complete.parent_dev_hdl, event_data->complete.parent_port_num);
// Propagate a new device event
ESP_ERROR_CHECK(usbh_devs_new_dev_event(event_data->complete.dev_hdl));
break;
case ENUM_EVENT_CANCELED:
hub_port_disable(event_data->canceled.parent_dev_hdl, event_data->canceled.parent_port_num);
break;
default:
abort(); // Should never occur
break;
}
}
// ------------------- Client Related ----------------------
static bool endpoint_callback(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr)
{
ep_wrapper_t *ep_wrap = (ep_wrapper_t *)user_arg;
client_t *client_obj = (client_t *)ep_wrap->constant.intf_obj->constant.client_obj;
HOST_ENTER_CRITICAL_SAFE();
// Store the event to be handled later. Note that we allow overwriting of events because more severe will halt the pipe prevent any further events.
ep_wrap->dynamic.last_event = ep_event;
// Add the EP to the client's pending list if it's not in the list already
if (!ep_wrap->dynamic.flags.pending) {
ep_wrap->dynamic.flags.pending = 1;
TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, ep_wrap, dynamic.tailq_entry);
TAILQ_INSERT_TAIL(&client_obj->dynamic.pending_ep_tailq, ep_wrap, dynamic.tailq_entry);
}
bool yield = _unblock_client(client_obj, in_isr);
HOST_EXIT_CRITICAL_SAFE();
return yield;
}
static void get_config_desc_transfer_cb(usb_transfer_t *transfer)
{
SemaphoreHandle_t transfer_done = (SemaphoreHandle_t)transfer->context;
xSemaphoreGive(transfer_done);
}
// ------------------------------------------------ Library Functions --------------------------------------------------
// ----------------------- Public --------------------------
esp_err_t usb_host_install(const usb_host_config_t *config)
{
HOST_CHECK(config != NULL, ESP_ERR_INVALID_ARG);
HOST_ENTER_CRITICAL();
HOST_CHECK_FROM_CRIT(p_host_lib_obj == NULL, ESP_ERR_INVALID_STATE);
HOST_EXIT_CRITICAL();
esp_err_t ret;
host_lib_t *host_lib_obj = heap_caps_calloc(1, sizeof(host_lib_t), MALLOC_CAP_DEFAULT);
SemaphoreHandle_t event_sem = xSemaphoreCreateBinary();
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex();
if (host_lib_obj == NULL || event_sem == NULL || mux_lock == NULL) {
ret = ESP_ERR_NO_MEM;
goto alloc_err;
}
// Initialize host library object
TAILQ_INIT(&host_lib_obj->mux_protected.client_tailq);
host_lib_obj->constant.event_sem = event_sem;
host_lib_obj->constant.mux_lock = mux_lock;
/*
Install each layer of the Host stack (listed below) from the lowest layer to the highest
- USB PHY
- HCD
- USBH
- Enum
- Hub
*/
// Install USB PHY (if necessary). USB PHY driver will also enable the underlying Host Controller
if (!config->skip_phy_setup) {
// Host Library defaults to internal PHY
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
#if CONFIG_IDF_TARGET_ESP32P4 // ESP32-P4 has 2 USB-DWC peripherals, each with its dedicated PHY. We support HS+UTMI only ATM.
.target = USB_PHY_TARGET_UTMI,
#else
.target = USB_PHY_TARGET_INT,
#endif
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, // In Host mode, the speed is determined by the connected device
.ext_io_conf = NULL,
.otg_io_conf = NULL,
};
ret = usb_new_phy(&phy_config, &host_lib_obj->constant.phy_handle);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "PHY install error: %s", esp_err_to_name(ret));
goto phy_err;
}
}
// Install HCD
hcd_config_t hcd_config = {
.intr_flags = config->intr_flags
};
ret = hcd_install(&hcd_config);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "HCD install error: %s", esp_err_to_name(ret));
goto hcd_err;
}
// Install USBH
usbh_config_t usbh_config = {
.proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL,
.event_cb = usbh_event_callback,
.event_cb_arg = NULL,
};
ret = usbh_install(&usbh_config);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "USBH install error: %s", esp_err_to_name(ret));
goto usbh_err;
}
// Install Enumeration driver
enum_config_t enum_config = {
.proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL,
.enum_event_cb = enum_event_callback,
.enum_event_cb_arg = NULL,
#if ENABLE_ENUM_FILTER_CALLBACK
.enum_filter_cb = config->enum_filter_cb,
.enum_filter_cb_arg = NULL,
#endif // ENABLE_ENUM_FILTER_CALLBACK
};
ret = enum_install(&enum_config, &host_lib_obj->constant.enum_client);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Enum. driver install error: %s", esp_err_to_name(ret));
goto enum_err;
}
// Install Hub
hub_config_t hub_config = {
.proc_req_cb = proc_req_callback,
.proc_req_cb_arg = NULL,
.event_cb = hub_event_callback,
.event_cb_arg = NULL,
};
ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Hub driver Install error: %s", esp_err_to_name(ret));
goto hub_err;
}
// Assign host library object
HOST_ENTER_CRITICAL();
if (p_host_lib_obj != NULL) {
HOST_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto assign_err;
}
p_host_lib_obj = host_lib_obj;
HOST_EXIT_CRITICAL();
if (!config->root_port_unpowered) {
// Start the root hub
ESP_ERROR_CHECK(hub_root_start());
}
ret = ESP_OK;
return ret;
assign_err:
ESP_ERROR_CHECK(hub_uninstall());
hub_err:
ESP_ERROR_CHECK(enum_uninstall());
enum_err:
ESP_ERROR_CHECK(usbh_uninstall());
usbh_err:
ESP_ERROR_CHECK(hcd_uninstall());
hcd_err:
if (host_lib_obj->constant.phy_handle) {
ESP_ERROR_CHECK(usb_del_phy(host_lib_obj->constant.phy_handle));
}
phy_err:
alloc_err:
if (mux_lock) {
vSemaphoreDelete(mux_lock);
}
if (event_sem) {
vSemaphoreDelete(event_sem);
}
heap_caps_free(host_lib_obj);
return ret;
}
esp_err_t usb_host_uninstall(void)
{
// All devices must have been freed at this point
HOST_ENTER_CRITICAL();
HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.process_pending_flags == 0 &&
p_host_lib_obj->dynamic.lib_event_flags == 0 &&
p_host_lib_obj->dynamic.flags.val == 0,
ESP_ERR_INVALID_STATE);
HOST_EXIT_CRITICAL();
// Stop the root hub
ESP_ERROR_CHECK(hub_root_stop());
// Unassign the host library object
HOST_ENTER_CRITICAL();
host_lib_t *host_lib_obj = p_host_lib_obj;
p_host_lib_obj = NULL;
HOST_EXIT_CRITICAL();
/*
Uninstall each layer of the Host stack (listed below) from the highest layer to the lowest
- Hub
- Enum
- USBH
- HCD
- USB PHY
*/
ESP_ERROR_CHECK(hub_uninstall());
ESP_ERROR_CHECK(enum_uninstall());
ESP_ERROR_CHECK(usbh_uninstall());
ESP_ERROR_CHECK(hcd_uninstall());
// If the USB PHY was setup, then delete it
if (host_lib_obj->constant.phy_handle) {
ESP_ERROR_CHECK(usb_del_phy(host_lib_obj->constant.phy_handle));
}
// Free memory objects
vSemaphoreDelete(host_lib_obj->constant.mux_lock);
vSemaphoreDelete(host_lib_obj->constant.event_sem);
heap_caps_free(host_lib_obj);
return ESP_OK;
}
esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret)
{
// Check arguments and state
HOST_CHECK(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
esp_err_t ret = (timeout_ticks == 0) ? ESP_OK : ESP_ERR_TIMEOUT; // We don't want to return ESP_ERR_TIMEOUT if we aren't blocking
uint32_t event_flags;
HOST_ENTER_CRITICAL();
// Set handling_events flag. This prevents the host library from being uninstalled
p_host_lib_obj->dynamic.flags.handling_events = 1;
HOST_EXIT_CRITICAL();
while (1) {
// Loop until there are no more events
if (xSemaphoreTake(p_host_lib_obj->constant.event_sem, timeout_ticks) == pdFALSE) {
// Timed out waiting for semaphore or currently no events
break;
}
// Read and clear process pending flags
HOST_ENTER_CRITICAL();
uint32_t process_pending_flags = p_host_lib_obj->dynamic.process_pending_flags;
p_host_lib_obj->dynamic.process_pending_flags = 0;
HOST_EXIT_CRITICAL();
if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_USBH) {
ESP_ERROR_CHECK(usbh_process());
}
if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_HUB) {
ESP_ERROR_CHECK(hub_process());
}
if (process_pending_flags & PROCESS_REQUEST_PENDING_FLAG_ENUM) {
ESP_ERROR_CHECK(enum_process());
}
ret = ESP_OK;
// Set timeout_ticks to 0 so that we can check for events again without blocking
timeout_ticks = 0;
}
HOST_ENTER_CRITICAL();
p_host_lib_obj->dynamic.flags.handling_events = 0;
// Read and clear any event flags
event_flags = p_host_lib_obj->dynamic.lib_event_flags;
p_host_lib_obj->dynamic.lib_event_flags = 0;
HOST_EXIT_CRITICAL();
if (event_flags_ret != NULL) {
*event_flags_ret = event_flags;
}
return ret;
}
esp_err_t usb_host_lib_unblock(void)
{
// All devices must have been freed at this point
HOST_ENTER_CRITICAL();
HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
_unblock_lib(false);
HOST_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t usb_host_lib_info(usb_host_lib_info_t *info_ret)
{
HOST_CHECK(info_ret != NULL, ESP_ERR_INVALID_ARG);
int num_devs_temp;
int num_clients_temp;
HOST_ENTER_CRITICAL();
HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
num_clients_temp = p_host_lib_obj->dynamic.flags.num_clients;
HOST_EXIT_CRITICAL();
usbh_devs_num(&num_devs_temp);
// Write back return values
info_ret->num_devices = num_devs_temp;
info_ret->num_clients = num_clients_temp;
return ESP_OK;
}
esp_err_t usb_host_lib_set_root_port_power(bool enable)
{
esp_err_t ret;
if (enable) {
ret = hub_root_start();
} else {
ret = hub_root_stop();
}
return ret;
}
// ------------------------------------------------ Client Functions ---------------------------------------------------
// ----------------------- Private -------------------------
static void _handle_pending_ep(client_t *client_obj)
{
// Handle each EP on the pending list
while (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq)) {
// Get the next pending EP.
ep_wrapper_t *ep_wrap = TAILQ_FIRST(&client_obj->dynamic.pending_ep_tailq);
TAILQ_REMOVE(&client_obj->dynamic.pending_ep_tailq, ep_wrap, dynamic.tailq_entry);
TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, ep_wrap, dynamic.tailq_entry);
ep_wrap->dynamic.flags.pending = 0;
usbh_ep_event_t last_event = ep_wrap->dynamic.last_event;
uint32_t num_urb_dequeued = 0;
HOST_EXIT_CRITICAL();
// Handle pipe event
switch (last_event) {
case USBH_EP_EVENT_ERROR_XFER:
case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL:
case USBH_EP_EVENT_ERROR_OVERFLOW:
case USBH_EP_EVENT_ERROR_STALL:
// The endpoint is now stalled. Flush all pending URBs
ESP_ERROR_CHECK(usbh_ep_command(ep_wrap->constant.ep_hdl, USBH_EP_CMD_FLUSH));
// All URBs in this pipe are now retired waiting to be dequeued. Fall through to dequeue them
__attribute__((fallthrough));
case USBH_EP_EVENT_URB_DONE: {
// Dequeue all URBs and run their transfer callback
urb_t *urb;
usbh_ep_dequeue_urb(ep_wrap->constant.ep_hdl, &urb);
while (urb != NULL) {
// Clear the transfer's in-flight flag to indicate the transfer is no longer in-flight
urb->usb_host_inflight = false;
urb->transfer.callback(&urb->transfer);
num_urb_dequeued++;
usbh_ep_dequeue_urb(ep_wrap->constant.ep_hdl, &urb);
}
break;
}
default:
abort(); // Should never occur
break;
}
HOST_ENTER_CRITICAL();
// Update the endpoint's number of URB's in-flight
assert(num_urb_dequeued <= ep_wrap->dynamic.num_urb_inflight);
ep_wrap->dynamic.num_urb_inflight -= num_urb_dequeued;
}
}
// ----------------------- Public --------------------------
esp_err_t usb_host_client_register(const usb_host_client_config_t *client_config, usb_host_client_handle_t *client_hdl_ret)
{
HOST_CHECK(p_host_lib_obj, ESP_ERR_INVALID_STATE);
HOST_CHECK(client_config != NULL && client_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
HOST_CHECK(client_config->max_num_event_msg > 0, ESP_ERR_INVALID_ARG);
if (!client_config->is_synchronous) {
// Asynchronous clients must provide a
HOST_CHECK(client_config->async.client_event_callback != NULL, ESP_ERR_INVALID_ARG);
}
esp_err_t ret;
// Create client object
client_t *client_obj = heap_caps_calloc(1, sizeof(client_t), MALLOC_CAP_DEFAULT);
SemaphoreHandle_t event_sem = xSemaphoreCreateBinary();
QueueHandle_t event_msg_queue = xQueueCreate(client_config->max_num_event_msg, sizeof(usb_host_client_event_msg_t));
if (client_obj == NULL || event_sem == NULL || event_msg_queue == NULL) {
ret = ESP_ERR_NO_MEM;
goto alloc_err;
}
// Initialize client object
TAILQ_INIT(&client_obj->dynamic.pending_ep_tailq);
TAILQ_INIT(&client_obj->dynamic.idle_ep_tailq);
TAILQ_INIT(&client_obj->mux_protected.interface_tailq);
TAILQ_INIT(&client_obj->dynamic.done_ctrl_xfer_tailq);
client_obj->constant.event_sem = event_sem;
client_obj->constant.event_callback = client_config->async.client_event_callback;
client_obj->constant.callback_arg = client_config->async.callback_arg;
client_obj->constant.event_msg_queue = event_msg_queue;
// Add client to the host library's list of clients
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
HOST_ENTER_CRITICAL();
p_host_lib_obj->dynamic.flags.num_clients++;
HOST_EXIT_CRITICAL();
TAILQ_INSERT_TAIL(&p_host_lib_obj->mux_protected.client_tailq, client_obj, dynamic.tailq_entry);
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
// Write back client handle
*client_hdl_ret = (usb_host_client_handle_t)client_obj;
ret = ESP_OK;
return ret;
alloc_err:
if (event_msg_queue) {
vQueueDelete(event_msg_queue);
}
if (event_sem) {
vSemaphoreDelete(event_sem);
}
heap_caps_free(client_obj);
return ret;
}
esp_err_t usb_host_client_deregister(usb_host_client_handle_t client_hdl)
{
HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
esp_err_t ret;
// We take the mux_lock because we need to access the host library's client_tailq
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
HOST_ENTER_CRITICAL();
// Check that client can currently deregistered
bool can_deregister;
if (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq) ||
!TAILQ_EMPTY(&client_obj->dynamic.idle_ep_tailq) ||
!TAILQ_EMPTY(&client_obj->dynamic.done_ctrl_xfer_tailq) ||
client_obj->dynamic.flags.handling_events ||
client_obj->dynamic.flags.taking_mux ||
client_obj->dynamic.flags.num_intf_claimed != 0 ||
client_obj->dynamic.num_done_ctrl_xfer != 0 ||
client_obj->dynamic.opened_dev_addr_map[0] != 0 ||
client_obj->dynamic.opened_dev_addr_map[1] != 0 ||
client_obj->dynamic.opened_dev_addr_map[2] != 0 ||
client_obj->dynamic.opened_dev_addr_map[3] != 0) {
can_deregister = false;
} else {
can_deregister = true;
}
HOST_EXIT_CRITICAL();
if (!can_deregister) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// Remove client object from the library's list of clients
TAILQ_REMOVE(&p_host_lib_obj->mux_protected.client_tailq, client_obj, dynamic.tailq_entry);
HOST_ENTER_CRITICAL();
p_host_lib_obj->dynamic.flags.num_clients--;
if (p_host_lib_obj->dynamic.flags.num_clients == 0) {
// This is the last client being deregistered. Notify the lib handler
p_host_lib_obj->dynamic.lib_event_flags |= USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS;
_unblock_lib(false);
}
HOST_EXIT_CRITICAL();
// Free client object
vQueueDelete(client_obj->constant.event_msg_queue);
vSemaphoreDelete(client_obj->constant.event_sem);
heap_caps_free(client_obj);
ret = ESP_OK;
exit:
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
return ret;
}
esp_err_t usb_host_client_handle_events(usb_host_client_handle_t client_hdl, TickType_t timeout_ticks)
{
// Check arguments and state
HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG);
HOST_CHECK(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
esp_err_t ret = (timeout_ticks == 0) ? ESP_OK : ESP_ERR_TIMEOUT; // We don't want to return ESP_ERR_TIMEOUT if we aren't blocking
client_t *client_obj = (client_t *)client_hdl;
HOST_ENTER_CRITICAL();
// Set handling_events flag. This prevents the client from being deregistered
client_obj->dynamic.flags.handling_events = 1;
HOST_EXIT_CRITICAL();
while (1) {
// Loop until there are no more events
if (xSemaphoreTake(client_obj->constant.event_sem, timeout_ticks) == pdFALSE) {
// Timed out waiting for semaphore or currently no events
break;
}
HOST_ENTER_CRITICAL();
// Handle pending endpoints
if (!TAILQ_EMPTY(&client_obj->dynamic.pending_ep_tailq)) {
_handle_pending_ep(client_obj);
}
// Handle any done control transfers
while (client_obj->dynamic.num_done_ctrl_xfer > 0) {
urb_t *urb = TAILQ_FIRST(&client_obj->dynamic.done_ctrl_xfer_tailq);
TAILQ_REMOVE(&client_obj->dynamic.done_ctrl_xfer_tailq, urb, tailq_entry);
client_obj->dynamic.num_done_ctrl_xfer--;
HOST_EXIT_CRITICAL();
// Clear the transfer's in-flight flag to indicate the transfer is no longer in-flight
urb->usb_host_inflight = false;
// Call the transfer's callback
urb->transfer.callback(&urb->transfer);
HOST_ENTER_CRITICAL();
}
HOST_EXIT_CRITICAL();
// Handle event messages
while (uxQueueMessagesWaiting(client_obj->constant.event_msg_queue) > 0) {
// Dequeue the event message and call the client event callback
usb_host_client_event_msg_t event_msg;
BaseType_t queue_ret = xQueueReceive(client_obj->constant.event_msg_queue, &event_msg, 0);
assert(queue_ret == pdTRUE);
client_obj->constant.event_callback(&event_msg, client_obj->constant.callback_arg);
}
ret = ESP_OK;
// Set timeout_ticks to 0 so that we can check for events again without blocking
timeout_ticks = 0;
}
HOST_ENTER_CRITICAL();
client_obj->dynamic.flags.handling_events = 0;
HOST_EXIT_CRITICAL();
return ret;
}
esp_err_t usb_host_client_unblock(usb_host_client_handle_t client_hdl)
{
HOST_CHECK(client_hdl != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
HOST_ENTER_CRITICAL();
_unblock_client(client_obj, false);
HOST_EXIT_CRITICAL();
return ESP_OK;
}
// ------------------------------------------------- Device Handling ---------------------------------------------------
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)
{
HOST_CHECK(dev_addr > 0 && client_hdl != NULL && dev_hdl_ret != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
esp_err_t ret;
usb_device_handle_t dev_hdl;
ret = usbh_devs_open(dev_addr, &dev_hdl);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "usbh_devs_open error: %s", esp_err_to_name(ret));
goto exit;
}
HOST_ENTER_CRITICAL();
if (_check_client_opened_device(client_obj, dev_addr)) {
// Client has already opened the device. Close it and return an error
ret = ESP_ERR_INVALID_STATE;
HOST_EXIT_CRITICAL();
goto already_opened;
}
// Record in client object that we have opened the device of this address
_record_client_opened_device(client_obj, dev_addr);
HOST_EXIT_CRITICAL();
*dev_hdl_ret = dev_hdl;
ret = ESP_OK;
return ret;
already_opened:
ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
exit:
return ret;
}
esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl)
{
HOST_CHECK(dev_hdl != NULL && client_hdl != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
// We take the lock because we need to walk the interface list
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
esp_err_t ret;
// Check that all interfaces claimed by this client do not belong to this device
bool all_released = true;
interface_t *intf_obj;
TAILQ_FOREACH(intf_obj, &client_obj->mux_protected.interface_tailq, mux_protected.tailq_entry) {
if (intf_obj->constant.dev_hdl == dev_hdl) {
all_released = false;
break;
}
}
if (!all_released) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// Check that client actually opened the device in the first place
HOST_ENTER_CRITICAL();
uint8_t dev_addr;
ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr));
if (!_check_client_opened_device(client_obj, dev_addr)) {
// Client never opened this device
ret = ESP_ERR_INVALID_STATE;
HOST_EXIT_CRITICAL();
goto exit;
}
// Proceed to clear the record of the device form the client
_clear_client_opened_device(client_obj, dev_addr);
HOST_EXIT_CRITICAL();
ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
ret = ESP_OK;
exit:
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
return ret;
}
esp_err_t usb_host_device_free_all(void)
{
HOST_ENTER_CRITICAL();
HOST_CHECK_FROM_CRIT(p_host_lib_obj->dynamic.flags.num_clients == 0, ESP_ERR_INVALID_STATE); // All clients must have been deregistered
HOST_EXIT_CRITICAL();
esp_err_t ret;
#if ENABLE_USB_HUBS
hub_notify_all_free();
#endif // ENABLE_USB_HUBS
ret = usbh_devs_mark_all_free();
// If ESP_ERR_NOT_FINISHED is returned, caller must wait for USB_HOST_LIB_EVENT_FLAGS_ALL_FREE to confirm all devices are free
return ret;
}
esp_err_t usb_host_device_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret)
{
HOST_CHECK(dev_addr_list != NULL && num_dev_ret != NULL, ESP_ERR_INVALID_ARG);
return usbh_devs_addr_list_fill(list_len, dev_addr_list, num_dev_ret);
}
// ------------------------------------------------- Device Requests ---------------------------------------------------
// ------------------- Cached Requests ---------------------
esp_err_t usb_host_device_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_info)
{
HOST_CHECK(dev_hdl != NULL && dev_info != NULL, ESP_ERR_INVALID_ARG);
return usbh_dev_get_info(dev_hdl, dev_info);
}
// ----------------------------------------------- Descriptor Requests -------------------------------------------------
// ----------------- Cached Descriptors --------------------
esp_err_t usb_host_get_device_descriptor(usb_device_handle_t dev_hdl, const usb_device_desc_t **device_desc)
{
HOST_CHECK(dev_hdl != NULL && device_desc != NULL, ESP_ERR_INVALID_ARG);
return usbh_dev_get_desc(dev_hdl, device_desc);
}
esp_err_t usb_host_get_active_config_descriptor(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc)
{
HOST_CHECK(dev_hdl != NULL && config_desc != NULL, ESP_ERR_INVALID_ARG);
return usbh_dev_get_config_desc(dev_hdl, config_desc);
}
// ----------------- Descriptors Transfer Requests --------------------
static usb_transfer_status_t wait_for_transmission_done(usb_transfer_t *transfer)
{
SemaphoreHandle_t transfer_done = (SemaphoreHandle_t)transfer->context;
xSemaphoreTake(transfer_done, portMAX_DELAY);
usb_transfer_status_t status = transfer->status;
// EP0 halt->flush->clear is managed by USBH and lower layers
return status;
}
static esp_err_t get_config_desc_transfer(usb_host_client_handle_t client_hdl, usb_transfer_t *ctrl_transfer, const int bConfigurationValue, const int num_bytes)
{
const usb_device_desc_t *dev_desc;
ESP_ERROR_CHECK(usbh_dev_get_desc(ctrl_transfer->device_handle, &dev_desc));
usb_setup_packet_t *setup_pkt = (usb_setup_packet_t *)ctrl_transfer->data_buffer;
USB_SETUP_PACKET_INIT_GET_CONFIG_DESC(setup_pkt, bConfigurationValue - 1, num_bytes);
ctrl_transfer->num_bytes = sizeof(usb_setup_packet_t) + usb_round_up_to_mps(num_bytes, dev_desc->bMaxPacketSize0);
// IN data stage should return exactly num_bytes (SHORT_DESC_REQ_LEN or wTotalLength) bytes
const int expect_num_bytes = sizeof(usb_setup_packet_t) + num_bytes;
// Submit control transfer
esp_err_t ret = usb_host_transfer_submit_control(client_hdl, ctrl_transfer);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Submit ctrl transfer failed %s", esp_err_to_name(ret));
return ret;
}
// Wait for transfer to finish
const usb_transfer_status_t status_short_desc = wait_for_transmission_done(ctrl_transfer);
if (status_short_desc != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(USB_HOST_TAG, "Get config descriptor transfer status: %d", status_short_desc);
ret = ESP_ERR_INVALID_STATE;
return ret;
}
// Check IN transfer returned the expected correct number of bytes
if ((expect_num_bytes != 0) && (ctrl_transfer->actual_num_bytes != expect_num_bytes)) {
if (ctrl_transfer->actual_num_bytes > expect_num_bytes) {
// The device returned more bytes than requested.
// This violates the USB specs chapter 9.3.5, but we can continue
ESP_LOGW(USB_HOST_TAG, "Incorrect number of bytes returned %d", ctrl_transfer->actual_num_bytes);
return ESP_OK;
} else {
// The device returned less bytes than requested. We cannot continue.
ESP_LOGE(USB_HOST_TAG, "Incorrect number of bytes returned %d", ctrl_transfer->actual_num_bytes);
return ESP_ERR_INVALID_RESPONSE;
}
}
return ESP_OK;
}
esp_err_t usb_host_get_config_desc(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bConfigurationValue, const usb_config_desc_t **config_desc_ret)
{
esp_err_t ret = ESP_OK;
HOST_CHECK(client_hdl != NULL && dev_hdl != NULL && config_desc_ret != NULL, ESP_ERR_INVALID_ARG);
// Get number of configurations
const usb_device_desc_t *dev_desc;
ESP_ERROR_CHECK(usbh_dev_get_desc(dev_hdl, &dev_desc));
HOST_CHECK(bConfigurationValue != 0, ESP_ERR_INVALID_ARG);
HOST_CHECK(bConfigurationValue <= dev_desc->bNumConfigurations, ESP_ERR_NOT_SUPPORTED);
// Initialize transfer
usb_transfer_t *ctrl_transfer;
if (usb_host_transfer_alloc(sizeof(usb_setup_packet_t) + CTRL_TRANSFER_MAX_DATA_LEN, 0, &ctrl_transfer)) {
return ESP_ERR_NO_MEM;
}
SemaphoreHandle_t transfer_done = xSemaphoreCreateBinary();
if (transfer_done == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
ctrl_transfer->device_handle = dev_hdl;
ctrl_transfer->bEndpointAddress = 0;
ctrl_transfer->callback = get_config_desc_transfer_cb;
ctrl_transfer->context = (void *)transfer_done;
// Initiate control transfer for short config descriptor
ret = get_config_desc_transfer(client_hdl, ctrl_transfer, bConfigurationValue, SHORT_DESC_REQ_LEN);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Get short config desc. failed %s", esp_err_to_name(ret));
goto exit;
}
// Get length of full config descriptor
const usb_config_desc_t *config_desc_short = (usb_config_desc_t *)(ctrl_transfer->data_buffer + sizeof(usb_setup_packet_t));
// Initiate control transfer for full config descriptor
ret = get_config_desc_transfer(client_hdl, ctrl_transfer, bConfigurationValue, config_desc_short->wTotalLength);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Get full config desc. failed %s", esp_err_to_name(ret));
goto exit;
}
// Allocate memory to store the configuration descriptor
const usb_config_desc_t *config_desc_full = (usb_config_desc_t *)(ctrl_transfer->data_buffer + sizeof(usb_setup_packet_t));
usb_config_desc_t *config_desc = heap_caps_malloc(config_desc_full->wTotalLength, MALLOC_CAP_DEFAULT);
if (config_desc == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
// Copy the configuration descriptor
memcpy(config_desc, config_desc_full, config_desc_full->wTotalLength);
*config_desc_ret = config_desc;
ret = ESP_OK;
exit:
if (ctrl_transfer) {
usb_host_transfer_free(ctrl_transfer);
}
if (transfer_done != NULL) {
vSemaphoreDelete(transfer_done);
}
return ret;
}
esp_err_t usb_host_free_config_desc(const usb_config_desc_t *config_desc)
{
HOST_CHECK(config_desc != NULL, ESP_ERR_INVALID_ARG);
heap_caps_free((usb_config_desc_t*)config_desc);
return ESP_OK;
}
// ----------------------------------------------- Interface Functions -------------------------------------------------
// ----------------------- Private -------------------------
static esp_err_t ep_wrapper_alloc(usb_device_handle_t dev_hdl, const usb_ep_desc_t *ep_desc, interface_t *intf_obj, ep_wrapper_t **ep_wrap_ret)
{
ep_wrapper_t *ep_wrap = heap_caps_calloc(1, sizeof(ep_wrapper_t), MALLOC_CAP_DEFAULT);
if (ep_wrap == NULL) {
return ESP_ERR_NO_MEM;
}
esp_err_t ret;
usbh_ep_handle_t ep_hdl;
usbh_ep_config_t ep_config = {
.bInterfaceNumber = intf_obj->constant.intf_desc->bInterfaceNumber,
.bAlternateSetting = intf_obj->constant.intf_desc->bAlternateSetting,
.bEndpointAddress = ep_desc->bEndpointAddress,
.ep_cb = endpoint_callback,
.ep_cb_arg = (void *)ep_wrap,
.context = (void *)ep_wrap,
};
ret = usbh_ep_alloc(dev_hdl, &ep_config, &ep_hdl);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "EP allocation error %s", esp_err_to_name(ret));
goto alloc_err;
}
// Initialize endpoint wrapper item
ep_wrap->constant.ep_hdl = ep_hdl;
ep_wrap->constant.intf_obj = intf_obj;
// Write back result
*ep_wrap_ret = ep_wrap;
ret = ESP_OK;
return ret;
alloc_err:
heap_caps_free(ep_wrap);
return ret;
}
static void ep_wrapper_free(usb_device_handle_t dev_hdl, ep_wrapper_t *ep_wrap)
{
if (ep_wrap == NULL) {
return;
}
// Free the underlying endpoint
ESP_ERROR_CHECK(usbh_ep_free(ep_wrap->constant.ep_hdl));
// Free the endpoint wrapper item
heap_caps_free(ep_wrap);
}
static interface_t *interface_alloc(client_t *client_obj, usb_device_handle_t dev_hdl, const usb_intf_desc_t *intf_desc)
{
interface_t *intf_obj = heap_caps_calloc(1, sizeof(interface_t) + (sizeof(ep_wrapper_t *) * intf_desc->bNumEndpoints), MALLOC_CAP_DEFAULT);
if (intf_obj == NULL) {
return NULL;
}
intf_obj->constant.intf_desc = intf_desc;
intf_obj->constant.client_obj = client_obj;
intf_obj->constant.dev_hdl = dev_hdl;
return intf_obj;
}
static void interface_free(interface_t *intf_obj)
{
if (intf_obj == NULL) {
return;
}
for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) {
assert(intf_obj->constant.endpoints[i] == NULL);
}
heap_caps_free(intf_obj);
}
static esp_err_t interface_claim(client_t *client_obj, usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uint8_t bAlternateSetting, interface_t **intf_obj_ret)
{
esp_err_t ret;
// We need to walk to configuration descriptor to find the correct interface descriptor, and each of its constituent endpoint descriptors
// Find the interface descriptor and allocate the interface object
int offset_intf;
const usb_intf_desc_t *intf_desc = usb_parse_interface_descriptor(config_desc, bInterfaceNumber, bAlternateSetting, &offset_intf);
if (intf_desc == NULL) {
ESP_LOGE(USB_HOST_TAG, "Interface %d not found in config. desc.", bInterfaceNumber);
ret = ESP_ERR_NOT_FOUND;
goto exit;
}
// Allocate interface object
interface_t *intf_obj = interface_alloc(client_obj, dev_hdl, intf_desc);
if (intf_obj == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
// Find each endpoint descriptor in the interface by index, and allocate those endpoints
for (int i = 0; i < intf_desc->bNumEndpoints; i++) {
int offset_ep = offset_intf;
const usb_ep_desc_t *ep_desc = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &offset_ep);
if (ep_desc == NULL) {
ESP_LOGE(USB_HOST_TAG, "EP desc. %d not found in Interface desc.", bInterfaceNumber);
ret = ESP_ERR_NOT_FOUND;
goto ep_alloc_err;
}
// Allocate the endpoint wrapper item
ep_wrapper_t *ep_wrap;
ret = ep_wrapper_alloc(dev_hdl, ep_desc, intf_obj, &ep_wrap);
if (ret != ESP_OK) {
goto ep_alloc_err;
}
// Fill the interface object with the allocated endpoints
intf_obj->constant.endpoints[i] = ep_wrap;
}
// Add interface object to client (safe because we have already taken the mutex)
TAILQ_INSERT_TAIL(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry);
// Add each endpoint wrapper item to the client's endpoint list
HOST_ENTER_CRITICAL();
for (int i = 0; i < intf_desc->bNumEndpoints; i++) {
TAILQ_INSERT_TAIL(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry);
}
HOST_EXIT_CRITICAL();
// Write back result
*intf_obj_ret = intf_obj;
ret = ESP_OK;
return ret;
ep_alloc_err:
for (int i = 0; i < intf_desc->bNumEndpoints; i++) {
ep_wrapper_free(dev_hdl, intf_obj->constant.endpoints[i]);
intf_obj->constant.endpoints[i] = NULL;
}
interface_free(intf_obj);
exit:
return ret;
}
static esp_err_t interface_release(client_t *client_obj, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber)
{
esp_err_t ret;
// Find the interface object
interface_t *intf_obj_iter;
interface_t *intf_obj = NULL;
TAILQ_FOREACH(intf_obj_iter, &client_obj->mux_protected.interface_tailq, mux_protected.tailq_entry) {
if (intf_obj_iter->constant.dev_hdl == dev_hdl && intf_obj_iter->constant.intf_desc->bInterfaceNumber == bInterfaceNumber) {
intf_obj = intf_obj_iter;
break;
}
}
if (intf_obj == NULL) {
ret = ESP_ERR_NOT_FOUND;
goto exit;
}
// Check that all endpoints in the interface are in a state to be freed
// Todo: Check that each EP is halted before allowing them to be freed (IDF-7273)
HOST_ENTER_CRITICAL();
bool can_free = true;
for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) {
ep_wrapper_t *ep_wrap = intf_obj->constant.endpoints[i];
// Endpoint must not be on the pending list and must not have in-flight URBs
if (ep_wrap->dynamic.num_urb_inflight != 0 || ep_wrap->dynamic.flags.pending) {
can_free = false;
break;
}
}
if (!can_free) {
HOST_EXIT_CRITICAL();
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// Proceed to remove all endpoint wrapper items from the list
for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) {
TAILQ_REMOVE(&client_obj->dynamic.idle_ep_tailq, intf_obj->constant.endpoints[i], dynamic.tailq_entry);
}
HOST_EXIT_CRITICAL();
// Remove the interface object from the list (safe because we have already taken the mutex)
TAILQ_REMOVE(&client_obj->mux_protected.interface_tailq, intf_obj, mux_protected.tailq_entry);
// Free each endpoint in the interface
for (int i = 0; i < intf_obj->constant.intf_desc->bNumEndpoints; i++) {
ep_wrapper_free(dev_hdl, intf_obj->constant.endpoints[i]);
intf_obj->constant.endpoints[i] = NULL;
}
// Free the interface object itself
interface_free(intf_obj);
ret = ESP_OK;
exit:
return ret;
}
// ----------------------- Public --------------------------
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)
{
HOST_CHECK(client_hdl != NULL && dev_hdl != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
HOST_ENTER_CRITICAL();
uint8_t dev_addr;
ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr));
// Check if client actually opened device
HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_INVALID_STATE);
client_obj->dynamic.flags.taking_mux = 1;
HOST_EXIT_CRITICAL();
// Take mux lock. This protects the client being released or other clients from claiming interfaces
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
esp_err_t ret;
const usb_config_desc_t *config_desc;
ESP_ERROR_CHECK(usbh_dev_get_config_desc(dev_hdl, &config_desc));
interface_t *intf_obj;
// Claim interface
ret = interface_claim(client_obj, dev_hdl, config_desc, bInterfaceNumber, bAlternateSetting, &intf_obj);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Claiming interface error: %s", esp_err_to_name(ret));
goto exit;
}
ret = ESP_OK;
exit:
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
HOST_ENTER_CRITICAL();
if (ret == ESP_OK) {
client_obj->dynamic.flags.num_intf_claimed++;
}
client_obj->dynamic.flags.taking_mux = 0;
HOST_EXIT_CRITICAL();
return ret;
}
esp_err_t usb_host_interface_release(usb_host_client_handle_t client_hdl, usb_device_handle_t dev_hdl, uint8_t bInterfaceNumber)
{
HOST_CHECK(client_hdl != NULL && dev_hdl != NULL, ESP_ERR_INVALID_ARG);
client_t *client_obj = (client_t *)client_hdl;
HOST_ENTER_CRITICAL();
uint8_t dev_addr;
ESP_ERROR_CHECK(usbh_dev_get_addr(dev_hdl, &dev_addr));
// Check if client actually opened device
HOST_CHECK_FROM_CRIT(_check_client_opened_device(client_obj, dev_addr), ESP_ERR_INVALID_STATE);
client_obj->dynamic.flags.taking_mux = 1;
HOST_EXIT_CRITICAL();
// Take mux lock. This protects the client being released or other clients from claiming interfaces
xSemaphoreTake(p_host_lib_obj->constant.mux_lock, portMAX_DELAY);
esp_err_t ret = interface_release(client_obj, dev_hdl, bInterfaceNumber);
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
HOST_ENTER_CRITICAL();
if (ret == ESP_OK) {
client_obj->dynamic.flags.num_intf_claimed--;
}
client_obj->dynamic.flags.taking_mux = 0;
HOST_EXIT_CRITICAL();
return ret;
}
/* Just print error returned from usbh_ep_get_handle() */
static void print_error_ep_get_handle(esp_err_t err)
{
ESP_LOGE(USB_HOST_TAG, "Get EP handle error: %s", esp_err_to_name(err));
}
/* Just print error returned from usbh_ep_command() */
static void print_error_ep_command(esp_err_t err)
{
ESP_LOGE(USB_HOST_TAG, "EP command error: %s", esp_err_to_name(err));
}
esp_err_t usb_host_endpoint_halt(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress)
{
esp_err_t ret;
usbh_ep_handle_t ep_hdl;
ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl);
if (ret != ESP_OK) {
print_error_ep_get_handle(ret);
goto exit;
}
ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_HALT);
if (ret != ESP_OK) {
print_error_ep_command(ret);
}
exit:
return ret;
}
esp_err_t usb_host_endpoint_flush(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress)
{
esp_err_t ret;
usbh_ep_handle_t ep_hdl;
ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl);
if (ret != ESP_OK) {
print_error_ep_get_handle(ret);
goto exit;
}
ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_FLUSH);
if (ret != ESP_OK) {
print_error_ep_command(ret);
}
exit:
return ret;
}
esp_err_t usb_host_endpoint_clear(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress)
{
esp_err_t ret;
usbh_ep_handle_t ep_hdl;
ret = usbh_ep_get_handle(dev_hdl, bEndpointAddress, &ep_hdl);
if (ret != ESP_OK) {
print_error_ep_get_handle(ret);
goto exit;
}
ret = usbh_ep_command(ep_hdl, USBH_EP_CMD_CLEAR);
if (ret != ESP_OK) {
print_error_ep_command(ret);
}
exit:
return ret;
}
// ------------------------------------------------ Asynchronous I/O ---------------------------------------------------
// ----------------------- Public --------------------------
esp_err_t usb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer)
{
urb_t *urb = urb_alloc(data_buffer_size, num_isoc_packets);
if (urb == NULL) {
return ESP_ERR_NO_MEM;
}
*transfer = &urb->transfer;
return ESP_OK;
}
esp_err_t usb_host_transfer_free(usb_transfer_t *transfer)
{
if (transfer == NULL) {
return ESP_OK;
}
urb_t *urb = __containerof(transfer, urb_t, transfer);
urb_free(urb);
return ESP_OK;
}
esp_err_t usb_host_transfer_submit(usb_transfer_t *transfer)
{
HOST_CHECK(transfer != NULL, ESP_ERR_INVALID_ARG);
// Check that transfer and target endpoint are valid
HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); // Target device must be set
HOST_CHECK((transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) != 0, ESP_ERR_INVALID_ARG);
usbh_ep_handle_t ep_hdl;
ep_wrapper_t *ep_wrap = NULL;
urb_t *urb_obj = __containerof(transfer, urb_t, transfer);
esp_err_t ret;
ret = usbh_ep_get_handle(transfer->device_handle, transfer->bEndpointAddress, &ep_hdl);
if (ret != ESP_OK) {
print_error_ep_get_handle(ret);
goto err;
}
ep_wrap = usbh_ep_get_context(ep_hdl);
assert(ep_wrap != NULL);
// Check that we are not submitting a transfer already in-flight
HOST_CHECK(!urb_obj->usb_host_inflight, ESP_ERR_NOT_FINISHED);
urb_obj->usb_host_inflight = true;
HOST_ENTER_CRITICAL();
ep_wrap->dynamic.num_urb_inflight++;
HOST_EXIT_CRITICAL();
ret = usbh_ep_enqueue_urb(ep_hdl, urb_obj);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Enqueue URB error: %s", esp_err_to_name(ret));
goto submit_err;
}
return ret;
submit_err:
HOST_ENTER_CRITICAL();
ep_wrap->dynamic.num_urb_inflight--;
HOST_EXIT_CRITICAL();
urb_obj->usb_host_inflight = false;
err:
return ret;
}
esp_err_t usb_host_transfer_submit_control(usb_host_client_handle_t client_hdl, usb_transfer_t *transfer)
{
HOST_CHECK(client_hdl != NULL && transfer != NULL, ESP_ERR_INVALID_ARG);
// Check that control transfer is valid
HOST_CHECK(transfer->device_handle != NULL, ESP_ERR_INVALID_ARG); // Target device must be set
// Control transfers must be targeted at EP 0
HOST_CHECK((transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) == 0, ESP_ERR_INVALID_ARG);
usb_device_handle_t dev_hdl = transfer->device_handle;
urb_t *urb_obj = __containerof(transfer, urb_t, transfer);
// Check that we are not submitting a transfer already in-flight
HOST_CHECK(!urb_obj->usb_host_inflight, ESP_ERR_NOT_FINISHED);
urb_obj->usb_host_inflight = true;
// Save client handle into URB
urb_obj->usb_host_client = (void *)client_hdl;
esp_err_t ret;
ret = usbh_dev_submit_ctrl_urb(dev_hdl, urb_obj);
if (ret != ESP_OK) {
ESP_LOGE(USB_HOST_TAG, "Submit CTRL URB error: %s", esp_err_to_name(ret));
urb_obj->usb_host_inflight = false;
}
return ret;
}