Merge branch 'refactor/usb_host_usbh_api' into 'master'

refactor(usb/host): Refactor USBH API

See merge request espressif/esp-idf!30029
This commit is contained in:
Darian
2024-04-24 14:50:25 +08:00
16 changed files with 1120 additions and 820 deletions

View File

@@ -205,7 +205,7 @@ typedef struct {
* - The peripheral must have been reset and clock un-gated
* - The USB PHY (internal or external) and associated GPIOs must already be configured
* - GPIO pins configured
* - Interrupt allocated but DISABLED (in case of an unknown interupt state)
* - Interrupt allocated but DISABLED (in case of an unknown interrupt state)
* Exit:
* - Checks to see if DWC_OTG is alive, and if HW version/config is correct
* - HAl context initialized
@@ -290,7 +290,7 @@ static inline void usb_dwc_hal_port_init(usb_dwc_hal_context_t *hal)
/**
* @brief Deinitialize the host port
*
* - Will disable the host port's interrupts preventing further port aand channel events from ocurring
* - Will disable the host port's interrupts preventing further port aand channel events from occurring
*
* @param hal Context of the HAL layer
*/
@@ -333,7 +333,6 @@ static inline void usb_dwc_hal_port_toggle_power(usb_dwc_hal_context_t *hal, boo
*/
static inline void usb_dwc_hal_port_toggle_reset(usb_dwc_hal_context_t *hal, bool enable)
{
HAL_ASSERT(hal->channels.num_allocd == 0); //Cannot reset if there are still allocated channels
usb_dwc_ll_hprt_set_port_reset(hal->dev, enable);
}
@@ -447,7 +446,7 @@ static inline void usb_dwc_hal_port_periodic_enable(usb_dwc_hal_context_t *hal)
/**
* @brief Disable periodic scheduling
*
* Disabling periodic scheduling will save a bit of DMA bandwith (as the controller will no longer fetch the schedule
* Disabling periodic scheduling will save a bit of DMA bandwidth (as the controller will no longer fetch the schedule
* from the frame list).
*
* @note Before disabling periodic scheduling, it is the user's responsibility to ensure that all periodic channels have
@@ -505,17 +504,17 @@ static inline usb_dwc_speed_t usb_dwc_hal_port_get_conn_speed(usb_dwc_hal_contex
* @brief Disable the debounce lock
*
* This function must be called after calling usb_dwc_hal_port_check_if_connected() and will allow connection/disconnection
* events to occur again. Any pending connection or disconenction interrupts are cleared.
* events to occur again. Any pending connection or disconnection interrupts are cleared.
*
* @param hal Context of the HAL layer
*/
static inline void usb_dwc_hal_disable_debounce_lock(usb_dwc_hal_context_t *hal)
{
hal->flags.dbnc_lock_enabled = 0;
//Clear Conenction and disconenction interrupt in case it triggered again
//Clear Connection and disconnection interrupt in case it triggered again
usb_dwc_ll_gintsts_clear_intrs(hal->dev, USB_DWC_LL_INTR_CORE_DISCONNINT);
usb_dwc_ll_hprt_intr_clear(hal->dev, USB_DWC_LL_INTR_HPRT_PRTCONNDET);
//Reenable the hprt (connection) and disconnection interrupts
//Re-enable the hprt (connection) and disconnection interrupts
usb_dwc_ll_gintmsk_en_intrs(hal->dev, USB_DWC_LL_INTR_CORE_PRTINT | USB_DWC_LL_INTR_CORE_DISCONNINT);
}
@@ -672,10 +671,10 @@ bool usb_dwc_hal_chan_request_halt(usb_dwc_hal_chan_t *chan_obj);
/**
* @brief Indicate that a channel is halted after a port error
*
* When a port error occurs (e.g., discconect, overcurrent):
* When a port error occurs (e.g., disconnect, overcurrent):
* - Any previously active channels will remain active (i.e., they will not receive a channel interrupt)
* - Attempting to disable them using usb_dwc_hal_chan_request_halt() will NOT generate an interrupt for ISOC channels
* (probalby something to do with the periodic scheduling)
* (probably something to do with the periodic scheduling)
*
* However, the channel's enable bit can be left as 1 since after a port error, a soft reset will be done anyways.
* This function simply updates the channels internal state variable to indicate it is halted (thus allowing it to be

View File

@@ -214,9 +214,7 @@ struct pipe_obj {
uint32_t waiting_halt: 1;
uint32_t pipe_cmd_processing: 1;
uint32_t has_urb: 1; // Indicates there is at least one URB either pending, in-flight, or done
uint32_t persist: 1; // indicates that this pipe should persist through a run-time port reset
uint32_t reset_lock: 1; // Indicates that this pipe is undergoing a run-time reset
uint32_t reserved27: 27;
uint32_t reserved29: 29;
};
uint32_t val;
} cs_flags;
@@ -560,28 +558,6 @@ static esp_err_t _pipe_cmd_clear(pipe_t *pipe);
// ------------------------ Port ---------------------------
/**
* @brief Prepare persistent pipes for reset
*
* This function checks if all pipes are reset persistent and proceeds to free their underlying HAL channels for the
* persistent pipes. This should be called before a run time reset
*
* @param port Port object
* @return true All pipes are persistent and their channels are freed
* @return false Not all pipes are persistent
*/
static bool _port_persist_all_pipes(port_t *port);
/**
* @brief Recovers all persistent pipes after a reset
*
* This function will recover all persistent pipes after a reset and reallocate their underlying HAl channels. This
* function should be called after a reset.
*
* @param port Port object
*/
static void _port_recover_all_pipes(port_t *port);
/**
* @brief Checks if all pipes are in the halted state
*
@@ -1162,44 +1138,6 @@ esp_err_t hcd_uninstall(void)
// ----------------------- Helpers -------------------------
static bool _port_persist_all_pipes(port_t *port)
{
if (port->num_pipes_queued > 0) {
// All pipes must be idle before we run-time reset
return false;
}
bool all_persist = true;
pipe_t *pipe;
// Check that each pipe is persistent
TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
if (!pipe->cs_flags.persist) {
all_persist = false;
break;
}
}
if (!all_persist) {
// At least one pipe is not persistent. All pipes must be freed or made persistent before we can reset
return false;
}
TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
pipe->cs_flags.reset_lock = 1;
usb_dwc_hal_chan_free(port->hal, pipe->chan_obj);
}
return true;
}
static void _port_recover_all_pipes(port_t *port)
{
pipe_t *pipe;
TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
pipe->cs_flags.persist = 0;
pipe->cs_flags.reset_lock = 0;
usb_dwc_hal_chan_alloc(port->hal, pipe->chan_obj, (void *)pipe);
usb_dwc_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char);
}
CACHE_SYNC_FRAME_LIST(port->frame_list);
}
static bool _port_check_all_pipes_halted(port_t *port)
{
bool all_halted = true;
@@ -1276,20 +1214,26 @@ static esp_err_t _port_cmd_power_off(port_t *port)
static esp_err_t _port_cmd_reset(port_t *port)
{
esp_err_t ret;
// Port can only a reset when it is in the enabled or disabled states (in case of new connection)
// Port can only a reset when it is in the enabled or disabled (in the case of a new connection)states.
if (port->state != HCD_PORT_STATE_ENABLED && port->state != HCD_PORT_STATE_DISABLED) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
bool is_runtime_reset = (port->state == HCD_PORT_STATE_ENABLED) ? true : false;
if (is_runtime_reset && !_port_persist_all_pipes(port)) {
// If this is a run time reset, check all pipes that are still allocated can persist the reset
// Port can only be reset if all pipes are idle
if (port->num_pipes_queued > 0) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
// All pipes (if any_) are guaranteed to be persistent at this point. Proceed to resetting the bus
/*
Proceed to resetting the bus
- Update the port's state variable
- Hold the bus in the reset state for RESET_HOLD_MS.
- Return the bus to the idle state for RESET_RECOVERY_MS
*/
port->state = HCD_PORT_STATE_RESETTING;
// Put and hold the bus in the reset state. If the port was previously enabled, a disabled event will occur after this
// Place the bus into the reset state. If the port was previously enabled, a disabled event will occur after this
usb_dwc_hal_port_toggle_reset(port->hal, true);
HCD_EXIT_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(RESET_HOLD_MS));
@@ -1299,7 +1243,8 @@ static esp_err_t _port_cmd_reset(port_t *port)
ret = ESP_ERR_INVALID_RESPONSE;
goto bailout;
}
// Return the bus to the idle state and hold it for the required reset recovery time. Port enabled event should occur
// Return the bus to the idle state. Port enabled event should occur
usb_dwc_hal_port_toggle_reset(port->hal, false);
HCD_EXIT_CRITICAL();
vTaskDelay(pdMS_TO_TICKS(RESET_RECOVERY_MS));
@@ -1309,16 +1254,20 @@ static esp_err_t _port_cmd_reset(port_t *port)
ret = ESP_ERR_INVALID_RESPONSE;
goto bailout;
}
// Set FIFO sizes based on the selected biasing
usb_dwc_hal_set_fifo_bias(port->hal, port->fifo_bias);
// We start periodic scheduling only after a RESET command since SOFs only start after a reset
usb_dwc_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN);
usb_dwc_hal_port_periodic_enable(port->hal);
// Reinitialize port registers.
usb_dwc_hal_set_fifo_bias(port->hal, port->fifo_bias); // Set FIFO biases
usb_dwc_hal_port_set_frame_list(port->hal, port->frame_list, FRAME_LIST_LEN); // Set periodic frame list
usb_dwc_hal_port_periodic_enable(port->hal); // Enable periodic scheduling
ret = ESP_OK;
bailout:
if (is_runtime_reset) {
_port_recover_all_pipes(port);
// Reinitialize channel registers
pipe_t *pipe;
TAILQ_FOREACH(pipe, &port->pipes_idle_tailq, tailq_entry) {
usb_dwc_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char);
}
CACHE_SYNC_FRAME_LIST(port->frame_list);
exit:
return ret;
}
@@ -1987,8 +1936,7 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl)
HCD_ENTER_CRITICAL();
// Check that all URBs have been removed and pipe has no pending events
HCD_CHECK_FROM_CRIT(!pipe->multi_buffer_control.buffer_is_executing
&& !pipe->cs_flags.has_urb
&& !pipe->cs_flags.reset_lock,
&& !pipe->cs_flags.has_urb,
ESP_ERR_INVALID_STATE);
// Remove pipe from the list of idle pipes (it must be in the idle list because it should have no queued URBs)
TAILQ_REMOVE(&pipe->port->pipes_idle_tailq, pipe, tailq_entry);
@@ -2011,8 +1959,7 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps)
HCD_ENTER_CRITICAL();
// Check if pipe is in the correct state to be updated
HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing &&
!pipe->cs_flags.has_urb &&
!pipe->cs_flags.reset_lock,
!pipe->cs_flags.has_urb,
ESP_ERR_INVALID_STATE);
pipe->ep_char.mps = mps;
// Update the underlying channel's registers
@@ -2027,8 +1974,7 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr)
HCD_ENTER_CRITICAL();
// Check if pipe is in the correct state to be updated
HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing &&
!pipe->cs_flags.has_urb &&
!pipe->cs_flags.reset_lock,
!pipe->cs_flags.has_urb,
ESP_ERR_INVALID_STATE);
pipe->ep_char.dev_addr = dev_addr;
// Update the underlying channel's registers
@@ -2037,35 +1983,6 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr)
return ESP_OK;
}
esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg)
{
pipe_t *pipe = (pipe_t *)pipe_hdl;
HCD_ENTER_CRITICAL();
// Check if pipe is in the correct state to be updated
HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing &&
!pipe->cs_flags.has_urb &&
!pipe->cs_flags.reset_lock,
ESP_ERR_INVALID_STATE);
pipe->callback = callback;
pipe->callback_arg = user_arg;
HCD_EXIT_CRITICAL();
return ESP_OK;
}
esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl)
{
pipe_t *pipe = (pipe_t *)pipe_hdl;
HCD_ENTER_CRITICAL();
// Check if pipe is in the correct state to be updated
HCD_CHECK_FROM_CRIT(!pipe->cs_flags.pipe_cmd_processing &&
!pipe->cs_flags.has_urb &&
!pipe->cs_flags.reset_lock,
ESP_ERR_INVALID_STATE);
pipe->cs_flags.persist = 1;
HCD_EXIT_CRITICAL();
return ESP_OK;
}
void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl)
{
pipe_t *pipe = (pipe_t *)pipe_hdl;
@@ -2102,27 +2019,22 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command)
esp_err_t ret = ESP_OK;
HCD_ENTER_CRITICAL();
// Cannot execute pipe commands the pipe is already executing a command, or if the pipe or its port are no longer valid
if (pipe->cs_flags.reset_lock) {
ret = ESP_ERR_INVALID_STATE;
} else {
pipe->cs_flags.pipe_cmd_processing = 1;
switch (command) {
case HCD_PIPE_CMD_HALT: {
ret = _pipe_cmd_halt(pipe);
break;
}
case HCD_PIPE_CMD_FLUSH: {
ret = _pipe_cmd_flush(pipe);
break;
}
case HCD_PIPE_CMD_CLEAR: {
ret = _pipe_cmd_clear(pipe);
break;
}
}
pipe->cs_flags.pipe_cmd_processing = 0;
pipe->cs_flags.pipe_cmd_processing = 1;
switch (command) {
case HCD_PIPE_CMD_HALT: {
ret = _pipe_cmd_halt(pipe);
break;
}
case HCD_PIPE_CMD_FLUSH: {
ret = _pipe_cmd_flush(pipe);
break;
}
case HCD_PIPE_CMD_CLEAR: {
ret = _pipe_cmd_clear(pipe);
break;
}
}
pipe->cs_flags.pipe_cmd_processing = 0;
HCD_EXIT_CRITICAL();
return ret;
}
@@ -2658,8 +2570,7 @@ esp_err_t hcd_urb_enqueue(hcd_pipe_handle_t pipe_hdl, urb_t *urb)
// Check that pipe and port are in the correct state to receive URBs
HCD_CHECK_FROM_CRIT(pipe->port->state == HCD_PORT_STATE_ENABLED // The pipe's port must be in the correct state
&& pipe->state == HCD_PIPE_STATE_ACTIVE // The pipe must be in the correct state
&& !pipe->cs_flags.pipe_cmd_processing // Pipe cannot currently be processing a pipe command
&& !pipe->cs_flags.reset_lock, // Pipe cannot be persisting through a port reset
&& !pipe->cs_flags.pipe_cmd_processing, // Pipe cannot currently be processing a pipe command
ESP_ERR_INVALID_STATE);
// Use the URB's reserved_ptr to store the pipe's
urb->hcd_ptr = (void *)pipe;

