mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
1757 lines
68 KiB
C
1757 lines
68 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
#include "usb_private.h"
|
|
#include "ext_hub.h"
|
|
#include "usb/usb_helpers.h"
|
|
|
|
typedef struct ext_port_s *ext_port_hdl_t; /* This will be implemented during ext_port driver implementation */
|
|
|
|
#define EXT_HUB_MAX_STATUS_BYTES_SIZE (sizeof(uint32_t))
|
|
#define EXT_HUB_STATUS_CHANGE_FLAG (1 << 0)
|
|
#define EXT_HUB_STATUS_PORT1_CHANGE_FLAG (1 << 1)
|
|
#define EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
|
|
|
|
/**
|
|
* @brief Device state
|
|
*
|
|
* Global state of the Device
|
|
*/
|
|
typedef enum {
|
|
EXT_HUB_STATE_ATTACHED, /**< Device attached, but not state is unknown (no: Hub Descriptor, Device status and Hub status) */
|
|
EXT_HUB_STATE_CONFIGURED, /**< Device attached and configured (has Hub Descriptor, Device status and Hub status were requested )*/
|
|
EXT_HUB_STATE_SUSPENDED, /**< Device suspended */
|
|
EXT_HUB_STATE_FAILED /**< Device has internal error */
|
|
} ext_hub_state_t;
|
|
|
|
/**
|
|
* @brief Device stages
|
|
*
|
|
* During the lifecycle, Hub requires different actions. To implement interaction with external Hub the FSM, based on these stages is using.
|
|
*
|
|
* Entry:
|
|
* - Every new attached external Hub should start from EXT_HUB_STAGE_GET_HUB_DESCRIPTOR. Without Fetching Hub Descriptor, device is not configured and doesn't have any ports.
|
|
* - After handling the response during EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, the External Hub Driver configures the Device according to the data from Hub Descriptor.
|
|
* - After completion of any stage, the Device does back to the IDLE stage and waits for another request. Source of the request could be: EP1 INT callback (the Hub or Ports changes) or external call.
|
|
* - Stages, that don't required response handling could not end up with fail.
|
|
*/
|
|
typedef enum {
|
|
// Device in IDLE state
|
|
EXT_HUB_STAGE_IDLE = 0, /**< Device in idle state and do not fulfill any actions */
|
|
// Stages, required response handling
|
|
EXT_HUB_STAGE_GET_DEVICE_STATUS, /**< Device requests Device Status. For more details, refer to 9.4.5 Get Status of usb_20 */
|
|
EXT_HUB_STAGE_CHECK_DEVICE_STATUS, /**< Device received the Device Status and required its' handling */
|
|
EXT_HUB_STAGE_GET_HUB_DESCRIPTOR, /**< Device requests Hub Descriptor. For more details, refer to 11.24.2.5 Get Hub Descriptor of usb_20 */
|
|
EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR, /**< Device received the Hub Descriptor and requires its' handling */
|
|
EXT_HUB_STAGE_GET_HUB_STATUS, /**< Device requests Hub Status. For more details, refer to 11.24.2.6 Get Hub Status of usb_20 */
|
|
EXT_HUB_STAGE_CHECK_HUB_STATUS, /**< Device received the Hub Status and requires its' handling */
|
|
// Stages, don't required response handling
|
|
EXT_HUB_STAGE_PORT_FEATURE, /**< Device completed the Port Feature class-specific request (Set Feature or Clear Feature). For more details, refer to 11.24.2 Class-specific Requests of usb_20 */
|
|
EXT_HUB_STAGE_PORT_STATUS_REQUEST, /**< Device completed the Port Get Status class-specific request. For more details, refer to 11.24.2 Class-specific Requests of usb_20 */
|
|
EXT_HUB_STAGE_FAILURE /**< Device has internal error and requires handling */
|
|
} ext_hub_stage_t;
|
|
|
|
const char *const ext_hub_stage_strings[] = {
|
|
"IDLE",
|
|
"GET_DEVICE_STATUS",
|
|
"CHECK_DEVICE_STATUS",
|
|
"GET_HUB_DESCRIPTOR",
|
|
"CHECK_HUB_DESCRIPTOR",
|
|
"GET_HUB_STATUS",
|
|
"CHECK_HUB_STATUS",
|
|
"PORT_FEATURE",
|
|
"PORT_STATUS_REQUEST",
|
|
"FAILURE"
|
|
};
|
|
|
|
/**
|
|
* @brief Device action flags
|
|
*/
|
|
typedef enum {
|
|
DEV_ACTION_EP0_COMPLETE = (1 << 1), /**< Device complete one of stages, requires handling */
|
|
DEV_ACTION_EP1_FLUSH = (1 << 2), /**< Device's Interrupt EP needs to be flushed */
|
|
DEV_ACTION_EP1_DEQUEUE = (1 << 3), /**< Device's Interrupt EP needs to be dequeued */
|
|
DEV_ACTION_EP1_CLEAR = (1 << 4), /**< Device's Interrupt EP needs to be cleared */
|
|
DEV_ACTION_REQ = (1 << 5), /**< Device has new actions and required handling */
|
|
DEV_ACTION_ERROR = (1 << 6), /**< Device encounters an error */
|
|
DEV_ACTION_GONE = (1 << 7), /**< Device was gone */
|
|
DEV_ACTION_RELEASE = (1 << 8), /**< Device was released */
|
|
DEV_ACTION_FREE = (1 << 9), /**< Device should be freed */
|
|
} dev_action_t;
|
|
|
|
typedef struct ext_hub_s ext_hub_dev_t;
|
|
|
|
/**
|
|
* @brief External Hub device configuration parameters for device allocation
|
|
*/
|
|
typedef struct {
|
|
usb_device_handle_t dev_hdl; /**< Device's handle */
|
|
uint8_t dev_addr; /**< Device's bus address */
|
|
const usb_intf_desc_t *iface_desc; /**< Device's Interface Descriptor pointer */
|
|
const usb_ep_desc_t *ep_in_desc; /**< Device's IN Endpoint Descriptor pointer */
|
|
} device_config_t;
|
|
|
|
struct ext_hub_s {
|
|
struct {
|
|
TAILQ_ENTRY(ext_hub_s) tailq_entry;
|
|
union {
|
|
struct {
|
|
uint32_t in_pending_list: 1; /**< Device is in pending list */
|
|
uint32_t waiting_release: 1; /**< Device waiting to be released */
|
|
uint32_t waiting_children: 1; /**< Device is waiting children to be freed */
|
|
uint32_t waiting_free: 1; /**< Device waiting to be freed */
|
|
uint32_t is_gone: 1; /**< Device is gone */
|
|
uint32_t reserved27: 27; /**< Reserved */
|
|
};
|
|
uint32_t val; /**< Device's flags value */
|
|
} flags;
|
|
uint32_t action_flags; /**< Device's action flags */
|
|
} dynamic; /**< Dynamic members require a critical section */
|
|
|
|
struct {
|
|
ext_hub_stage_t stage; /**< Device's stage */
|
|
uint8_t maxchild; /**< Amount of allocated ports. Could be 0 for some Hubs. Is increased when new port is added and decreased when port has been freed. */
|
|
ext_hub_state_t state; /**< Device's state */
|
|
} single_thread; /**< Single thread members don't require a critical section, as long as they are never accessed from multiple threads */
|
|
|
|
struct {
|
|
// For optimisation & debug only
|
|
uint8_t iface_num; /**< Device's bInterfaceNum */
|
|
// Driver purpose
|
|
uint8_t dev_addr; /**< Device's bus address */
|
|
usb_device_handle_t dev_hdl; /**< Device's handle */
|
|
urb_t *ctrl_urb; /**< Device's Control pipe transfer URB */
|
|
urb_t *in_urb; /**< Device's Interrupt pipe URB */
|
|
usbh_ep_handle_t ep_in_hdl; /**< Device's Interrupt EP handle */
|
|
|
|
usb_hub_descriptor_t *hub_desc; /**< Device's Hub descriptor pointer. Could be NULL when not requested */
|
|
ext_port_hdl_t *ports; /**< Flexible array of Ports. Could be NULL, when maxchild is 0 */
|
|
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
|
|
};
|
|
|
|
typedef struct {
|
|
struct {
|
|
TAILQ_HEAD(ext_hubs, ext_hub_s) ext_hubs_tailq; /**< Idle tailq */
|
|
TAILQ_HEAD(ext_hubs_cb, ext_hub_s) ext_hubs_pending_tailq; /**< Pending tailq */
|
|
} dynamic; /**< Dynamic members require a critical section */
|
|
|
|
struct {
|
|
ext_hub_cb_t proc_req_cb; /**< Process callback */
|
|
void *proc_req_cb_arg; /**< Process callback argument */
|
|
const ext_hub_port_driver_t* port_driver; /**< External Port Driver */
|
|
} constant; /**< Constant members. Do not change after installation thus do not require a critical section or mutex */
|
|
} ext_hub_driver_t;
|
|
|
|
static ext_hub_driver_t *p_ext_hub_driver = NULL;
|
|
static portMUX_TYPE ext_hub_driver_lock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
const char *EXT_HUB_TAG = "EXT_HUB";
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ------------------------------- Helpers -------------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#define EXT_HUB_ENTER_CRITICAL() portENTER_CRITICAL(&ext_hub_driver_lock)
|
|
#define EXT_HUB_EXIT_CRITICAL() portEXIT_CRITICAL(&ext_hub_driver_lock)
|
|
#define EXT_HUB_ENTER_CRITICAL_SAFE() portENTER_CRITICAL_SAFE(&ext_hub_driver_lock)
|
|
#define EXT_HUB_EXIT_CRITICAL_SAFE() portEXIT_CRITICAL_SAFE(&ext_hub_driver_lock)
|
|
|
|
#define EXT_HUB_CHECK(cond, ret_val) ({ \
|
|
if (!(cond)) { \
|
|
return (ret_val); \
|
|
} \
|
|
})
|
|
#define EXT_HUB_CHECK_FROM_CRIT(cond, ret_val) ({ \
|
|
if (!(cond)) { \
|
|
EXT_HUB_EXIT_CRITICAL(); \
|
|
return ret_val; \
|
|
} \
|
|
})
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ----------------------- Forward declaration ---------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags);
|
|
static void device_error(ext_hub_dev_t *ext_hub_dev);
|
|
static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ---------------------- Callbacks (implementation) ---------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
static bool interrupt_pipe_cb(usbh_ep_handle_t ep_hdl, usbh_ep_event_t ep_event, void *user_arg, bool in_isr)
|
|
{
|
|
uint32_t action_flags;
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)user_arg;
|
|
|
|
switch (ep_event) {
|
|
case USBH_EP_EVENT_URB_DONE: {
|
|
// A interrupt transfer completed on EP1's pipe . We need to dequeue it
|
|
action_flags = DEV_ACTION_EP1_DEQUEUE;
|
|
break;
|
|
case USBH_EP_EVENT_ERROR_XFER:
|
|
case USBH_EP_EVENT_ERROR_URB_NOT_AVAIL:
|
|
case USBH_EP_EVENT_ERROR_OVERFLOW:
|
|
// EP1's pipe has encountered an error. We need to retire all URBs, dequeue them, then make the pipe active again
|
|
action_flags = DEV_ACTION_EP1_FLUSH |
|
|
DEV_ACTION_EP1_DEQUEUE |
|
|
DEV_ACTION_EP1_CLEAR;
|
|
if (in_isr) {
|
|
ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr);
|
|
} else {
|
|
ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 Error", ext_hub_dev->constant.dev_addr);
|
|
}
|
|
break;
|
|
case USBH_EP_EVENT_ERROR_STALL:
|
|
// EP1's pipe encountered a "protocol stall". We just need to dequeue URBs then make the pipe active again
|
|
action_flags = DEV_ACTION_EP1_DEQUEUE | DEV_ACTION_EP1_CLEAR;
|
|
if (in_isr) {
|
|
ESP_EARLY_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr);
|
|
} else {
|
|
ESP_LOGE(EXT_HUB_TAG, "Device %d EP1 STALL", ext_hub_dev->constant.dev_addr);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
action_flags = 0;
|
|
break;
|
|
}
|
|
|
|
EXT_HUB_ENTER_CRITICAL_SAFE();
|
|
bool call_proc_req_cb = _device_set_actions(ext_hub_dev, action_flags);
|
|
EXT_HUB_EXIT_CRITICAL_SAFE();
|
|
|
|
bool yield = false;
|
|
if (call_proc_req_cb) {
|
|
yield = p_ext_hub_driver->constant.proc_req_cb(in_isr, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
return yield;
|
|
}
|
|
|
|
/**
|
|
* @brief Control transfer completion callback
|
|
*
|
|
* Is called by lower logic when transfer is completed with or without error
|
|
*
|
|
* @param[in] ctrl_xfer Pointer to a transfer buffer
|
|
*/
|
|
static void control_transfer_complete_cb(usb_transfer_t *ctrl_xfer)
|
|
{
|
|
bool call_proc_req_cb = false;
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *) ctrl_xfer->context;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_EP0_COMPLETE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
}
|
|
|
|
static void interrupt_transfer_complete_cb(usb_transfer_t *intr_xfer)
|
|
{
|
|
assert(intr_xfer);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)intr_xfer->context;
|
|
assert(ext_hub_dev);
|
|
|
|
switch (intr_xfer->status) {
|
|
case USB_TRANSFER_STATUS_COMPLETED:
|
|
ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, intr_xfer->data_buffer, intr_xfer->actual_num_bytes, ESP_LOG_VERBOSE);
|
|
device_status_change_handle(ext_hub_dev, intr_xfer->data_buffer, intr_xfer->actual_num_bytes);
|
|
break;
|
|
case USB_TRANSFER_STATUS_NO_DEVICE:
|
|
// Device was removed, nothing to do
|
|
break;
|
|
case USB_TRANSFER_STATUS_CANCELED:
|
|
// Cancellation due to USB Host uninstall routine
|
|
break;
|
|
default:
|
|
// Any other error
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d] Interrupt transfer failed, status %d", ext_hub_dev->constant.dev_addr, intr_xfer->status);
|
|
device_error(ext_hub_dev);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --------------------------- Internal Logic ---------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
static bool _device_set_actions(ext_hub_dev_t *ext_hub_dev, uint32_t action_flags)
|
|
{
|
|
/*
|
|
THIS FUNCTION MUST BE CALLED FROM A CRITICAL SECTION
|
|
*/
|
|
if (action_flags == 0) {
|
|
return false;
|
|
}
|
|
bool call_proc_req_cb;
|
|
// Check if device is already on the callback list
|
|
if (!ext_hub_dev->dynamic.flags.in_pending_list) {
|
|
// Move device form idle device list to callback device list
|
|
TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry);
|
|
TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry);
|
|
ext_hub_dev->dynamic.action_flags |= action_flags;
|
|
ext_hub_dev->dynamic.flags.in_pending_list = 1;
|
|
call_proc_req_cb = true;
|
|
} else {
|
|
// The device is already on the callback list, thus a processing request is already pending.
|
|
ext_hub_dev->dynamic.action_flags |= action_flags;
|
|
call_proc_req_cb = false;
|
|
}
|
|
return call_proc_req_cb;
|
|
}
|
|
|
|
static esp_err_t device_enable_int_ep(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Enable EP IN", ext_hub_dev->constant.dev_addr);
|
|
esp_err_t ret = usbh_ep_enqueue_urb(ext_hub_dev->constant.ep_in_hdl, ext_hub_dev->constant.in_urb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Failed to submit in urb: %s", esp_err_to_name(ret));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void device_has_changed(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
// TODO: IDF-10053 Hub status change handling
|
|
// After getting the IRQ about Hub status change we need to request status
|
|
// device_get_status(ext_hub_dev);
|
|
ESP_LOGW(EXT_HUB_TAG, "Hub status change has not been implemented yet");
|
|
device_enable_int_ep(ext_hub_dev);
|
|
}
|
|
|
|
//
|
|
// Figure 11-22. Hub and Port Status Change Bitmap
|
|
// Bit | N | ... | 3 | 2 | 1 | 0 |
|
|
// Value |1/0| ... |1/0|1/0|1/0|1/0|
|
|
// | | | | |
|
|
// | | | | +------ Hub change detected
|
|
// | | | +---------- Port 1 change detected
|
|
// | | +-------------- Port 2 change detected
|
|
// | +------------------ Port 3 change detected
|
|
// | ...
|
|
// +---------------------------- Port N change detected
|
|
//
|
|
static void device_status_change_handle(ext_hub_dev_t *ext_hub_dev, const uint8_t* data, const int length)
|
|
{
|
|
uint32_t device_status = 0;
|
|
// Driver does not support Hubs with EP IN wMaxPacketSize > 4
|
|
assert(length <= EXT_HUB_MAX_STATUS_BYTES_SIZE);
|
|
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
device_status |= (uint32_t)(data[i] << i);
|
|
}
|
|
|
|
if (device_status) {
|
|
// If device has a status change, we will re-trigger back the transfer
|
|
// after all handling will be done
|
|
if (device_status & EXT_HUB_STATUS_CHANGE_FLAG) {
|
|
// Check device status
|
|
device_has_changed(ext_hub_dev);
|
|
}
|
|
// HINTs:
|
|
// - every byte of Data IN has 8 bits of possible port statuses bits: (length * 8)
|
|
// - very first bit of status is used for Hub status: (length * 8) - 1
|
|
for (uint8_t i = 0; i < (length * 8) - 1; i++) {
|
|
// Check ports statuses
|
|
if (device_status & (EXT_HUB_STATUS_PORT1_CHANGE_FLAG << i)) {
|
|
assert(i < ext_hub_dev->single_thread.maxchild); // Port should be in range
|
|
assert(p_ext_hub_driver->constant.port_driver); // Port driver call should be valid
|
|
// Request Port status to handle changes
|
|
p_ext_hub_driver->constant.port_driver->get_status(ext_hub_dev->constant.ports[i]);
|
|
}
|
|
}
|
|
} else {
|
|
// Device status is 0. Could happen when HS Hub with HS device is connected to HS Hub, connected to FS root port
|
|
// Re-trigger transfer right now
|
|
device_enable_int_ep(ext_hub_dev);
|
|
}
|
|
}
|
|
|
|
static void device_error(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
bool call_proc_req_cb = false;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_ERROR);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
}
|
|
|
|
static esp_err_t device_port_new(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx)
|
|
{
|
|
assert(p_ext_hub_driver->constant.port_driver);
|
|
esp_err_t ret = p_ext_hub_driver->constant.port_driver->new (NULL, (void**) &ext_hub_dev->constant.ports[port_idx]);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port allocation error: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret));
|
|
goto fail;
|
|
}
|
|
|
|
ext_hub_dev->single_thread.maxchild++;
|
|
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static esp_err_t device_port_free(ext_hub_dev_t *ext_hub_dev, uint8_t port_idx)
|
|
{
|
|
EXT_HUB_CHECK(ext_hub_dev->constant.ports[port_idx] != NULL, ESP_ERR_INVALID_STATE);
|
|
|
|
bool call_proc_req_cb = false;
|
|
bool all_ports_freed = false;
|
|
|
|
assert(ext_hub_dev->single_thread.maxchild != 0);
|
|
assert(p_ext_hub_driver->constant.port_driver);
|
|
esp_err_t ret = p_ext_hub_driver->constant.port_driver->free(ext_hub_dev->constant.ports[port_idx]);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to free port: %s", ext_hub_dev->constant.dev_addr, port_idx + 1, esp_err_to_name(ret));
|
|
goto exit;
|
|
}
|
|
|
|
ext_hub_dev->constant.ports[port_idx] = NULL;
|
|
ext_hub_dev->single_thread.maxchild--;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
if (ext_hub_dev->dynamic.flags.is_gone) {
|
|
all_ports_freed = (ext_hub_dev->single_thread.maxchild == 0);
|
|
|
|
if (all_ports_freed) {
|
|
ext_hub_dev->dynamic.flags.waiting_children = 0;
|
|
ext_hub_dev->dynamic.flags.waiting_free = 1;
|
|
call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE);
|
|
} else {
|
|
ext_hub_dev->dynamic.flags.waiting_children = 1;
|
|
}
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
// Close the device if all children were freed
|
|
if (all_ports_freed) {
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Release USBH device object", ext_hub_dev->constant.dev_addr);
|
|
ESP_ERROR_CHECK(usbh_dev_close(ext_hub_dev->constant.dev_hdl));
|
|
}
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
ret = ESP_OK;
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static void device_release(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
esp_err_t ret;
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Device release", ext_hub_dev->constant.dev_addr);
|
|
|
|
// Release IN EP
|
|
ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT));
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
ext_hub_dev->dynamic.flags.is_gone = 1;
|
|
ext_hub_dev->dynamic.flags.waiting_release = 0;
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (ext_hub_dev->single_thread.state >= EXT_HUB_STATE_CONFIGURED) {
|
|
// Hub device was configured and has a descriptor
|
|
assert(ext_hub_dev->constant.hub_desc != NULL);
|
|
assert(p_ext_hub_driver->constant.port_driver);
|
|
// Mark all ports as gone
|
|
for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) {
|
|
if (ext_hub_dev->constant.ports[i]) {
|
|
ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[i]);
|
|
if (ret == ESP_OK) {
|
|
// Port doesn't have a device and can be recycled right now
|
|
ret = device_port_free(ext_hub_dev, i);
|
|
if (ret != ESP_OK) {
|
|
// Hub runs into an error state
|
|
// TODO: IDF-10057 Hub handling error
|
|
}
|
|
} else if (ret == ESP_ERR_NOT_FINISHED) {
|
|
// Port has a device and will be recycled after USBH device will be released by all clients and freed
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone", ext_hub_dev->constant.dev_addr, i + 1);
|
|
} else {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s",
|
|
ext_hub_dev->constant.dev_addr, i + 1, esp_err_to_name(ret));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static esp_err_t device_alloc_desc(ext_hub_dev_t *ext_hub_hdl, const usb_hub_descriptor_t *hub_desc)
|
|
{
|
|
// Allocate memory to store the configuration descriptor
|
|
usb_hub_descriptor_t *desc = heap_caps_malloc(hub_desc->bDescLength, MALLOC_CAP_DEFAULT); // Buffer to copy over full configuration descriptor (wTotalLength)
|
|
if (desc == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
// Copy the hub descriptor
|
|
memcpy(desc, hub_desc, hub_desc->bDescLength);
|
|
// Assign the hub descriptor to the device object
|
|
assert(ext_hub_hdl->constant.hub_desc == NULL);
|
|
ext_hub_hdl->constant.hub_desc = desc;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t device_alloc(device_config_t *config, ext_hub_dev_t **ext_hub_dev)
|
|
{
|
|
esp_err_t ret;
|
|
urb_t *ctrl_urb = NULL;
|
|
urb_t *in_urb = NULL;
|
|
|
|
#if !ENABLE_MULTIPLE_HUBS
|
|
usb_device_info_t dev_info;
|
|
ESP_ERROR_CHECK(usbh_dev_get_info(config->dev_hdl, &dev_info));
|
|
if (dev_info.parent.dev_hdl) {
|
|
ESP_LOGW(EXT_HUB_TAG, "Multiple Hubs not supported, use menuconfig to enable feature");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
goto fail;
|
|
}
|
|
#endif // ENABLE_MULTIPLE_HUBS
|
|
|
|
ext_hub_dev_t *hub_dev = heap_caps_calloc(1, sizeof(ext_hub_dev_t), MALLOC_CAP_DEFAULT);
|
|
|
|
if (hub_dev == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Unable to allocate device");
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto fail;
|
|
}
|
|
|
|
// Allocate Control transfer URB
|
|
ctrl_urb = urb_alloc(sizeof(usb_setup_packet_t) + EXT_HUB_CTRL_TRANSFER_MAX_DATA_LEN, 0);
|
|
if (ctrl_urb == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Control URB");
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto ctrl_urb_fail;
|
|
}
|
|
|
|
if (config->ep_in_desc->wMaxPacketSize > EXT_HUB_MAX_STATUS_BYTES_SIZE) {
|
|
ESP_LOGE(EXT_HUB_TAG, "wMaxPacketSize=%d is not supported", config->ep_in_desc->wMaxPacketSize);
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
goto in_urb_fail;
|
|
}
|
|
|
|
in_urb = urb_alloc(config->ep_in_desc->wMaxPacketSize, 0);
|
|
// Allocate Interrupt transfer URB
|
|
if (in_urb == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Unable to allocate Interrupt URB");
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto in_urb_fail;
|
|
}
|
|
|
|
usbh_ep_handle_t ep_hdl;
|
|
usbh_ep_config_t ep_config = {
|
|
.bInterfaceNumber = config->iface_desc->bInterfaceNumber,
|
|
.bAlternateSetting = config->iface_desc->bAlternateSetting,
|
|
.bEndpointAddress = config->ep_in_desc->bEndpointAddress,
|
|
.ep_cb = interrupt_pipe_cb,
|
|
.ep_cb_arg = (void *)hub_dev,
|
|
.context = (void *)hub_dev,
|
|
};
|
|
|
|
ret = usbh_ep_alloc(config->dev_hdl, &ep_config, &ep_hdl);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Endpoint allocation failure: %s", esp_err_to_name(ret));
|
|
goto ep_fail;
|
|
}
|
|
// Configure Control transfer URB
|
|
ctrl_urb->usb_host_client = (void *) p_ext_hub_driver;
|
|
ctrl_urb->transfer.callback = control_transfer_complete_cb;
|
|
ctrl_urb->transfer.context = (void *) hub_dev;
|
|
|
|
// Client is a memory address of the p_ext_hub_driver driver object
|
|
in_urb->usb_host_client = (void *) p_ext_hub_driver;
|
|
in_urb->transfer.callback = interrupt_transfer_complete_cb;
|
|
in_urb->transfer.context = (void *) hub_dev;
|
|
in_urb->transfer.num_bytes = config->ep_in_desc->wMaxPacketSize;
|
|
|
|
hub_dev->dynamic.flags.val = 0;
|
|
hub_dev->dynamic.action_flags = 0;
|
|
// Mutex protected
|
|
hub_dev->single_thread.state = EXT_HUB_STATE_ATTACHED;
|
|
// Constant members
|
|
hub_dev->constant.ep_in_hdl = ep_hdl;
|
|
hub_dev->constant.ctrl_urb = ctrl_urb;
|
|
hub_dev->constant.in_urb = in_urb;
|
|
hub_dev->constant.dev_hdl = config->dev_hdl;
|
|
hub_dev->constant.dev_addr = config->dev_addr;
|
|
hub_dev->constant.iface_num = config->iface_desc->bInterfaceNumber;
|
|
// Single thread members
|
|
hub_dev->single_thread.stage = EXT_HUB_STAGE_IDLE;
|
|
// We will update number of ports during Hub Descriptor handling stage
|
|
hub_dev->single_thread.maxchild = 0;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, hub_dev, dynamic.tailq_entry);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] New device (iface %d)", config->dev_addr, hub_dev->constant.iface_num);
|
|
|
|
*ext_hub_dev = hub_dev;
|
|
return ret;
|
|
|
|
ep_fail:
|
|
urb_free(in_urb);
|
|
in_urb_fail:
|
|
urb_free(ctrl_urb);
|
|
ctrl_urb_fail:
|
|
heap_caps_free(hub_dev);
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static esp_err_t device_configure(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
esp_err_t ret;
|
|
EXT_HUB_CHECK(ext_hub_dev->constant.hub_desc != NULL, ESP_ERR_INVALID_STATE);
|
|
usb_hub_descriptor_t *hub_desc = ext_hub_dev->constant.hub_desc;
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Device configure (iface %d)",
|
|
ext_hub_dev->constant.dev_addr,
|
|
ext_hub_dev->constant.iface_num);
|
|
|
|
if (hub_desc->wHubCharacteristics.compound) {
|
|
ESP_LOGD(EXT_HUB_TAG, "\tCompound device");
|
|
} else {
|
|
ESP_LOGD(EXT_HUB_TAG, "\tStandalone HUB");
|
|
}
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "\t%d external port%s",
|
|
ext_hub_dev->constant.hub_desc->bNbrPorts,
|
|
(ext_hub_dev->constant.hub_desc->bNbrPorts == 1) ? "" : "s");
|
|
|
|
switch (hub_desc->wHubCharacteristics.power_switching) {
|
|
case USB_W_HUB_CHARS_PORT_PWR_CTRL_NO:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tNo power switching (usb 1.0)");
|
|
break;
|
|
case USB_W_HUB_CHARS_PORT_PWR_CTRL_INDV:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tIndividual port power switching");
|
|
break;
|
|
default:
|
|
// USB_W_HUB_CHARS_PORT_PWR_CTRL_ALL
|
|
ESP_LOGD(EXT_HUB_TAG, "\tAll ports power at once");
|
|
break;
|
|
}
|
|
|
|
switch (hub_desc->wHubCharacteristics.ovr_current_protect) {
|
|
case USB_W_HUB_CHARS_PORT_OVER_CURR_NO:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tNo over-current protection");
|
|
break;
|
|
case USB_W_HUB_CHARS_PORT_OVER_CURR_INDV:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tIndividual port over-current protection");
|
|
break;
|
|
default:
|
|
// USB_W_HUB_CHARS_PORT_OVER_CURR_ALL
|
|
ESP_LOGD(EXT_HUB_TAG, "\tGlobal over-current protection");
|
|
break;
|
|
}
|
|
|
|
if (hub_desc->wHubCharacteristics.indicator_support) {
|
|
ESP_LOGD(EXT_HUB_TAG, "\tPort indicators are supported");
|
|
}
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "\tPower on to power good time: %dms", hub_desc->bPwrOn2PwrGood * 2);
|
|
ESP_LOGD(EXT_HUB_TAG, "\tMaximum current: %d mA", hub_desc->bHubContrCurrent);
|
|
|
|
// Create External Port flexible array
|
|
ext_hub_dev->constant.ports = heap_caps_calloc(ext_hub_dev->constant.hub_desc->bNbrPorts, sizeof(ext_port_hdl_t), MALLOC_CAP_DEFAULT);
|
|
if (ext_hub_dev->constant.ports == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Ports list allocation error");
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto fail;
|
|
}
|
|
|
|
// Create port and add it to pending list
|
|
for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) {
|
|
ext_hub_dev->constant.ports[i] = NULL;
|
|
ret = device_port_new(ext_hub_dev, i);
|
|
if (ret != ESP_OK) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
ext_hub_dev->single_thread.state = EXT_HUB_STATE_CONFIGURED;
|
|
return ESP_OK;
|
|
|
|
fail:
|
|
// Free allocated resources
|
|
for (uint8_t i = 0; i < ext_hub_dev->constant.hub_desc->bNbrPorts; i++) {
|
|
if (ext_hub_dev->constant.ports[i]) {
|
|
device_port_free(ext_hub_dev, i);
|
|
}
|
|
}
|
|
|
|
if (ext_hub_dev->constant.ports) {
|
|
assert(ext_hub_dev->single_thread.maxchild == 0);
|
|
heap_caps_free(ext_hub_dev->constant.ports);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void device_free(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
assert(ext_hub_dev->single_thread.maxchild == 0); // All ports should be freed by now
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Freeing device", ext_hub_dev->constant.dev_addr);
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
assert(ext_hub_dev->dynamic.flags.waiting_free == 1); // Hub should await being freed
|
|
ext_hub_dev->dynamic.flags.waiting_free = 0;
|
|
TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (ext_hub_dev->constant.hub_desc) {
|
|
heap_caps_free(ext_hub_dev->constant.hub_desc);
|
|
}
|
|
if (ext_hub_dev->constant.ports) {
|
|
heap_caps_free(ext_hub_dev->constant.ports);
|
|
}
|
|
|
|
ESP_ERROR_CHECK(usbh_ep_free(ext_hub_dev->constant.ep_in_hdl));
|
|
urb_free(ext_hub_dev->constant.ctrl_urb);
|
|
urb_free(ext_hub_dev->constant.in_urb);
|
|
|
|
heap_caps_free(ext_hub_dev);
|
|
}
|
|
|
|
static esp_err_t get_dev_by_hdl(usb_device_handle_t dev_hdl, ext_hub_dev_t **ext_hub_hdl)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
// Go through the Hubs lists to find the hub with the specified device address
|
|
ext_hub_dev_t *found_hub = NULL;
|
|
ext_hub_dev_t *hub = NULL;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) {
|
|
if (hub->constant.dev_hdl == dev_hdl) {
|
|
found_hub = hub;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) {
|
|
if (hub->constant.dev_hdl == dev_hdl) {
|
|
found_hub = hub;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
if (found_hub == NULL) {
|
|
ret = ESP_ERR_NOT_FOUND;
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
*ext_hub_hdl = found_hub;
|
|
return ret;
|
|
}
|
|
|
|
static esp_err_t get_dev_by_addr(uint8_t dev_addr, ext_hub_dev_t **ext_hub_hdl)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
// Go through the Hubs lists to find the Hub with the specified device address
|
|
ext_hub_dev_t *found_hub = NULL;
|
|
ext_hub_dev_t *hub = NULL;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, dynamic.tailq_entry) {
|
|
if (hub->constant.dev_addr == dev_addr) {
|
|
found_hub = hub;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
TAILQ_FOREACH(hub, &p_ext_hub_driver->dynamic.ext_hubs_tailq, dynamic.tailq_entry) {
|
|
if (hub->constant.dev_addr == dev_addr) {
|
|
found_hub = hub;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
if (found_hub == NULL) {
|
|
ret = ESP_ERR_NOT_FOUND;
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
*ext_hub_hdl = found_hub;
|
|
return ret;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// -------------------------- Device handling ---------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
static bool handle_hub_descriptor(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
esp_err_t ret;
|
|
bool pass;
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
const usb_hub_descriptor_t *hub_desc = (const usb_hub_descriptor_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t));
|
|
|
|
if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Bad transfer status %d: stage=%d", ctrl_xfer->status, ext_hub_dev->single_thread.stage);
|
|
return false;
|
|
}
|
|
|
|
ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE);
|
|
|
|
ret = device_alloc_desc(ext_hub_dev, hub_desc);
|
|
if (ret != ESP_OK) {
|
|
pass = false;
|
|
goto exit;
|
|
}
|
|
|
|
ret = device_configure(ext_hub_dev);
|
|
if (ret != ESP_OK) {
|
|
pass = false;
|
|
goto exit;
|
|
}
|
|
|
|
pass = true;
|
|
exit:
|
|
return pass;
|
|
}
|
|
|
|
static bool handle_device_status(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
const usb_device_status_t *dev_status = (const usb_device_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t));
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Device status: ", ext_hub_dev->constant.dev_addr);
|
|
ESP_LOGD(EXT_HUB_TAG, "\tPower: %s", dev_status->self_powered ? "self-powered" : "bus-powered");
|
|
ESP_LOGD(EXT_HUB_TAG, "\tRemoteWakeup: %s", dev_status->remote_wakeup ? "yes" : "no");
|
|
|
|
if (dev_status->remote_wakeup) {
|
|
// Device in remote_wakeup, we need send command Clear Device Feature: USB_W_VALUE_FEATURE_DEVICE_REMOTE_WAKEUP
|
|
// HEX codes of command: 00 01 01 00 00 00 00 00
|
|
// TODO: IDF-10055 Hub Support remote_wakeup feature
|
|
ESP_LOGW(EXT_HUB_TAG, "Remote Wakeup feature has not been implemented yet");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool handle_hub_status(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
const usb_hub_status_t *hub_status = (const usb_hub_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t));
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Hub status: ", ext_hub_dev->constant.dev_addr);
|
|
ESP_LOGD(EXT_HUB_TAG, "\tExternal power supply: %s", hub_status->wHubStatus.HUB_LOCAL_POWER ? "yes" : "no");
|
|
ESP_LOGD(EXT_HUB_TAG, "\tOvercurrent: %s", hub_status->wHubStatus.HUB_OVER_CURRENT ? "yes" : "no");
|
|
|
|
if (hub_status->wHubStatus.HUB_OVER_CURRENT) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Device has overcurrent!");
|
|
// Hub has an overcurrent, we need to disable all port and/or disable parent port
|
|
// TODO: IDF-10056 Hubs overcurrent handling
|
|
ESP_LOGW(EXT_HUB_TAG, "Feature has not been implemented yet");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void handle_port_feature(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer);
|
|
uint8_t port_idx = port_num - 1;
|
|
|
|
assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts);
|
|
assert(p_ext_hub_driver->constant.port_driver);
|
|
p_ext_hub_driver->constant.port_driver->req_process(ext_hub_dev->constant.ports[port_idx]);
|
|
}
|
|
|
|
static void handle_port_status(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
uint8_t port_num = USB_SETUP_PACKET_GET_PORT((usb_setup_packet_t *)ctrl_xfer->data_buffer);
|
|
uint8_t port_idx = port_num - 1;
|
|
const usb_port_status_t *new_status = (const usb_port_status_t *)(ctrl_xfer->data_buffer + sizeof(usb_setup_packet_t));
|
|
|
|
assert(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts);
|
|
assert(p_ext_hub_driver->constant.port_driver);
|
|
p_ext_hub_driver->constant.port_driver->set_status(ext_hub_dev->constant.ports[port_idx], new_status);
|
|
}
|
|
|
|
static bool device_control_request(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
esp_err_t ret;
|
|
usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
switch (ext_hub_dev->single_thread.stage) {
|
|
case EXT_HUB_STAGE_GET_DEVICE_STATUS:
|
|
USB_SETUP_PACKET_INIT_GET_STATUS((usb_setup_packet_t *)transfer->data_buffer);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_device_status_t);
|
|
break;
|
|
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR:
|
|
USB_SETUP_PACKET_INIT_GET_HUB_DESCRIPTOR((usb_setup_packet_t *)transfer->data_buffer);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_descriptor_t);
|
|
break;
|
|
case EXT_HUB_STAGE_GET_HUB_STATUS:
|
|
USB_SETUP_PACKET_INIT_GET_HUB_STATUS((usb_setup_packet_t *)transfer->data_buffer);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_hub_status_t);
|
|
break;
|
|
default:
|
|
// Should never occur
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Failed to submit ctrl urb: %s", esp_err_to_name(ret));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool device_control_response_handling(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
bool stage_pass = false;
|
|
usb_transfer_t *ctrl_xfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
// Check transfer status
|
|
if (ctrl_xfer->status != USB_TRANSFER_STATUS_COMPLETED) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Control request bad transfer status %d",
|
|
ctrl_xfer->status);
|
|
return stage_pass;
|
|
}
|
|
|
|
// Transfer completed, verbose data in ESP_LOG_VERBOSE is set
|
|
ESP_LOG_BUFFER_HEXDUMP(EXT_HUB_TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_VERBOSE);
|
|
|
|
switch (ext_hub_dev->single_thread.stage) {
|
|
case EXT_HUB_STAGE_CHECK_DEVICE_STATUS:
|
|
stage_pass = handle_device_status(ext_hub_dev);
|
|
break;
|
|
case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR:
|
|
stage_pass = handle_hub_descriptor(ext_hub_dev);
|
|
break;
|
|
case EXT_HUB_STAGE_CHECK_HUB_STATUS:
|
|
stage_pass = handle_hub_status(ext_hub_dev);
|
|
break;
|
|
case EXT_HUB_STAGE_PORT_FEATURE:
|
|
handle_port_feature(ext_hub_dev);
|
|
stage_pass = true;
|
|
break;
|
|
case EXT_HUB_STAGE_PORT_STATUS_REQUEST:
|
|
handle_port_status(ext_hub_dev);
|
|
stage_pass = true;
|
|
break;
|
|
default:
|
|
// Should never occur
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
return stage_pass;
|
|
}
|
|
|
|
static bool stage_need_process(ext_hub_stage_t stage)
|
|
{
|
|
bool need_process_cb = false;
|
|
|
|
switch (stage) {
|
|
// Stages, required control transfer
|
|
case EXT_HUB_STAGE_GET_DEVICE_STATUS:
|
|
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR:
|
|
case EXT_HUB_STAGE_GET_HUB_STATUS:
|
|
// Error stage
|
|
case EXT_HUB_STAGE_FAILURE:
|
|
need_process_cb = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return need_process_cb;
|
|
}
|
|
|
|
// return
|
|
// true - next stage requires the processing
|
|
// false - terminal stage
|
|
static bool device_set_next_stage(ext_hub_dev_t *ext_hub_dev, bool last_stage_pass)
|
|
{
|
|
ext_hub_stage_t last_stage = ext_hub_dev->single_thread.stage;
|
|
ext_hub_stage_t next_stage;
|
|
|
|
if (last_stage_pass) {
|
|
ESP_LOGD(EXT_HUB_TAG, "Stage %s OK", ext_hub_stage_strings[last_stage]);
|
|
if (last_stage == EXT_HUB_STAGE_GET_DEVICE_STATUS ||
|
|
last_stage == EXT_HUB_STAGE_GET_HUB_DESCRIPTOR ||
|
|
last_stage == EXT_HUB_STAGE_GET_HUB_STATUS) {
|
|
// Simply increment to get the next stage
|
|
next_stage = last_stage + 1;
|
|
} else {
|
|
// Terminal stages, move to IDLE
|
|
next_stage = EXT_HUB_STAGE_IDLE;
|
|
}
|
|
} else {
|
|
ESP_LOGE(EXT_HUB_TAG, "Stage %s FAILED", ext_hub_stage_strings[last_stage]);
|
|
// These stages cannot fail
|
|
assert(last_stage != EXT_HUB_STAGE_PORT_FEATURE ||
|
|
last_stage != EXT_HUB_STAGE_PORT_STATUS_REQUEST);
|
|
|
|
next_stage = EXT_HUB_STAGE_FAILURE;
|
|
}
|
|
|
|
ext_hub_dev->single_thread.stage = next_stage;
|
|
return stage_need_process(next_stage);
|
|
}
|
|
|
|
static void handle_error(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
ext_hub_dev->single_thread.state = EXT_HUB_STATE_FAILED;
|
|
// TODO: IDF-10057 Hub handling error
|
|
ESP_LOGW(EXT_HUB_TAG, "%s has not been implemented yet", __FUNCTION__);
|
|
}
|
|
|
|
static void handle_device(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
bool call_proc_req_cb;
|
|
bool stage_pass = false;
|
|
// FSM for external Hub
|
|
switch (ext_hub_dev->single_thread.stage) {
|
|
case EXT_HUB_STAGE_IDLE:
|
|
break;
|
|
case EXT_HUB_STAGE_GET_DEVICE_STATUS:
|
|
case EXT_HUB_STAGE_GET_HUB_DESCRIPTOR:
|
|
case EXT_HUB_STAGE_GET_HUB_STATUS:
|
|
stage_pass = device_control_request(ext_hub_dev);
|
|
break;
|
|
case EXT_HUB_STAGE_CHECK_DEVICE_STATUS:
|
|
case EXT_HUB_STAGE_CHECK_HUB_DESCRIPTOR:
|
|
case EXT_HUB_STAGE_CHECK_HUB_STATUS:
|
|
case EXT_HUB_STAGE_PORT_FEATURE:
|
|
case EXT_HUB_STAGE_PORT_STATUS_REQUEST:
|
|
stage_pass = device_control_response_handling(ext_hub_dev);
|
|
break;
|
|
case EXT_HUB_STAGE_FAILURE:
|
|
handle_error(ext_hub_dev);
|
|
stage_pass = true;
|
|
break;
|
|
default:
|
|
// Should never occur
|
|
abort();
|
|
break;
|
|
}
|
|
|
|
call_proc_req_cb = device_set_next_stage(ext_hub_dev, stage_pass);
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
}
|
|
|
|
static void handle_ep1_flush(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_HALT));
|
|
ESP_ERROR_CHECK(usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_FLUSH));
|
|
}
|
|
|
|
static void handle_ep1_dequeue(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
// Dequeue all URBs and run their transfer callback
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Interrupt dequeue", ext_hub_dev->constant.dev_addr);
|
|
|
|
urb_t *urb;
|
|
usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_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);
|
|
usbh_ep_dequeue_urb(ext_hub_dev->constant.ep_in_hdl, &urb);
|
|
}
|
|
}
|
|
|
|
static void handle_ep1_clear(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
// We allow the pipe command to fail just in case the pipe becomes invalid mid command
|
|
usbh_ep_command(ext_hub_dev->constant.ep_in_hdl, USBH_EP_CMD_CLEAR);
|
|
}
|
|
|
|
static void handle_gone(ext_hub_dev_t *ext_hub_dev)
|
|
{
|
|
bool call_proc_req_cb = false;
|
|
|
|
// Set the flags
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
ext_hub_dev->dynamic.flags.waiting_free = 1;
|
|
call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_FREE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ------------------------------ Driver ---------------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
esp_err_t ext_hub_install(const ext_hub_config_t *config)
|
|
{
|
|
esp_err_t ret;
|
|
ext_hub_driver_t *ext_hub_drv = heap_caps_calloc(1, sizeof(ext_hub_driver_t), MALLOC_CAP_DEFAULT);
|
|
SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex();
|
|
if (ext_hub_drv == NULL || mux_lock == NULL) {
|
|
ret = ESP_ERR_NO_MEM;
|
|
goto err;
|
|
}
|
|
// Save callbacks
|
|
ext_hub_drv->constant.proc_req_cb = config->proc_req_cb;
|
|
ext_hub_drv->constant.proc_req_cb_arg = config->proc_req_cb_arg;
|
|
// Copy Port driver pointer
|
|
ext_hub_drv->constant.port_driver = config->port_driver;
|
|
|
|
if (ext_hub_drv->constant.port_driver == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Port Driver has not been implemented yet");
|
|
ret = ESP_ERR_NOT_FINISHED;
|
|
goto err;
|
|
}
|
|
|
|
TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_tailq);
|
|
TAILQ_INIT(&ext_hub_drv->dynamic.ext_hubs_pending_tailq);
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
if (p_ext_hub_driver != NULL) {
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
ret = ESP_ERR_INVALID_STATE;
|
|
goto err;
|
|
}
|
|
p_ext_hub_driver = ext_hub_drv;
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "Driver installed");
|
|
return ESP_OK;
|
|
|
|
err:
|
|
if (mux_lock != NULL) {
|
|
vSemaphoreDelete(mux_lock);
|
|
}
|
|
heap_caps_free(ext_hub_drv);
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_uninstall(void)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_tailq), ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_CHECK_FROM_CRIT(TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq), ESP_ERR_INVALID_STATE);
|
|
ext_hub_driver_t *ext_hub_drv = p_ext_hub_driver;
|
|
p_ext_hub_driver = NULL;
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
// Free resources
|
|
heap_caps_free(ext_hub_drv);
|
|
ESP_LOGD(EXT_HUB_TAG, "Driver uninstalled");
|
|
return ESP_OK;
|
|
}
|
|
|
|
void *ext_hub_get_client(void)
|
|
{
|
|
bool driver_installed = false;
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
driver_installed = (p_ext_hub_driver != NULL);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
return (driver_installed) ? (void*) p_ext_hub_driver : NULL;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// -------------------------- External Hub API ---------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
esp_err_t ext_hub_get_handle(usb_device_handle_t dev_hdl, ext_hub_handle_t *ext_hub_hdl)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
return get_dev_by_hdl(dev_hdl, ext_hub_hdl);
|
|
}
|
|
|
|
static esp_err_t find_first_intf_desc(const usb_config_desc_t *config_desc, device_config_t *hub_config)
|
|
{
|
|
bool iface_found = false;
|
|
const usb_ep_desc_t *ep_in_desc = NULL;
|
|
|
|
int offset = 0;
|
|
const usb_intf_desc_t *next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type(
|
|
(const usb_standard_desc_t *)config_desc,
|
|
config_desc->wTotalLength,
|
|
USB_B_DESCRIPTOR_TYPE_INTERFACE,
|
|
&offset);
|
|
|
|
while (next_intf_desc != NULL) {
|
|
if (iface_found) {
|
|
// TODO: IDF-10058 Hubs support interface selection (HS)
|
|
ESP_LOGW(EXT_HUB_TAG, "Device has several Interfaces, selection has not been implemented yet. Using first.");
|
|
break;
|
|
}
|
|
// Parse all interfaces
|
|
if (USB_CLASS_HUB == next_intf_desc->bInterfaceClass) {
|
|
// We have found the first interface descriptor with matching bInterfaceNumber
|
|
|
|
if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_FS) {
|
|
// TODO: IDF-10059 Hubs support multiple TT (HS)
|
|
if (next_intf_desc->bInterfaceProtocol != USB_B_DEV_PROTOCOL_HUB_HS_NO_TT) {
|
|
ESP_LOGW(EXT_HUB_TAG, "Transaction Translator has not been implemented yet");
|
|
}
|
|
|
|
switch (next_intf_desc->bInterfaceProtocol) {
|
|
case USB_B_DEV_PROTOCOL_HUB_HS_NO_TT:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tNo TT");
|
|
break;
|
|
case USB_B_DEV_PROTOCOL_HUB_HS_SINGLE_TT:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tSingle TT");
|
|
break;
|
|
case USB_B_DEV_PROTOCOL_HUB_HS_MULTI_TT:
|
|
ESP_LOGD(EXT_HUB_TAG, "\tMulti TT");
|
|
break;
|
|
default:
|
|
ESP_LOGE(EXT_HUB_TAG, "\tInterface Protocol (%#x) not supported", next_intf_desc->bInterfaceProtocol);
|
|
goto next_iface;
|
|
}
|
|
}
|
|
|
|
// Hub Interface always should have only one Interrupt endpoint
|
|
if (next_intf_desc->bNumEndpoints != 1) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Unexpected number of endpoints (%d)", next_intf_desc->bNumEndpoints);
|
|
goto next_iface;
|
|
}
|
|
// Get related IN EP
|
|
ep_in_desc = usb_parse_endpoint_descriptor_by_index(next_intf_desc, 0, config_desc->wTotalLength, &offset);
|
|
if (ep_in_desc == NULL) {
|
|
ESP_LOGE(EXT_HUB_TAG, "EP descriptor not found (iface=%d)", next_intf_desc->bInterfaceNumber);
|
|
goto next_iface;
|
|
}
|
|
|
|
if (!USB_EP_DESC_GET_EP_DIR(ep_in_desc) ||
|
|
(USB_EP_DESC_GET_XFERTYPE(ep_in_desc) != USB_TRANSFER_TYPE_INTR)) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Interrupt EP not found (iface=%d)", next_intf_desc->bInterfaceNumber);
|
|
goto next_iface;
|
|
}
|
|
|
|
// Interface found, fill the config
|
|
hub_config->iface_desc = next_intf_desc;
|
|
hub_config->ep_in_desc = ep_in_desc;
|
|
iface_found = true;
|
|
}
|
|
|
|
next_iface:
|
|
next_intf_desc = (const usb_intf_desc_t *)usb_parse_next_descriptor_of_type(
|
|
(const usb_standard_desc_t *)next_intf_desc,
|
|
config_desc->wTotalLength,
|
|
USB_B_DESCRIPTOR_TYPE_INTERFACE,
|
|
&offset);
|
|
}
|
|
|
|
return (iface_found) ? ESP_OK : ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
esp_err_t ext_hub_new_dev(uint8_t dev_addr)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
esp_err_t ret;
|
|
ext_hub_dev_t *hub_dev = NULL;
|
|
usb_device_handle_t dev_hdl = NULL;
|
|
const usb_config_desc_t *config_desc = NULL;
|
|
bool call_proc_req_cb = false;
|
|
|
|
// Open device
|
|
ret = usbh_devs_open(dev_addr, &dev_hdl);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "USBH device opening error: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
// Get Configuration Descriptor
|
|
ret = usbh_dev_get_config_desc(dev_hdl, &config_desc);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "Getting config desc error %s", esp_err_to_name(ret));
|
|
goto exit;
|
|
}
|
|
|
|
// Find related Hub Interface descriptor
|
|
device_config_t hub_config = {
|
|
.dev_hdl = dev_hdl,
|
|
.dev_addr = dev_addr,
|
|
.iface_desc = NULL,
|
|
.ep_in_desc = NULL,
|
|
};
|
|
|
|
ret = find_first_intf_desc(config_desc, &hub_config);
|
|
if (ret != ESP_OK) {
|
|
goto exit;
|
|
}
|
|
|
|
// Create External Hub device
|
|
ret = device_alloc(&hub_config, &hub_dev);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "External HUB device alloc error %s", esp_err_to_name(ret));
|
|
goto exit;
|
|
}
|
|
|
|
hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_HUB_DESCRIPTOR;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
call_proc_req_cb = _device_set_actions(hub_dev, DEV_ACTION_REQ);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
return ret;
|
|
|
|
exit:
|
|
ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_dev_gone(uint8_t dev_addr)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
esp_err_t ret;
|
|
|
|
ext_hub_dev_t *ext_hub_dev = NULL;
|
|
bool in_pending = false;
|
|
|
|
EXT_HUB_CHECK(dev_addr != 0, ESP_ERR_INVALID_ARG);
|
|
// Find device with dev_addr in the devices TAILQ
|
|
// TODO: IDF-10058
|
|
// Release all devices by dev_addr
|
|
ret = get_dev_by_addr(dev_addr, &ext_hub_dev);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGD(EXT_HUB_TAG, "No device with address %d was found", dev_addr);
|
|
goto exit;
|
|
}
|
|
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Device gone", ext_hub_dev->constant.dev_addr);
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
if (ext_hub_dev->dynamic.flags.waiting_release ||
|
|
ext_hub_dev->dynamic.flags.waiting_children) {
|
|
// External Hub was already released or waiting children to be freed
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
ret = ESP_ERR_INVALID_STATE;
|
|
goto exit;
|
|
}
|
|
if (ext_hub_dev->dynamic.flags.in_pending_list) {
|
|
in_pending = true;
|
|
// TODO: IDF-10490
|
|
// test case:
|
|
// - detach the external Hub device right before the Hub Descriptor request.
|
|
_device_set_actions(ext_hub_dev, DEV_ACTION_RELEASE);
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
// Device not in pending, can be released immediately
|
|
if (!in_pending) {
|
|
device_release(ext_hub_dev);
|
|
}
|
|
|
|
ret = ESP_OK;
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_all_free(void)
|
|
{
|
|
bool call_proc_req_cb = false;
|
|
bool wait_for_free = false;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
for (int i = 0; i < 2; i++) {
|
|
ext_hub_dev_t *hub_curr;
|
|
ext_hub_dev_t *hub_next;
|
|
// Go through pending list first
|
|
if (i == 0) {
|
|
hub_curr = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq);
|
|
} else {
|
|
hub_curr = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_tailq);
|
|
}
|
|
while (hub_curr != NULL) {
|
|
hub_next = TAILQ_NEXT(hub_curr, dynamic.tailq_entry);
|
|
hub_curr->dynamic.flags.waiting_release = 1;
|
|
call_proc_req_cb = _device_set_actions(hub_curr, DEV_ACTION_RELEASE);
|
|
// At least one hub should be released
|
|
wait_for_free = true;
|
|
hub_curr = hub_next;
|
|
}
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
return (wait_for_free) ? ESP_ERR_NOT_FINISHED : ESP_OK;
|
|
}
|
|
|
|
esp_err_t ext_hub_status_handle_complete(ext_hub_handle_t ext_hub_hdl)
|
|
{
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
|
|
EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE);
|
|
|
|
return device_enable_int_ep(ext_hub_dev);
|
|
}
|
|
|
|
esp_err_t ext_hub_process(void)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
// Keep processing until all device's with pending events have been handled
|
|
while (!TAILQ_EMPTY(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq)) {
|
|
// Move the device back into the idle device list,
|
|
ext_hub_dev_t *ext_hub_dev = TAILQ_FIRST(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq);
|
|
TAILQ_REMOVE(&p_ext_hub_driver->dynamic.ext_hubs_pending_tailq, ext_hub_dev, dynamic.tailq_entry);
|
|
TAILQ_INSERT_TAIL(&p_ext_hub_driver->dynamic.ext_hubs_tailq, ext_hub_dev, dynamic.tailq_entry);
|
|
// Clear the device's flags
|
|
uint32_t action_flags = ext_hub_dev->dynamic.action_flags;
|
|
ext_hub_dev->dynamic.action_flags = 0;
|
|
ext_hub_dev->dynamic.flags.in_pending_list = 0;
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Exit critical section to handle device action flags in their listed order
|
|
--------------------------------------------------------------------- */
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d] Processing actions 0x%"PRIx32"", ext_hub_dev->constant.dev_addr, action_flags);
|
|
|
|
if (action_flags & DEV_ACTION_REQ ||
|
|
action_flags & DEV_ACTION_EP0_COMPLETE) {
|
|
handle_device(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_EP1_FLUSH) {
|
|
handle_ep1_flush(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_EP1_DEQUEUE) {
|
|
handle_ep1_dequeue(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_EP1_CLEAR) {
|
|
handle_ep1_clear(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_ERROR) {
|
|
handle_error(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_GONE) {
|
|
handle_gone(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_RELEASE) {
|
|
device_release(ext_hub_dev);
|
|
}
|
|
if (action_flags & DEV_ACTION_FREE) {
|
|
device_free(ext_hub_dev);
|
|
}
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
/* ---------------------------------------------------------------------
|
|
Re-enter critical sections. All device action flags should have been handled.
|
|
--------------------------------------------------------------------- */
|
|
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --------------------- External Hub - Device related -------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
esp_err_t ext_hub_get_hub_status(ext_hub_handle_t ext_hub_hdl)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
|
|
ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_HUB_STATUS;
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ext_hub_get_status(ext_hub_handle_t ext_hub_hdl)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
|
|
ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_GET_DEVICE_STATUS;
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
bool call_proc_req_cb = _device_set_actions(ext_hub_dev, DEV_ACTION_REQ);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (call_proc_req_cb) {
|
|
p_ext_hub_driver->constant.proc_req_cb(false, p_ext_hub_driver->constant.proc_req_cb_arg);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --------------------- External Hub - Port related ---------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
esp_err_t ext_hub_port_recycle(ext_hub_handle_t ext_hub_hdl, uint8_t port_num)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
|
|
esp_err_t ret;
|
|
uint8_t port_idx = port_num - 1;
|
|
bool free_port = false;
|
|
bool release_port = false;
|
|
|
|
EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED);
|
|
EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE);
|
|
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
if (ext_hub_dev->dynamic.flags.waiting_release) {
|
|
release_port = true;
|
|
} else if (ext_hub_dev->dynamic.flags.waiting_children) {
|
|
assert(ext_hub_dev->dynamic.flags.waiting_release == 0); // Device should be already released
|
|
assert(ext_hub_dev->dynamic.flags.is_gone == 1); // Device should be gone by now
|
|
free_port = true;
|
|
}
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
if (!release_port && !free_port) {
|
|
// Parent still present, recycle the port
|
|
ret = p_ext_hub_driver->constant.port_driver->recycle(ext_hub_dev->constant.ports[port_idx]);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to recycle the port: %s", ext_hub_dev->constant.dev_addr, port_num, esp_err_to_name(ret));
|
|
goto exit;
|
|
}
|
|
} else {
|
|
if (release_port) {
|
|
// Notify the port that parent is not available anymore and port should be recycled then freed
|
|
ret = p_ext_hub_driver->constant.port_driver->gone(ext_hub_dev->constant.ports[port_idx]);
|
|
if (ret == ESP_OK) {
|
|
ESP_LOGD(EXT_HUB_TAG, "[%d:%d] Port doesn't have a device and can be freed right now",
|
|
ext_hub_dev->constant.dev_addr,
|
|
port_num);
|
|
assert(free_port == false);
|
|
free_port = true;
|
|
} else if (ret == ESP_ERR_NOT_FINISHED) {
|
|
// Port has a device and will be recycled after USBH device will be released by all clients and freed
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Port is gone",
|
|
ext_hub_dev->constant.dev_addr,
|
|
port_num);
|
|
// Logically, recycling logic are finished for the Hub Driver, return ESP_OK to free the node
|
|
assert(free_port == false);
|
|
} else {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Unable to mark port as gone: %s",
|
|
ext_hub_dev->constant.dev_addr, port_num, esp_err_to_name(ret));
|
|
}
|
|
}
|
|
|
|
if (free_port) {
|
|
ret = device_port_free(ext_hub_dev, port_idx);
|
|
if (ret != ESP_OK) {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = ESP_OK;
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_port_reset(ext_hub_handle_t ext_hub_hdl, uint8_t port_num)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
uint8_t port_idx = port_num - 1;
|
|
EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED);
|
|
|
|
return p_ext_hub_driver->constant.port_driver->reset(ext_hub_dev->constant.ports[port_idx]);
|
|
}
|
|
|
|
esp_err_t ext_hub_port_active(ext_hub_handle_t ext_hub_hdl, uint8_t port_num)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
uint8_t port_idx = port_num - 1;
|
|
EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED);
|
|
|
|
return p_ext_hub_driver->constant.port_driver->active(ext_hub_dev->constant.ports[port_idx]);
|
|
}
|
|
|
|
esp_err_t ext_hub_port_disable(ext_hub_handle_t ext_hub_hdl, uint8_t port_num)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
uint8_t port_idx = port_num - 1;
|
|
EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED);
|
|
|
|
return p_ext_hub_driver->constant.port_driver->disable(ext_hub_dev->constant.ports[port_idx]);
|
|
}
|
|
|
|
esp_err_t ext_hub_port_get_speed(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, usb_speed_t *speed)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
uint8_t port_idx = port_num - 1;
|
|
EXT_HUB_CHECK(port_idx < ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(p_ext_hub_driver->constant.port_driver != NULL, ESP_ERR_NOT_SUPPORTED);
|
|
|
|
return p_ext_hub_driver->constant.port_driver->get_speed(ext_hub_dev->constant.ports[port_idx], speed);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --------------------------- USB Chapter 11 ----------------------------------
|
|
// -----------------------------------------------------------------------------
|
|
|
|
esp_err_t ext_hub_set_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
esp_err_t ret;
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE);
|
|
|
|
USB_SETUP_PACKET_INIT_SET_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t);
|
|
|
|
ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE;
|
|
ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Set port feature %#x, failed to submit ctrl urb: %s",
|
|
ext_hub_dev->constant.dev_addr,
|
|
port_num,
|
|
feature,
|
|
esp_err_to_name(ret));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_clear_port_feature(ext_hub_handle_t ext_hub_hdl, uint8_t port_num, uint8_t feature)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
esp_err_t ret;
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE);
|
|
|
|
USB_SETUP_PACKET_INIT_CLEAR_PORT_FEATURE((usb_setup_packet_t *)transfer->data_buffer, port_num, feature);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t);
|
|
|
|
ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_FEATURE;
|
|
ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Clear port feature %#x, failed to submit ctrl urb: %s",
|
|
ext_hub_dev->constant.dev_addr,
|
|
port_num,
|
|
feature,
|
|
esp_err_to_name(ret));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t ext_hub_get_port_status(ext_hub_handle_t ext_hub_hdl, uint8_t port_num)
|
|
{
|
|
EXT_HUB_ENTER_CRITICAL();
|
|
EXT_HUB_CHECK_FROM_CRIT(p_ext_hub_driver != NULL, ESP_ERR_INVALID_STATE);
|
|
EXT_HUB_EXIT_CRITICAL();
|
|
|
|
esp_err_t ret;
|
|
EXT_HUB_CHECK(ext_hub_hdl != NULL, ESP_ERR_INVALID_ARG);
|
|
ext_hub_dev_t *ext_hub_dev = (ext_hub_dev_t *)ext_hub_hdl;
|
|
usb_transfer_t *transfer = &ext_hub_dev->constant.ctrl_urb->transfer;
|
|
|
|
EXT_HUB_CHECK(port_num != 0 && port_num <= ext_hub_dev->constant.hub_desc->bNbrPorts, ESP_ERR_INVALID_SIZE);
|
|
EXT_HUB_CHECK(ext_hub_dev->single_thread.state == EXT_HUB_STATE_CONFIGURED, ESP_ERR_INVALID_STATE);
|
|
|
|
USB_SETUP_PACKET_INIT_GET_PORT_STATUS((usb_setup_packet_t *)transfer->data_buffer, port_num);
|
|
transfer->num_bytes = sizeof(usb_setup_packet_t) + sizeof(usb_port_status_t);
|
|
|
|
ext_hub_dev->single_thread.stage = EXT_HUB_STAGE_PORT_STATUS_REQUEST;
|
|
ret = usbh_dev_submit_ctrl_urb(ext_hub_dev->constant.dev_hdl, ext_hub_dev->constant.ctrl_urb);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(EXT_HUB_TAG, "[%d:%d] Get port status, failed to submit ctrl urb: %s",
|
|
ext_hub_dev->constant.dev_addr,
|
|
port_num,
|
|
esp_err_to_name(ret));
|
|
}
|
|
return ret;
|
|
}
|