Merge branch 'feature/hcd_pipe_persistence' into 'master'

(4) USB HCD: Add pipe persistence feature

Closes IDF-3395

See merge request espressif/esp-idf!13934
This commit is contained in:
Angus Gratton
2021-06-30 09:06:27 +00:00
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) 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 //Clear and save frame list
hal->periodic_frame_list = frame_list; hal->periodic_frame_list = frame_list;
hal->frame_list_len = len; hal->frame_list_len = len;

View File

@@ -272,7 +272,9 @@ struct pipe_obj {
uint32_t paused: 1; uint32_t paused: 1;
uint32_t pipe_cmd_processing: 1; uint32_t pipe_cmd_processing: 1;
uint32_t is_active: 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; uint32_t val;
} cs_flags; } cs_flags;
@@ -596,6 +598,28 @@ static bool _port_pause_all_pipes(port_t *port);
*/ */
static void _port_unpause_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 * @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) static bool _port_bus_reset(port_t *port)
{ {
assert(port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED); 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: { case HCD_PORT_CMD_RESET: {
//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 states (in case of new connection)
if (port->state == HCD_PORT_STATE_ENABLED || port->state == HCD_PORT_STATE_DISABLED) { 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)) { if (_port_bus_reset(port)) {
//Set FIFO sizes to default //Set FIFO sizes to default
usbh_hal_set_fifo_size(port->hal, &fifo_config_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 { } else {
ret = ESP_ERR_INVALID_RESPONSE; ret = ESP_ERR_INVALID_RESPONSE;
} }
if (is_runtime_reset) {
_port_recover_all_pipes(port);
}
} }
break; 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_parse == 0
&& pipe->multi_buffer_control.buffer_num_to_exec == 0 && pipe->multi_buffer_control.buffer_num_to_exec == 0
&& pipe->num_urb_pending == 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); 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) //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); 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 HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
&& !pipe->cs_flags.pipe_cmd_processing && !pipe->cs_flags.pipe_cmd_processing
&& pipe->num_urb_pending == 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); ESP_ERR_INVALID_STATE);
pipe->ep_char.mps = mps; pipe->ep_char.mps = mps;
//Update the underlying channel's registers //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 HCD_CHECK_FROM_CRIT(pipe->state != HCD_PIPE_STATE_INVALID
&& !pipe->cs_flags.pipe_cmd_processing && !pipe->cs_flags.pipe_cmd_processing
&& pipe->num_urb_pending == 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); ESP_ERR_INVALID_STATE);
pipe->ep_char.dev_addr = dev_addr; pipe->ep_char.dev_addr = dev_addr;
//Update the underlying channel's registers //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; 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) void *hcd_pipe_get_context(hcd_pipe_handle_t pipe_hdl)
{ {
pipe_t *pipe = (pipe_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(); 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 //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; ret = ESP_ERR_INVALID_STATE;
} else { } else {
pipe->cs_flags.pipe_cmd_processing = 1; 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 //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 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->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); ESP_ERR_INVALID_STATE);
//Use the URB's reserved_ptr to store the pipe's //Use the URB's reserved_ptr to store the pipe's
urb->hcd_ptr = (void *)pipe; 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); 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 * @brief Get the context variable of a pipe from its handle
* *