View File

@@ -40,6 +40,7 @@ implement the bare minimum to control the root HCD port.
#define ENUM_CTRL_TRANSFER_MAX_DATA_LEN CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE
#define ENUM_DEV_ADDR 1 // Device address used in enumeration
#define ENUM_DEV_UID 1 // Unique ID for device connected to root port
#define ENUM_CONFIG_INDEX_DEFAULT 0 // Index used to get the first configuration descriptor of the device
#define ENUM_SHORT_DESC_REQ_LEN 8 // Number of bytes to request when getting a short descriptor (just enough to get bMaxPacketSize0 or wTotalLength)
#define ENUM_WORST_CASE_MPS_LS 8 // The worst case MPS of EP0 for a LS device
@@ -50,9 +51,12 @@ implement the bare minimum to control the root HCD port.
// Hub driver action flags. LISTED IN THE ORDER THEY SHOULD BE HANDLED IN within hub_process(). Some actions are mutually exclusive
#define HUB_DRIVER_FLAG_ACTION_ROOT_EVENT 0x01
#define HUB_DRIVER_FLAG_ACTION_PORT 0x02
#define HUB_DRIVER_FLAG_ACTION_PORT_REQ 0x02
#define HUB_DRIVER_FLAG_ACTION_ENUM_EVENT 0x04
#define PORT_REQ_DISABLE 0x01
#define PORT_REQ_RECOVER 0x02
/**
* @brief Root port states
*
@@ -60,9 +64,8 @@ implement the bare minimum to control the root HCD port.
typedef enum {
ROOT_PORT_STATE_NOT_POWERED, /**< Root port initialized and/or not powered */
ROOT_PORT_STATE_POWERED, /**< Root port is powered, device is not connected */
ROOT_PORT_STATE_ENUM, /**< A device has been connected to the root port and is undergoing enumeration */
ROOT_PORT_STATE_ENUM_FAILED, /**< Enumeration of a connected device has failed. Waiting for that device to be disconnected */
ROOT_PORT_ACTIVE, /**< The connected device was enumerated and port is active */
ROOT_PORT_STATE_DISABLED, /**< A device is connected but is disabled (i.e., not reset, no SOFs are sent) */
ROOT_PORT_STATE_ENABLED, /**< A device is connected, port has been reset, SOFs are sent */
ROOT_PORT_STATE_RECOVERY, /**< Root port encountered an error and needs to be recovered */
} root_port_state_t;
@@ -156,7 +159,6 @@ typedef struct {
urb_t *urb; /**< URB used for enumeration control transfers. Max data length of ENUM_CTRL_TRANSFER_MAX_DATA_LEN */
// Initialized at start of a particular enumeration
usb_device_handle_t dev_hdl; /**< Handle of device being enumerated */
hcd_pipe_handle_t pipe; /**< Default pipe handle of the device being enumerated */
// Updated during enumeration
enum_stage_t stage; /**< Current enumeration stage */
int expect_num_bytes; /**< Expected number of bytes for IN transfers stages. Set to 0 for OUT transfer */
@@ -185,10 +187,11 @@ typedef struct {
uint32_t val;
} flags;
root_port_state_t root_port_state;
unsigned int port_reqs;
} dynamic;
// Single thread members don't require a critical section so long as they are never accessed from multiple threads
struct {
usb_device_handle_t root_dev_hdl; // Indicates if an enumerated device is connected to the root port
unsigned int root_dev_uid; // UID of the device connected to root port. 0 if no device connected
enum_ctrl_t enum_ctrl;
} single_thread;
// Constant members do no change after installation thus do not require a critical section
@@ -241,40 +244,26 @@ const char *HUB_DRIVER_TAG = "HUB";
static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port_event, void *user_arg, bool in_isr);
/**
* @brief HCD pipe callback for the default pipe of the device under enumeration
* @brief Control transfer callback used for enumeration
*
* - This callback is called from the context of the HCD, so any event handling should be deferred to hub_process()
* - This callback needs to call proc_req_cb to ensure that hub_process() gets a chance to run
*
* @param pipe_hdl HCD pipe handle
* @param pipe_event Pipe event
* @param user_arg Callback argument
* @param in_isr Whether callback is in an ISR context
* @return Whether a yield is required
* @param transfer Transfer object
*/
static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr);
static void enum_transfer_callback(usb_transfer_t *transfer);
// ------------------------------------------------- Enum Functions ----------------------------------------------------
static bool enum_stage_start(enum_ctrl_t *enum_ctrl)
{
// Get the speed of the device, and set the enum MPS to the worst case size for now
usb_speed_t speed;
if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) {
return false;
}
enum_ctrl->bMaxPacketSize0 = (speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS;
// Try to add the device to USBH
usb_device_handle_t enum_dev_hdl;
hcd_pipe_handle_t enum_dflt_pipe_hdl;
// We use NULL as the parent device to indicate the Root Hub port 1. We currently only support a single device
if (usbh_hub_add_dev(p_hub_driver_obj->constant.root_port_hdl, speed, &enum_dev_hdl, &enum_dflt_pipe_hdl) != ESP_OK) {
return false;
}
// Set our own default pipe callback
ESP_ERROR_CHECK(hcd_pipe_update_callback(enum_dflt_pipe_hdl, enum_dflt_pipe_callback, NULL));
enum_ctrl->dev_hdl = enum_dev_hdl;
enum_ctrl->pipe = enum_dflt_pipe_hdl;
// Open the newly added device (at address 0)
ESP_ERROR_CHECK(usbh_devs_open(0, &p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl));
// Get the speed of the device to set the initial MPS of EP0
usb_device_info_t dev_info;
ESP_ERROR_CHECK(usbh_dev_get_info(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl, &dev_info));
enum_ctrl->bMaxPacketSize0 = (dev_info.speed == USB_SPEED_LOW) ? ENUM_WORST_CASE_MPS_LS : ENUM_WORST_CASE_MPS_FS;
// Lock the device for enumeration. This allows us call usbh_dev_set_...() functions during enumeration
ESP_ERROR_CHECK(usbh_dev_enum_lock(p_hub_driver_obj->single_thread.enum_ctrl.dev_hdl));
// Flag to gracefully exit the enumeration process if requested by the user in the enumeration filter cb
#ifdef ENABLE_ENUM_FILTER_CALLBACK
@@ -285,7 +274,6 @@ static bool enum_stage_start(enum_ctrl_t *enum_ctrl)
static bool enum_stage_second_reset(enum_ctrl_t *enum_ctrl)
{
ESP_ERROR_CHECK(hcd_pipe_set_persist_reset(enum_ctrl->pipe)); // Persist the default pipe through the reset
if (hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to issue second reset");
return false;
@@ -442,7 +430,7 @@ static bool enum_stage_transfer(enum_ctrl_t *enum_ctrl)
abort();
break;
}
if (hcd_urb_enqueue(enum_ctrl->pipe, enum_ctrl->urb) != ESP_OK) {
if (usbh_dev_submit_ctrl_urb(enum_ctrl->dev_hdl, enum_ctrl->urb) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to submit: %s", enum_stage_strings[enum_ctrl->stage]);
return false;
}
@@ -467,18 +455,10 @@ static bool enum_stage_wait(enum_ctrl_t *enum_ctrl)
static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
{
// Dequeue the URB
urb_t *dequeued_enum_urb = hcd_urb_dequeue(enum_ctrl->pipe);
assert(dequeued_enum_urb == enum_ctrl->urb);
// Check transfer status
usb_transfer_t *transfer = &dequeued_enum_urb->transfer;
usb_transfer_t *transfer = &enum_ctrl->urb->transfer;
if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) {
ESP_LOGE(HUB_DRIVER_TAG, "Bad transfer status %d: %s", transfer->status, enum_stage_strings[enum_ctrl->stage]);
if (transfer->status == USB_TRANSFER_STATUS_STALL) {
// EP stalled, clearing the pipe to execute further stages
ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_CLEAR));
}
return false;
}
// Check IN transfer returned the expected correct number of bytes
@@ -505,8 +485,8 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
ret = false;
break;
}
// Update and save the MPS of the default pipe
if (hcd_pipe_update_mps(enum_ctrl->pipe, device_desc->bMaxPacketSize0) != ESP_OK) {
// Update and save the MPS of the EP0
if (usbh_dev_set_ep0_mps(enum_ctrl->dev_hdl, device_desc->bMaxPacketSize0) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to update MPS");
ret = false;
break;
@@ -517,16 +497,15 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
break;
}
case ENUM_STAGE_CHECK_ADDR: {
// Update the pipe and device's address, and fill the address into the device object
ESP_ERROR_CHECK(hcd_pipe_update_dev_addr(enum_ctrl->pipe, ENUM_DEV_ADDR));
ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR));
// Update the device's address
ESP_ERROR_CHECK(usbh_dev_set_addr(enum_ctrl->dev_hdl, ENUM_DEV_ADDR));
ret = true;
break;
}
case ENUM_STAGE_CHECK_FULL_DEV_DESC: {
// Fill device descriptor into the device object
// Set the device's descriptor
const usb_device_desc_t *device_desc = (const usb_device_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t));
ESP_ERROR_CHECK(usbh_hub_enum_fill_dev_desc(enum_ctrl->dev_hdl, device_desc));
ESP_ERROR_CHECK(usbh_dev_set_desc(enum_ctrl->dev_hdl, device_desc));
enum_ctrl->iManufacturer = device_desc->iManufacturer;
enum_ctrl->iProduct = device_desc->iProduct;
enum_ctrl->iSerialNumber = device_desc->iSerialNumber;
@@ -555,10 +534,10 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
break;
}
case ENUM_STAGE_CHECK_FULL_CONFIG_DESC: {
// Fill configuration descriptor into the device object
// Set the device's configuration descriptor
const usb_config_desc_t *config_desc = (usb_config_desc_t *)(transfer->data_buffer + sizeof(usb_setup_packet_t));
enum_ctrl->bConfigurationValue = config_desc->bConfigurationValue;
ESP_ERROR_CHECK(usbh_hub_enum_fill_config_desc(enum_ctrl->dev_hdl, config_desc));
ESP_ERROR_CHECK(usbh_dev_set_config_desc(enum_ctrl->dev_hdl, config_desc));
ret = true;
break;
}
@@ -635,7 +614,7 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
} else { // ENUM_STAGE_CHECK_FULL_PROD_STR_DESC
select = 2;
}
ESP_ERROR_CHECK(usbh_hub_enum_fill_str_desc(enum_ctrl->dev_hdl, str_desc, select));
ESP_ERROR_CHECK(usbh_dev_set_str_desc(enum_ctrl->dev_hdl, str_desc, select));
ret = true;
break;
}
@@ -650,42 +629,27 @@ static bool enum_stage_transfer_check(enum_ctrl_t *enum_ctrl)
static void enum_stage_cleanup(enum_ctrl_t *enum_ctrl)
{
// We currently only support a single device connected to the root port. Move the device handle from enum to root
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_ACTIVE;
HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->single_thread.root_dev_hdl = enum_ctrl->dev_hdl;
usb_device_handle_t dev_hdl = enum_ctrl->dev_hdl;
// Unlock the device as we are done with the enumeration
ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl));
// Propagate a new device event
ESP_ERROR_CHECK(usbh_devs_new_dev_event(enum_ctrl->dev_hdl));
// We are done with using the device. Close it.
ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl));
// Clear values in enum_ctrl
enum_ctrl->dev_hdl = NULL;
enum_ctrl->pipe = NULL;
// Update device object after enumeration is done
ESP_ERROR_CHECK(usbh_hub_enum_done(dev_hdl));
}
static void enum_stage_cleanup_failed(enum_ctrl_t *enum_ctrl)
{
// Enumeration failed. Clear the enum device handle and pipe
if (enum_ctrl->dev_hdl) {
// If enumeration failed due to a port event, we need to Halt, flush, and dequeue enum default pipe in case there
// was an in-flight URB.
ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_HALT));
ESP_ERROR_CHECK(hcd_pipe_command(enum_ctrl->pipe, HCD_PIPE_CMD_FLUSH));
hcd_urb_dequeue(enum_ctrl->pipe); // This could return NULL if there
ESP_ERROR_CHECK(usbh_hub_enum_failed(enum_ctrl->dev_hdl)); // Free the underlying device object first before recovering the port
// Close the device and unlock it as we done with enumeration
ESP_ERROR_CHECK(usbh_dev_enum_unlock(enum_ctrl->dev_hdl));
ESP_ERROR_CHECK(usbh_devs_close(enum_ctrl->dev_hdl));
// We allow this to fail in case the device object was already freed
usbh_devs_remove(ENUM_DEV_UID);
}
// Clear values in enum_ctrl
enum_ctrl->dev_hdl = NULL;
enum_ctrl->pipe = NULL;
HUB_DRIVER_ENTER_CRITICAL();
// Enum could have failed due to a port error. If so, we need to trigger a port recovery
if (p_hub_driver_obj->dynamic.root_port_state == ROOT_PORT_STATE_RECOVERY) {
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT;
} else {
// Otherwise, we move to the enum failed state and wait for the device to disconnect
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM_FAILED;
}
HUB_DRIVER_EXIT_CRITICAL();
}
static enum_stage_t get_next_stage(enum_stage_t old_stage, enum_ctrl_t *enum_ctrl)
@@ -799,13 +763,13 @@ static bool root_port_callback(hcd_port_handle_t port_hdl, hcd_port_event_t port
return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
}
static bool enum_dflt_pipe_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_event_t pipe_event, void *user_arg, bool in_isr)
static void enum_transfer_callback(usb_transfer_t *transfer)
{
// Note: This callback may have triggered when a failed enumeration is already cleaned up (e.g., due to a failed port reset)
// We simply trigger a processing request to handle the completed enumeration control transfer
HUB_DRIVER_ENTER_CRITICAL_SAFE();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
HUB_DRIVER_EXIT_CRITICAL_SAFE();
return p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, in_isr, p_hub_driver_obj->constant.proc_req_cb_arg);
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
}
// ---------------------- Handlers -------------------------
@@ -818,17 +782,32 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
// Nothing to do
break;
case HCD_PORT_EVENT_CONNECTION: {
if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) == ESP_OK) {
ESP_LOGD(HUB_DRIVER_TAG, "Root port reset");
// Start enumeration
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENUM;
HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START;
} else {
if (hcd_port_command(root_port_hdl, HCD_PORT_CMD_RESET) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Root port reset failed");
goto reset_err;
}
ESP_LOGD(HUB_DRIVER_TAG, "Root port reset");
usb_speed_t speed;
if (hcd_port_get_speed(p_hub_driver_obj->constant.root_port_hdl, &speed) != ESP_OK) {
goto new_dev_err;
}
// Allocate a new device. We use a fixed ENUM_DEV_UID for now since we only support a single device
if (usbh_devs_add(ENUM_DEV_UID, speed, p_hub_driver_obj->constant.root_port_hdl) != ESP_OK) {
ESP_LOGE(HUB_DRIVER_TAG, "Failed to add device");
goto new_dev_err;
}
p_hub_driver_obj->single_thread.root_dev_uid = ENUM_DEV_UID;
// Start enumeration
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_ENABLED;
HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_START;
break;
new_dev_err:
// We allow this to fail in case a disconnect/port error happens while disabling.
hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
reset_err:
break;
}
case HCD_PORT_EVENT_DISCONNECTION:
@@ -838,17 +817,13 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
HUB_DRIVER_ENTER_CRITICAL();
switch (p_hub_driver_obj->dynamic.root_port_state) {
case ROOT_PORT_STATE_POWERED: // This occurred before enumeration
case ROOT_PORT_STATE_ENUM_FAILED: // This occurred after a failed enumeration.
// Therefore, there's no device and we can go straight to port recovery
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT;
case ROOT_PORT_STATE_DISABLED: // This occurred after the device has already been disabled
// Therefore, there's no device object to clean up, and we can go straight to port recovery
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ;
break;
case ROOT_PORT_STATE_ENUM:
// This occurred during enumeration. Therefore, we need to cleanup the failed enumeration
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_ENUM_EVENT;
p_hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_CLEANUP_FAILED;
break;
case ROOT_PORT_ACTIVE:
// There was an enumerated device. We need to indicate to USBH that the device is gone
case ROOT_PORT_STATE_ENABLED:
// There is an enabled (active) device. We need to indicate to USBH that the device is gone
pass_event_to_usbh = true;
break;
default:
@@ -858,7 +833,10 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_RECOVERY;
HUB_DRIVER_EXIT_CRITICAL();
if (pass_event_to_usbh) {
ESP_ERROR_CHECK(usbh_hub_dev_gone(p_hub_driver_obj->single_thread.root_dev_hdl));
// The port must have a device object
assert(p_hub_driver_obj->single_thread.root_dev_uid != 0);
// We allow this to fail in case the device object was already freed
usbh_devs_remove(p_hub_driver_obj->single_thread.root_dev_uid);
}
break;
}
@@ -868,6 +846,30 @@ static void root_port_handle_events(hcd_port_handle_t root_port_hdl)
}
}
static void root_port_req(hcd_port_handle_t root_port_hdl)
{
unsigned int port_reqs;
HUB_DRIVER_ENTER_CRITICAL();
port_reqs = p_hub_driver_obj->dynamic.port_reqs;
p_hub_driver_obj->dynamic.port_reqs = 0;
HUB_DRIVER_EXIT_CRITICAL();
if (port_reqs & PORT_REQ_DISABLE) {
ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port");
// We allow this to fail in case a disconnect/port error happens while disabling.
hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
}
if (port_reqs & PORT_REQ_RECOVER) {
ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port");
ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl));
ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON));
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
HUB_DRIVER_EXIT_CRITICAL();
}
}
static void enum_handle_events(void)
{
bool stage_pass;
@@ -926,7 +928,6 @@ static void enum_handle_events(void)
stage_pass = true;
break;
default:
// Note: Don't abort here. The enum_dflt_pipe_callback() can trigger a HUB_DRIVER_FLAG_ACTION_ENUM_EVENT after a cleanup.
stage_pass = true;
break;
}
@@ -948,18 +949,22 @@ static void enum_handle_events(void)
// ---------------------------------------------- Hub Driver Functions -------------------------------------------------
esp_err_t hub_install(hub_config_t *hub_config)
esp_err_t hub_install(hub_config_t *hub_config, void **client_ret)
{
HUB_DRIVER_ENTER_CRITICAL();
HUB_DRIVER_CHECK_FROM_CRIT(p_hub_driver_obj == NULL, ESP_ERR_INVALID_STATE);
HUB_DRIVER_EXIT_CRITICAL();
esp_err_t ret;
// Allocate Hub driver object
hub_driver_t *hub_driver_obj = heap_caps_calloc(1, sizeof(hub_driver_t), MALLOC_CAP_DEFAULT);
urb_t *enum_urb = urb_alloc(sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_DATA_LEN, 0);
if (hub_driver_obj == NULL || enum_urb == NULL) {
return ESP_ERR_NO_MEM;
}
esp_err_t ret;
enum_urb->usb_host_client = (void *)hub_driver_obj;
enum_urb->transfer.callback = enum_transfer_callback;
// Install HCD port
hcd_port_config_t port_config = {
.fifo_bias = HUB_ROOT_HCD_PORT_FIFO_BIAS,
@@ -972,6 +977,7 @@ esp_err_t hub_install(hub_config_t *hub_config)
if (ret != ESP_OK) {
goto err;
}
// Initialize Hub driver object
hub_driver_obj->single_thread.enum_ctrl.stage = ENUM_STAGE_NONE;
hub_driver_obj->single_thread.enum_ctrl.urb = enum_urb;
@@ -991,6 +997,9 @@ esp_err_t hub_install(hub_config_t *hub_config)
}
p_hub_driver_obj = hub_driver_obj;
HUB_DRIVER_EXIT_CRITICAL();
// Write-back client_ret pointer
*client_ret = (void *)hub_driver_obj;
ret = ESP_OK;
return ret;
@@ -1052,17 +1061,31 @@ esp_err_t hub_root_stop(void)
return ret;
}
esp_err_t hub_dev_is_free(uint8_t dev_addr)
esp_err_t hub_port_recycle(unsigned int dev_uid)
{
assert(dev_addr == ENUM_DEV_ADDR);
assert(p_hub_driver_obj->single_thread.root_dev_hdl);
p_hub_driver_obj->single_thread.root_dev_hdl = NULL;
// Device is free, we can now request its port be recycled
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT;
HUB_DRIVER_EXIT_CRITICAL();
if (dev_uid == p_hub_driver_obj->single_thread.root_dev_uid) {
// Device is free, we can now request its port be recycled
hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
p_hub_driver_obj->single_thread.root_dev_uid = 0;
HUB_DRIVER_ENTER_CRITICAL();
// How the port is recycled will depend on the port's state
switch (port_state) {
case HCD_PORT_STATE_ENABLED:
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_DISABLE;
break;
case HCD_PORT_STATE_RECOVERY:
p_hub_driver_obj->dynamic.port_reqs |= PORT_REQ_RECOVER;
break;
default:
abort(); // Should never occur
break;
}
p_hub_driver_obj->dynamic.flags.actions |= HUB_DRIVER_FLAG_ACTION_PORT_REQ;
HUB_DRIVER_EXIT_CRITICAL();
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
}
p_hub_driver_obj->constant.proc_req_cb(USB_PROC_REQ_SOURCE_HUB, false, p_hub_driver_obj->constant.proc_req_cb_arg);
return ESP_OK;
}
@@ -1077,29 +1100,8 @@ esp_err_t hub_process(void)
if (action_flags & HUB_DRIVER_FLAG_ACTION_ROOT_EVENT) {
root_port_handle_events(p_hub_driver_obj->constant.root_port_hdl);
}
if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT) {
// Check current state of port
hcd_port_state_t port_state = hcd_port_get_state(p_hub_driver_obj->constant.root_port_hdl);
switch (port_state) {
case HCD_PORT_STATE_ENABLED:
// Port is still enabled with a connect device. Disable it.
ESP_LOGD(HUB_DRIVER_TAG, "Disabling root port");
// We allow this to fail in case a disconnect/port error happens while disabling.
hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_DISABLE);
break;
case HCD_PORT_STATE_RECOVERY:
// Port is in recovery after a disconnect/error. Recover it.
ESP_LOGD(HUB_DRIVER_TAG, "Recovering root port");
ESP_ERROR_CHECK(hcd_port_recover(p_hub_driver_obj->constant.root_port_hdl));
ESP_ERROR_CHECK(hcd_port_command(p_hub_driver_obj->constant.root_port_hdl, HCD_PORT_CMD_POWER_ON));
HUB_DRIVER_ENTER_CRITICAL();
p_hub_driver_obj->dynamic.root_port_state = ROOT_PORT_STATE_POWERED;
HUB_DRIVER_EXIT_CRITICAL();
break;
default:
abort(); // Should never occur
break;
}
if (action_flags & HUB_DRIVER_FLAG_ACTION_PORT_REQ) {
root_port_req(p_hub_driver_obj->constant.root_port_hdl);
}
if (action_flags & HUB_DRIVER_FLAG_ACTION_ENUM_EVENT) {
enum_handle_events();

View File

@@ -418,36 +418,6 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps);
*/
esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr);
/**
* @brief Update a pipe's callback
*
* This function is intended to be called on default pipes at the end of enumeration to switch to a callback that
* handles the completion of regular control transfer.
* - Pipe is not current processing a command
* - Pipe does not have any enqueued URBs
* - Port cannot be resetting
*
* @param pipe_hdl Pipe handle
* @param callback Callback
* @param user_arg Callback argument
* @return esp_err_t
*/
esp_err_t hcd_pipe_update_callback(hcd_pipe_handle_t pipe_hdl, hcd_pipe_callback_t callback, void *user_arg);
/**
* @brief Make a pipe persist through a run time reset
*
* Normally when a HCD_PORT_CMD_RESET is called, all pipes should already have been freed. However There may be cases
* (such as during enumeration) when a pipe must persist through a reset. This function will mark a pipe as
* persistent allowing it to survive a reset. When HCD_PORT_CMD_RESET is called, the pipe can continue to be used after
* the reset.
*
* @param pipe_hdl Pipe handle
* @retval ESP_OK: Pipe successfully marked as persistent
* @retval ESP_ERR_INVALID_STATE: Pipe is not in a condition to be made persistent
*/
esp_err_t hcd_pipe_set_persist_reset(hcd_pipe_handle_t pipe_hdl);
/**
* @brief Get the context variable of a pipe from its handle
*

View File

@@ -42,9 +42,10 @@ typedef struct {
* - Initializes the HCD root port
*
* @param[in] hub_config Hub driver configuration
* @param[out] client_ret Unique pointer to identify the Hub as a USB Host client
* @return esp_err_t
*/
esp_err_t hub_install(hub_config_t *hub_config);
esp_err_t hub_install(hub_config_t *hub_config, void **client_ret);
/**
* @brief Uninstall Hub driver
@@ -78,15 +79,16 @@ esp_err_t hub_root_start(void);
esp_err_t hub_root_stop(void);
/**
* @brief Indicate to the Hub driver that a device has been freed
* @brief Indicate to the Hub driver that a device's port can be recycled
*
* Hub driver can now recover the port that the device was connected to
* The device connected to the port has been freed. The Hub driver can now
* recycled the port.
*
* @param dev_addr Device address
* @param dev_uid Device's unique ID
* @return
* - ESP_OK: Success
*/
esp_err_t hub_dev_is_free(uint8_t dev_addr);
esp_err_t hub_port_recycle(unsigned int dev_uid);
/**
* @brief Hub driver's processing function

View File

@@ -58,7 +58,7 @@ typedef struct {
usb_device_handle_t dev_hdl;
} dev_gone_data;
struct {
uint8_t dev_addr;
unsigned int dev_uid;
} dev_free_data;
};
} usbh_event_data_t;
@@ -130,7 +130,7 @@ typedef struct {
void *event_cb_arg; /**< USBH event callback argument */
} usbh_config_t;
// ------------------------------------------------- USBH Functions ----------------------------------------------------
// -------------------------------------------- USBH Processing Functions ----------------------------------------------
/**
* @brief Installs the USBH driver
@@ -169,6 +169,8 @@ esp_err_t usbh_uninstall(void);
*/
esp_err_t usbh_process(void);
// ---------------------------------------------- Device Pool Functions ------------------------------------------------
/**
* @brief Get the current number of devices
*
@@ -176,17 +178,13 @@ esp_err_t usbh_process(void);
* @param[out] num_devs_ret Current number of devices
* @return esp_err_t
*/
esp_err_t usbh_num_devs(int *num_devs_ret);
// ------------------------------------------------ Device Functions ---------------------------------------------------
// --------------------- Device Pool -----------------------
esp_err_t usbh_devs_num(int *num_devs_ret);
/**
* @brief Fill list with address of currently connected devices
*
* - This function fills the provided list with the address of current connected devices
* - Device address can then be used in usbh_dev_open()
* - Device address can then be used in usbh_devs_open()
* - If there are more devices than the list_len, this function will only fill
* up to list_len number of devices.
*
@@ -195,7 +193,44 @@ esp_err_t usbh_num_devs(int *num_devs_ret);
* @param[out] num_dev_ret Number of devices filled into list
* @return esp_err_t
*/
esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret);
esp_err_t usbh_devs_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num_dev_ret);
/**
* @brief Create a device and add it to the device pool
*
* The created device will not be enumerated where the device's address is 0,
* device and config descriptor are NULL. The device will still have a default
* pipe, thus allowing control transfers to be submitted.
*
* - Call usbh_devs_open() before communicating with the device
* - Call usbh_dev_enum_lock() before enumerating the device via the various
* usbh_dev_set_...() functions.
*
* @param[in] uid Unique ID assigned to the device
* @param[in] dev_speed Device's speed
* @param[in] port_hdl Handle of the port that the device is connected to
* @return esp_err_t
*/
esp_err_t usbh_devs_add(unsigned int uid, usb_speed_t dev_speed, hcd_port_handle_t port_hdl);
/**
* @brief Indicates to the USBH that a device is gone
*
* @param[in] uid Unique ID assigned to the device on creation (see 'usbh_devs_add()')
* @return esp_err_t
*/
esp_err_t usbh_devs_remove(unsigned int uid);
/**
* @brief Mark that all devices should be freed at the next possible opportunity
*
* A device marked as free will not be freed until the last client using the device has called usbh_devs_close()
*
* @return
* - ESP_OK: There were no devices to free to begin with. Current state is all free
* - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed")
*/
esp_err_t usbh_devs_mark_all_free(void);
/**
* @brief Open a device by address
@@ -206,30 +241,31 @@ esp_err_t usbh_dev_addr_list_fill(int list_len, uint8_t *dev_addr_list, int *num
* @param[out] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl);
esp_err_t usbh_devs_open(uint8_t dev_addr, usb_device_handle_t *dev_hdl);
/**
* @brief CLose a device
*
* Device can be opened by calling usbh_dev_open()
* Device can be opened by calling usbh_devs_open()
*
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_close(usb_device_handle_t dev_hdl);
esp_err_t usbh_devs_close(usb_device_handle_t dev_hdl);
/**
* @brief Mark that all devices should be freed at the next possible opportunity
* @brief Trigger a USBH_EVENT_NEW_DEV event for the device
*
* A device marked as free will not be freed until the last client using the device has called usbh_dev_close()
* This is typically called after a device has been fully enumerated.
*
* @return
* - ESP_OK: There were no devices to free to begin with. Current state is all free
* - ESP_ERR_NOT_FINISHED: One or more devices still need to be freed (but have been marked "to be freed")
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_mark_all_free(void);
esp_err_t usbh_devs_new_dev_event(usb_device_handle_t dev_hdl);
// ------------------- Single Device ----------------------
// ------------------------------------------------ Device Functions ---------------------------------------------------
// ----------------------- Getters -------------------------
/**
* @brief Get a device's address
@@ -245,7 +281,8 @@ esp_err_t usbh_dev_get_addr(usb_device_handle_t dev_hdl, uint8_t *dev_addr);
/**
* @brief Get a device's information
*
* @note This function can block
* @note It is possible that the device has not been enumerated yet, thus some
* fields may be NULL.
* @param[in] dev_hdl Device handle
* @param[out] dev_info Device information
* @return esp_err_t
@@ -257,6 +294,8 @@ esp_err_t usbh_dev_get_info(usb_device_handle_t dev_hdl, usb_device_info_t *dev_
*
* - The device descriptor is cached when the device is created by the Hub driver
*
* @note It is possible that the device has not been enumerated yet, thus the
* device descriptor could be NULL.
* @param[in] dev_hdl Device handle
* @param[out] dev_desc_ret Device descriptor
* @return esp_err_t
@@ -268,21 +307,108 @@ esp_err_t usbh_dev_get_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t
*
* Simply returns a reference to the internally cached configuration descriptor
*
* @note This function can block
* @note It is possible that the device has not been enumerated yet, thus the
* configuration descriptor could be NULL.
* @param[in] dev_hdl Device handle
* @param config_desc_ret
* @return esp_err_t
*/
esp_err_t usbh_dev_get_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t **config_desc_ret);
// ----------------------- Setters -------------------------
/**
* @brief Submit a control transfer (URB) to a device
* @brief Lock a device for enumeration
*
* - A device's enumeration lock must be set before any of its enumeration fields
* (e.g., address, device/config descriptors) can be set/updated.
* - The caller must be the sole opener of the device (see 'usbh_devs_open()')
* when locking the device for enumeration.
*
* @param[in] dev_hdl Device handle
* @param[in] urb URB
* @return esp_err_t
*/
esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb);
esp_err_t usbh_dev_enum_lock(usb_device_handle_t dev_hdl);
/**
* @brief Release a device's enumeration lock
*
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_dev_enum_unlock(usb_device_handle_t dev_hdl);
/**
* @brief Set the maximum packet size of EP0 for a device
*
* Typically called during enumeration after obtaining the first 8 bytes of the
* device's descriptor.
*
* @note The device's enumeration lock must be set before calling this function
* (see 'usbh_dev_enum_lock()')
* @param[in] dev_hdl Device handle
* @param[in] wMaxPacketSize Maximum packet size
* @return esp_err_t
*/
esp_err_t usbh_dev_set_ep0_mps(usb_device_handle_t dev_hdl, uint16_t wMaxPacketSize);
/**
* @brief Set a device's address
*
* Typically called during enumeration after a SET_ADDRESSS request has be
* sent to the device.
*
* @note The device's enumeration lock must be set before calling this function
* (see 'usbh_dev_enum_lock()')
* @param[in] dev_hdl Device handle
* @param[in] dev_addr
* @return esp_err_t
*/
esp_err_t usbh_dev_set_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr);
/**
* @brief Set a device's descriptor
*
* Typically called during enumeration after obtaining the device's descriptor
* via a GET_DESCRIPTOR request.
*
* @note The device's enumeration lock must be set before calling this function
* (see 'usbh_dev_enum_lock()')
* @param[in] dev_hdl Device handle
* @param[in] device_desc Device descriptor to copy
* @return esp_err_t
*/
esp_err_t usbh_dev_set_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc);
/**
* @brief Set a device's configuration descriptor
*
* Typically called during enumeration after obtaining the device's configuration
* descriptor via a GET_DESCRIPTOR request.
*
* @note The device's enumeration lock must be set before calling this function
* (see 'usbh_dev_enum_lock()')
* @param[in] dev_hdl Device handle
* @param[in] config_desc_full Configuration descriptor to copy
* @return esp_err_t
*/
esp_err_t usbh_dev_set_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full);
/**
* @brief Set a device's string descriptor
*
* Typically called during enumeration after obtaining one of the device's string
* descriptor via a GET_DESCRIPTOR request.
*
* @note The device's enumeration lock must be set before calling this function
* (see 'usbh_dev_enum_lock()')
* @param[in] dev_hdl Device handle
* @param[in] str_desc String descriptor to copy
* @param[in] select Select string descriptor. 0/1/2 for Manufacturer/Product/Serial
* Number string descriptors respectively
* @return esp_err_t
*/
esp_err_t usbh_dev_set_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select);
// ----------------------------------------------- Endpoint Functions -------------------------------------------------
@@ -291,7 +417,7 @@ esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb);
*
* This function allows clients to allocate a non-default endpoint (i.e., not EP0) on a connected device
*
* - A client must have opened the device using usbh_dev_open() before attempting to allocate an endpoint on the device
* - A client must have opened the device using usbh_devs_open() before attempting to allocate an endpoint on the device
* - A client should call this function to allocate all endpoints in an interface that the client has claimed.
* - A client must allocate an endpoint using this function before attempting to communicate with it
* - Once the client allocates an endpoint, the client is now owns/manages the endpoint. No other client should use or
@@ -334,6 +460,39 @@ esp_err_t usbh_ep_free(usbh_ep_handle_t ep_hdl);
*/
esp_err_t usbh_ep_get_handle(usb_device_handle_t dev_hdl, uint8_t bEndpointAddress, usbh_ep_handle_t *ep_hdl_ret);
/**
* @brief Execute a command on a particular endpoint
*
* Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc)
*
* @param[in] ep_hdl Endpoint handle
* @param[in] command Endpoint command
* @return esp_err_t
*/
esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command);
/**
* @brief Get the context of an endpoint
*
* Get the context variable assigned to and endpoint on allocation.
*
* @note This function can block
* @param[in] ep_hdl Endpoint handle
* @return Endpoint context
*/
void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl);
// ----------------------------------------------- Transfer Functions --------------------------------------------------
/**
* @brief Submit a control transfer (URB) to a device
*
* @param[in] dev_hdl Device handle
* @param[in] urb URB
* @return esp_err_t
*/
esp_err_t usbh_dev_submit_ctrl_urb(usb_device_handle_t dev_hdl, urb_t *urb);
/**
* @brief Enqueue a URB to an endpoint
*
@@ -357,132 +516,6 @@ esp_err_t usbh_ep_enqueue_urb(usbh_ep_handle_t ep_hdl, urb_t *urb);
*/
esp_err_t usbh_ep_dequeue_urb(usbh_ep_handle_t ep_hdl, urb_t **urb_ret);
/**
* @brief Execute a command on a particular endpoint
*
* Endpoint commands allows executing a certain action on an endpoint (e.g., halting, flushing, clearing etc)
*
* @param[in] ep_hdl Endpoint handle
* @param[in] command Endpoint command
* @return esp_err_t
*/
esp_err_t usbh_ep_command(usbh_ep_handle_t ep_hdl, usbh_ep_cmd_t command);
/**
* @brief Get the context of an endpoint
*
* Get the context variable assigned to and endpoint on allocation.
*
* @note This function can block
* @param[in] ep_hdl Endpoint handle
* @return Endpoint context
*/
void *usbh_ep_get_context(usbh_ep_handle_t ep_hdl);
// -------------------------------------------------- Hub Functions ----------------------------------------------------
// ------------------- Device Related ----------------------
/**
* @brief Indicates to USBH the start of enumeration for a device
*
* - The Hub driver calls this function before it starts enumerating a new device.
* - The USBH will allocate a new device that will be initialized by the Hub driver using the remaining hub enumeration
* functions.
* - The new device's default pipe handle is returned to all the Hub driver to be used during enumeration.
*
* @note Hub Driver only
* @param[in] port_hdl Handle of the port that the device is connected to
* @param[in] dev_speed Device's speed
* @param[out] new_dev_hdl Device's handle
* @param[out] default_pipe_hdl Device's default pipe handle
* @return esp_err_t
*/
esp_err_t usbh_hub_add_dev(hcd_port_handle_t port_hdl, usb_speed_t dev_speed, usb_device_handle_t *new_dev_hdl, hcd_pipe_handle_t *default_pipe_hdl);
/**
* @brief Indicates to the USBH that a device is gone
*
* @param dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_hub_dev_gone(usb_device_handle_t dev_hdl);
// ----------------- Enumeration Related -------------------
/**
* @brief Assign the enumerating device's address
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @param dev_addr
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_fill_dev_addr(usb_device_handle_t dev_hdl, uint8_t dev_addr);
/**
* @brief Fill the enumerating device's descriptor
*
* @note Hub Driver only
* @note Must call in sequence
* @param[in] dev_hdl Device handle
* @param device_desc
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_fill_dev_desc(usb_device_handle_t dev_hdl, const usb_device_desc_t *device_desc);
/**
* @brief Fill the enumerating device's active configuration descriptor
*
* @note Hub Driver only
* @note Must call in sequence
* @note This function can block
* @param[in] dev_hdl Device handle
* @param config_desc_full
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_fill_config_desc(usb_device_handle_t dev_hdl, const usb_config_desc_t *config_desc_full);
/**
* @brief Fill one of the string descriptors of the enumerating device
*
* @note Hub Driver only
* @note Must call in sequence
* @param dev_hdl Device handle
* @param str_desc Pointer to string descriptor
* @param select Select which string descriptor. 0/1/2 for Manufacturer/Product/Serial Number string descriptors respectively
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_fill_str_desc(usb_device_handle_t dev_hdl, const usb_str_desc_t *str_desc, int select);
/**
* @brief Indicate the device enumeration is completed
*
* This will allow the device to be opened by clients, and also trigger a USBH_EVENT_NEW_DEV event.
*
* @note Hub Driver only
* @note Must call in sequence
* @note This function can block
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_done(usb_device_handle_t dev_hdl);
/**
* @brief Indicate that device enumeration has failed
*
* This will cause the enumerating device's resources to be cleaned up
* The Hub Driver must guarantee that the enumerating device's default pipe is already halted, flushed, and dequeued.
*
* @note Hub Driver only
* @note Must call in sequence
* @note This function can block
* @param[in] dev_hdl Device handle
* @return esp_err_t
*/
esp_err_t usbh_hub_enum_failed(usb_device_handle_t dev_hdl);
#ifdef __cplusplus
}
#endif

