USB HCD: Add pipe persistence feature

This commit adds a pipe persistence feature allowing HCD pipes
to survive a port reset.
This commit is contained in:
Darian Leung 2021-06-10 00:15:55 +08:00
parent a413cbd784
commit d37451f2c7
3 changed files with 113 additions and 7 deletions

View File

@ -411,7 +411,6 @@ static inline bool usbh_hal_port_check_resume(usbh_hal_context_t *hal)
*/
static inline void usbh_hal_port_set_frame_list(usbh_hal_context_t *hal, uint32_t *frame_list, usb_hal_frame_list_len_t len)
{
HAL_ASSERT(!hal->flags.periodic_sched_enabled);
//Clear and save frame list
hal->periodic_frame_list = frame_list;
hal->frame_list_len = len;

View File

@ -272,7 +272,9 @@ struct pipe_obj {
uint32_t paused: 1;
uint32_t pipe_cmd_processing: 1;
uint32_t is_active: 1;
uint32_t reserved28: 28;
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 reserved26: 26;
};
uint32_t val;
} cs_flags;
@ -596,6 +598,28 @@ static bool _port_pause_all_pipes(port_t *port);
*/
static void _port_unpause_all_pipes(port_t *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 Send a reset condition on a port's bus
*
@ -1203,6 +1227,43 @@ static void _port_unpause_all_pipes(port_t *port)
}
}
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;
usbh_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;
usbh_hal_chan_alloc(port->hal, pipe->chan_obj, (void *)pipe);
usbh_hal_chan_set_ep_char(port->hal, pipe->chan_obj, &pipe->ep_char);
}
}
static bool _port_bus_reset(port_t *port)
{
assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED);
@ -1409,6 +1470,14 @@ esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command)
case HCD_PORT_CMD_RESET: {
//Port can only a reset when it is in the enabled or disabled states (in case of new connection)
if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED) {
bool is_runtime_reset = (port->state == HCD_PORT_STATE_ENABLED) ? true : false;
if (is_runtime_reset) {
//Check all pipes that are still allocated are persistent before we execute the reset
if (!_port_persist_all_pipes(port)) {
ret = ESP_ERR_INVALID_STATE;
break;
}
}
if (_port_bus_reset(port)) {
//Set FIFO sizes to default
usbh_hal_set_fifo_size(port->hal, &fifo_config_default);
@ -1420,6 +1489,10 @@ esp_err_t hcd_port_command(hcd_port_handle_t port_hdl, hcd_port_cmd_t command)
} else {
ret = ESP_ERR_INVALID_RESPONSE;
}
if (is_runtime_reset) {
_port_recover_all_pipes(port);
}
}
break;
}
@ -1909,7 +1982,8 @@ esp_err_t hcd_pipe_free(hcd_pipe_handle_t pipe_hdl)
&& pipe->multi_buffer_control.buffer_num_to_parse == 0
&& pipe->multi_buffer_control.buffer_num_to_exec == 0
&& pipe->num_urb_pending == 0
&& pipe->num_urb_done == 0,
&& pipe->num_urb_done == 0
&& !pipe->cs_flags.reset_lock,
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);
@ -1934,7 +2008,8 @@ esp_err_t hcd_pipe_update_mps(hcd_pipe_handle_t pipe_hdl, int mps)
HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
&& !pipe->cs_flags.pipe_cmd_processing
&& pipe->num_urb_pending == 0
&& pipe->num_urb_done == 0,
&& pipe->num_urb_done == 0
&& !pipe->cs_flags.reset_lock,
ESP_ERR_INVALID_STATE);
pipe->ep_char.mps = mps;
//Update the underlying channel's registers
@ -1951,7 +2026,8 @@ esp_err_t hcd_pipe_update_dev_addr(hcd_pipe_handle_t pipe_hdl, uint8_t dev_addr)
HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
&& !pipe->cs_flags.pipe_cmd_processing
&& pipe->num_urb_pending == 0
&& pipe->num_urb_done == 0,
&& pipe->num_urb_done == 0
&& !pipe->cs_flags.reset_lock,
ESP_ERR_INVALID_STATE);
pipe->ep_char.dev_addr = dev_addr;
//Update the underlying channel's registers
@ -1960,6 +2036,22 @@ 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_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->state != HCD_PIPE_STATE_INVALID
&& !pipe->cs_flags.pipe_cmd_processing
&& pipe->num_urb_pending == 0
&& pipe->num_urb_done == 0
&& !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;
@ -1994,7 +2086,7 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command)
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.pipe_cmd_processing || !pipe->port->flags.conn_dev_ena || pipe->state == HCD_PIPE_STATE_INVALID) {
if (pipe->cs_flags.pipe_cmd_processing || pipe->cs_flags.reset_lock || !pipe->port->flags.conn_dev_ena || pipe->state == HCD_PIPE_STATE_INVALID) {
ret = ESP_ERR_INVALID_STATE;
} else {
pipe->cs_flags.pipe_cmd_processing = 1;
@ -2538,7 +2630,8 @@ 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.pipe_cmd_processing //Pipe cannot currently be processing a pipe command
&& !pipe->cs_flags.reset_lock, //Pipe cannot be persisting through a port reset
ESP_ERR_INVALID_STATE);
//Use the URB's reserved_ptr to store the pipe's
urb->hcd_ptr = (void *)pipe;

View File

@ -421,6 +421,20 @@ 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 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
*