View File

@@ -148,6 +148,7 @@ typedef 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 *hub_client; // Pointer to Hub driver (acting as a client). Used to reroute completed USBH control transfers
} constant;
} host_lib_t;
@@ -171,8 +172,15 @@ static inline void _clear_client_opened_device(client_t *client_obj, uint8_t dev
static inline bool _check_client_opened_device(client_t *client_obj, uint8_t dev_addr)
{
assert(dev_addr != 0);
return (client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1)));
bool ret;
if (dev_addr != 0) {
ret = client_obj->dynamic.opened_dev_addr_map & (1 << (dev_addr - 1));
} else {
ret = false;
}
return ret;
}
static bool _unblock_client(client_t *client_obj, bool in_isr)
@@ -268,14 +276,18 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg)
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 done control transfer to the clients that submitted them
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();
// Redistribute completed control transfers to the clients that submitted them
if (event_data->ctrl_xfer_data.urb->usb_host_client == p_host_lib_obj->constant.hub_client) {
// Redistribute to Hub driver. 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: {
@@ -297,8 +309,8 @@ static void usbh_event_callback(usbh_event_data_t *event_data, void *arg)
break;
}
case USBH_EVENT_DEV_FREE: {
// Let the Hub driver know that the device is free
ESP_ERROR_CHECK(hub_dev_is_free(event_data->dev_free_data.dev_addr));
// Let the Hub driver know that the device is free and its port can be recycled
ESP_ERROR_CHECK(hub_port_recycle(event_data->dev_free_data.dev_uid));
break;
}
case USBH_EVENT_ALL_FREE: {
@@ -420,7 +432,7 @@ esp_err_t usb_host_install(const usb_host_config_t *config)
.enum_filter_cb = config->enum_filter_cb,
#endif // ENABLE_ENUM_FILTER_CALLBACK
};
ret = hub_install(&hub_config);
ret = hub_install(&hub_config, &host_lib_obj->constant.hub_client);
if (ret != ESP_OK) {
goto hub_err;
}
@@ -574,7 +586,7 @@ esp_err_t usb_host_lib_info(usb_host_lib_info_t *info_ret)
HOST_CHECK_FROM_CRIT(p_host_lib_obj != NULL, ESP_ERR_INVALID_STATE);
num_clients_temp = p_host_lib_obj->dynamic.flags.num_clients;
HOST_EXIT_CRITICAL();
usbh_num_devs(&num_devs_temp);
usbh_devs_num(&num_devs_temp);
// Write back return values
info_ret->num_devices = num_devs_temp;
@@ -820,7 +832,7 @@ esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_
esp_err_t ret;
usb_device_handle_t dev_hdl;
ret = usbh_dev_open(dev_addr, &dev_hdl);
ret = usbh_devs_open(dev_addr, &dev_hdl);
if (ret != ESP_OK) {
goto exit;
}
@@ -841,7 +853,7 @@ esp_err_t usb_host_device_open(usb_host_client_handle_t client_hdl, uint8_t dev_
return ret;
already_opened:
ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
ESP_ERROR_CHECK(usbh_devs_close(dev_hdl));
exit:
return ret;
}
@@ -883,7 +895,7 @@ esp_err_t usb_host_device_close(usb_host_client_handle_t client_hdl, usb_device_
_clear_client_opened_device(client_obj, dev_addr);
HOST_EXIT_CRITICAL();
ESP_ERROR_CHECK(usbh_dev_close(dev_hdl));
ESP_ERROR_CHECK(usbh_devs_close(dev_hdl));
ret = ESP_OK;
exit:
xSemaphoreGive(p_host_lib_obj->constant.mux_lock);
@@ -896,7 +908,7 @@ esp_err_t usb_host_device_free_all(void)
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;
ret = usbh_dev_mark_all_free();
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;
}
@@ -904,7 +916,7 @@ esp_err_t usb_host_device_free_all(void)
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_dev_addr_list_fill(list_len, dev_addr_list, num_dev_ret);
return usbh_devs_addr_list_fill(list_len, dev_addr_list, num_dev_ret);
}
// ------------------------------------------------- Device Requests ---------------------------------------------------

File diff suppressed because it is too large Load Diff