|
|
@@ -24,8 +24,6 @@
|
|
|
#include "usb_private.h"
|
|
|
#include "usb/usb_types_ch9.h"
|
|
|
|
|
|
-#include "esp_rom_sys.h"
|
|
|
-
|
|
|
// ----------------------------------------------------- Macros --------------------------------------------------------
|
|
|
|
|
|
// --------------------- Constants -------------------------
|
|
|
@@ -214,7 +212,8 @@ typedef struct {
|
|
|
union {
|
|
|
struct {
|
|
|
uint32_t executing: 1; //The buffer is currently executing
|
|
|
- uint32_t reserved7: 7;
|
|
|
+ uint32_t was_canceled: 1; //Buffer was done due to a cancellation (i.e., a halt request)
|
|
|
+ uint32_t reserved6: 6;
|
|
|
uint32_t stop_idx: 8; //The descriptor index when the channel was halted
|
|
|
hcd_pipe_event_t pipe_event: 8; //The pipe event when the buffer was done
|
|
|
uint32_t reserved8: 8;
|
|
|
@@ -257,7 +256,7 @@ struct pipe_obj {
|
|
|
//Pipe status/state/events related
|
|
|
hcd_pipe_state_t state;
|
|
|
hcd_pipe_event_t last_event;
|
|
|
- TaskHandle_t task_waiting_pipe_notif; //Task handle used for internal pipe events
|
|
|
+ volatile TaskHandle_t task_waiting_pipe_notif; //Task handle used for internal pipe events. Set by waiter, cleared by notifier
|
|
|
union {
|
|
|
struct {
|
|
|
uint32_t waiting_halt: 1;
|
|
|
@@ -290,7 +289,7 @@ struct port_obj {
|
|
|
hcd_port_state_t state;
|
|
|
usb_speed_t speed;
|
|
|
hcd_port_event_t last_event;
|
|
|
- TaskHandle_t task_waiting_port_notif; //Task handle used for internal port events
|
|
|
+ volatile TaskHandle_t task_waiting_port_notif; //Task handle used for internal port events. Set by waiter, cleared by notifier
|
|
|
union {
|
|
|
struct {
|
|
|
uint32_t event_pending: 1; //The port has an event that needs to be handled
|
|
|
@@ -423,13 +422,15 @@ static void _buffer_exec_cont(pipe_t *pipe);
|
|
|
*
|
|
|
* @param pipe Pipe object
|
|
|
* @param stop_idx Descriptor index when the buffer stopped execution
|
|
|
- * @param pipe_event Pipe event that caused the buffer to be complete
|
|
|
+ * @param pipe_event Pipe event that caused the buffer to be complete. Use HCD_PIPE_EVENT_NONE for halt request of disconnections
|
|
|
+ * @param canceled Whether the buffer was done due to a canceled (i.e., halt request). Must set pipe_event to HCD_PIPE_EVENT_NONE
|
|
|
*/
|
|
|
-static inline void _buffer_done(pipe_t *pipe, int stop_idx, hcd_pipe_event_t pipe_event)
|
|
|
+static inline void _buffer_done(pipe_t *pipe, int stop_idx, hcd_pipe_event_t pipe_event, bool canceled)
|
|
|
{
|
|
|
//Store the stop_idx and pipe_event for later parsing
|
|
|
dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx];
|
|
|
buffer_done->status_flags.executing = 0;
|
|
|
+ buffer_done->status_flags.was_canceled = canceled;
|
|
|
buffer_done->status_flags.stop_idx = stop_idx;
|
|
|
buffer_done->status_flags.pipe_event = pipe_event;
|
|
|
pipe->multi_buffer_control.rd_idx++;
|
|
|
@@ -474,11 +475,11 @@ static void _buffer_parse(pipe_t *pipe);
|
|
|
* @note This should only be called on pipes do not have any currently executing buffers.
|
|
|
*
|
|
|
* @param pipe Pipe object
|
|
|
- * @param cancelled Whether this flush is due to cancellation
|
|
|
+ * @param canceled Whether this flush is due to cancellation
|
|
|
* @return true One or more buffers were flushed
|
|
|
* @return false There were no buffers that needed to be flushed
|
|
|
*/
|
|
|
-static bool _buffer_flush_all(pipe_t *pipe, bool cancelled);
|
|
|
+static bool _buffer_flush_all(pipe_t *pipe, bool canceled);
|
|
|
|
|
|
// ------------------------ Pipe ---------------------------
|
|
|
|
|
|
@@ -689,22 +690,28 @@ static void _internal_port_event_wait(port_t *port)
|
|
|
//There must NOT be another thread/task already waiting for an internal event
|
|
|
assert(port->task_waiting_port_notif == NULL);
|
|
|
port->task_waiting_port_notif = xTaskGetCurrentTaskHandle();
|
|
|
- HCD_EXIT_CRITICAL();
|
|
|
- //Wait to be notified from ISR
|
|
|
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
|
- HCD_ENTER_CRITICAL();
|
|
|
- port->task_waiting_port_notif = NULL;
|
|
|
+ /* We need to loop as task notifications can come from anywhere. If we this
|
|
|
+ was a port event notification, task_waiting_port_notif will have been cleared
|
|
|
+ by the notifier. */
|
|
|
+ while (port->task_waiting_port_notif != NULL) {
|
|
|
+ HCD_EXIT_CRITICAL();
|
|
|
+ //Wait to be notified from ISR
|
|
|
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
|
+ HCD_ENTER_CRITICAL();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static bool _internal_port_event_notify_from_isr(port_t *port)
|
|
|
{
|
|
|
//There must be a thread/task waiting for an internal event
|
|
|
assert(port->task_waiting_port_notif != NULL);
|
|
|
- BaseType_t xTaskWoken = pdFALSE;
|
|
|
+ TaskHandle_t task_to_unblock = port->task_waiting_port_notif;
|
|
|
+ //Clear task_waiting_port_notif to indicate to the waiter that the unblock was indeed an port event notification
|
|
|
+ port->task_waiting_port_notif = NULL;
|
|
|
//Unblock the thread/task waiting for the notification
|
|
|
- HCD_EXIT_CRITICAL_ISR();
|
|
|
- vTaskNotifyGiveFromISR(port->task_waiting_port_notif, &xTaskWoken);
|
|
|
- HCD_ENTER_CRITICAL_ISR();
|
|
|
+ BaseType_t xTaskWoken = pdFALSE;
|
|
|
+ //Note: We don't exit the critical section to be atomic. vTaskNotifyGiveFromISR() doesn't block anyways
|
|
|
+ vTaskNotifyGiveFromISR(task_to_unblock, &xTaskWoken);
|
|
|
return (xTaskWoken == pdTRUE);
|
|
|
}
|
|
|
|
|
|
@@ -713,28 +720,34 @@ static void _internal_pipe_event_wait(pipe_t *pipe)
|
|
|
//There must NOT be another thread/task already waiting for an internal event
|
|
|
assert(pipe->task_waiting_pipe_notif == NULL);
|
|
|
pipe->task_waiting_pipe_notif = xTaskGetCurrentTaskHandle();
|
|
|
- HCD_EXIT_CRITICAL();
|
|
|
- //Wait to be notified from ISR
|
|
|
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
|
- HCD_ENTER_CRITICAL();
|
|
|
- pipe->task_waiting_pipe_notif = NULL;
|
|
|
+ /* We need to loop as task notifications can come from anywhere. If we this
|
|
|
+ was a pipe event notification, task_waiting_pipe_notif will have been cleared
|
|
|
+ by the notifier. */
|
|
|
+ while (pipe->task_waiting_pipe_notif != NULL) {
|
|
|
+ //Wait to be unblocked by notified
|
|
|
+ HCD_EXIT_CRITICAL();
|
|
|
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
|
+ HCD_ENTER_CRITICAL();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static bool _internal_pipe_event_notify(pipe_t *pipe, bool from_isr)
|
|
|
{
|
|
|
//There must be a thread/task waiting for an internal event
|
|
|
assert(pipe->task_waiting_pipe_notif != NULL);
|
|
|
+ TaskHandle_t task_to_unblock = pipe->task_waiting_pipe_notif;
|
|
|
+ //Clear task_waiting_pipe_notif to indicate to the waiter that the unblock was indeed an pipe event notification
|
|
|
+ pipe->task_waiting_pipe_notif = NULL;
|
|
|
bool ret;
|
|
|
if (from_isr) {
|
|
|
BaseType_t xTaskWoken = pdFALSE;
|
|
|
- HCD_EXIT_CRITICAL_ISR();
|
|
|
+ //Note: We don't exit the critical section to be atomic. vTaskNotifyGiveFromISR() doesn't block anyways
|
|
|
//Unblock the thread/task waiting for the pipe notification
|
|
|
- vTaskNotifyGiveFromISR(pipe->task_waiting_pipe_notif, &xTaskWoken);
|
|
|
- HCD_ENTER_CRITICAL_ISR();
|
|
|
+ vTaskNotifyGiveFromISR(task_to_unblock, &xTaskWoken);
|
|
|
ret = (xTaskWoken == pdTRUE);
|
|
|
} else {
|
|
|
HCD_EXIT_CRITICAL();
|
|
|
- xTaskNotifyGive(pipe->task_waiting_pipe_notif);
|
|
|
+ xTaskNotifyGive(task_to_unblock);
|
|
|
HCD_ENTER_CRITICAL();
|
|
|
ret = false;
|
|
|
}
|
|
|
@@ -836,7 +849,7 @@ static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj,
|
|
|
event = pipe->last_event;
|
|
|
//Mark the buffer as done
|
|
|
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj);
|
|
|
- _buffer_done(pipe, stop_idx, pipe->last_event);
|
|
|
+ _buffer_done(pipe, stop_idx, pipe->last_event, false);
|
|
|
//First check if there is another buffer we can execute. But we only want to execute if there's still a valid device
|
|
|
if (_buffer_can_exec(pipe) && pipe->port->flags.conn_dev_ena) {
|
|
|
//If the next buffer is filled and ready to execute, execute it
|
|
|
@@ -854,13 +867,12 @@ static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj,
|
|
|
case USBH_HAL_CHAN_EVENT_ERROR: {
|
|
|
//Get and store the pipe error event
|
|
|
usbh_hal_chan_error_t chan_error = usbh_hal_chan_get_error(chan_obj);
|
|
|
- usbh_hal_chan_clear_error(chan_obj);
|
|
|
pipe->last_event = pipe_decode_error_event(chan_error);
|
|
|
event = pipe->last_event;
|
|
|
pipe->state = HCD_PIPE_STATE_HALTED;
|
|
|
//Mark the buffer as done with an error
|
|
|
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj);
|
|
|
- _buffer_done(pipe, stop_idx, pipe->last_event);
|
|
|
+ _buffer_done(pipe, stop_idx, pipe->last_event, false);
|
|
|
//Parse the buffer
|
|
|
_buffer_parse(pipe);
|
|
|
break;
|
|
|
@@ -873,7 +885,7 @@ static hcd_pipe_event_t _intr_hdlr_chan(pipe_t *pipe, usbh_hal_chan_t *chan_obj,
|
|
|
//Halt request event is triggered when packet is successful completed. But just treat all halted transfers as errors
|
|
|
pipe->state = HCD_PIPE_STATE_HALTED;
|
|
|
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj);
|
|
|
- _buffer_done(pipe, stop_idx, HCD_PIPE_EVENT_NONE);
|
|
|
+ _buffer_done(pipe, stop_idx, HCD_PIPE_EVENT_NONE, true);
|
|
|
//Parse the buffer
|
|
|
_buffer_parse(pipe);
|
|
|
//Notify the task waiting for the pipe halt
|
|
|
@@ -1717,17 +1729,23 @@ static void pipe_set_ep_char(const hcd_pipe_config_t *pipe_config, usb_transfer_
|
|
|
static esp_err_t _pipe_cmd_halt(pipe_t *pipe)
|
|
|
{
|
|
|
esp_err_t ret;
|
|
|
- //Pipe must be in the active state in order to be halted
|
|
|
- if (pipe->state != HCD_PIPE_STATE_ACTIVE) {
|
|
|
- ret = ESP_ERR_INVALID_STATE;
|
|
|
+
|
|
|
+ //If pipe is already halted, just return.
|
|
|
+ if (pipe->state == HCD_PIPE_STATE_HALTED) {
|
|
|
+ ret = ESP_OK;
|
|
|
goto exit;
|
|
|
}
|
|
|
- //Request that the channel halts
|
|
|
- if (!usbh_hal_chan_request_halt(pipe->chan_obj)) {
|
|
|
- //We need to wait for channel to be halted. State will be updated in the ISR
|
|
|
- pipe->cs_flags.waiting_halt = 1;
|
|
|
- _internal_pipe_event_wait(pipe);
|
|
|
+ //If the pipe's port is invalid, we just mark the pipe as halted without needing to halt the underlying channel
|
|
|
+ if (pipe->port->flags.conn_dev_ena //Skip halting the underlying channel if the port is invalid
|
|
|
+ && !usbh_hal_chan_request_halt(pipe->chan_obj)) { //Check if the channel is already halted
|
|
|
+ //Channel is not halted, we need to request and wait for a haltWe need to wait for channel to be halted.
|
|
|
+ pipe->cs_flags.waiting_halt = 1;
|
|
|
+ _internal_pipe_event_wait(pipe);
|
|
|
+ //State should have been updated in the ISR
|
|
|
+ assert(pipe->state == HCD_PIPE_STATE_HALTED);
|
|
|
} else {
|
|
|
+ //We are already halted, just need to update the state
|
|
|
+ usbh_hal_chan_mark_halted(pipe->chan_obj);
|
|
|
pipe->state = HCD_PIPE_STATE_HALTED;
|
|
|
}
|
|
|
ret = ESP_OK;
|
|
|
@@ -1743,11 +1761,11 @@ static esp_err_t _pipe_cmd_flush(pipe_t *pipe)
|
|
|
ret = ESP_ERR_INVALID_STATE;
|
|
|
goto exit;
|
|
|
}
|
|
|
- //Cannot have a currently executing buffer
|
|
|
- assert(!pipe->multi_buffer_control.buffer_is_executing);
|
|
|
+ //If the port is still valid, we are canceling transfers. Otherwise, we are flushing due to a port error
|
|
|
+ bool canceled = pipe->port->flags.conn_dev_ena;
|
|
|
bool call_pipe_cb;
|
|
|
//Flush any filled buffers
|
|
|
- call_pipe_cb = _buffer_flush_all(pipe, true);
|
|
|
+ call_pipe_cb = _buffer_flush_all(pipe, canceled);
|
|
|
//Move all URBs from the pending tailq to the done tailq
|
|
|
if (pipe->num_urb_pending > 0) {
|
|
|
//Process all remaining pending URBs
|
|
|
@@ -1755,14 +1773,14 @@ static esp_err_t _pipe_cmd_flush(pipe_t *pipe)
|
|
|
TAILQ_FOREACH(urb, &pipe->pending_urb_tailq, tailq_entry) {
|
|
|
//Update the URB's current state
|
|
|
urb->hcd_var = URB_HCD_STATE_DONE;
|
|
|
- //We are canceling the URB. Update its actual_num_bytes and status
|
|
|
+ //URBs were never executed, Update the actual_num_bytes and status
|
|
|
urb->transfer.actual_num_bytes = 0;
|
|
|
- urb->transfer.status = USB_TRANSFER_STATUS_CANCELED;
|
|
|
+ urb->transfer.status = (canceled) ? USB_TRANSFER_STATUS_CANCELED : USB_TRANSFER_STATUS_NO_DEVICE;
|
|
|
if (pipe->ep_char.type == USB_PRIV_XFER_TYPE_ISOCHRONOUS) {
|
|
|
//Update the URB's isoc packet descriptors as well
|
|
|
for (int pkt_idx = 0; pkt_idx < urb->transfer.num_isoc_packets; pkt_idx++) {
|
|
|
urb->transfer.isoc_packet_desc[pkt_idx].actual_num_bytes = 0;
|
|
|
- urb->transfer.isoc_packet_desc[pkt_idx].status = USB_TRANSFER_STATUS_CANCELED;
|
|
|
+ urb->transfer.isoc_packet_desc[pkt_idx].status = (canceled) ? USB_TRANSFER_STATUS_CANCELED : USB_TRANSFER_STATUS_NO_DEVICE;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -2011,7 +2029,6 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command)
|
|
|
pipe_t *pipe = (pipe_t *)pipe_hdl;
|
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
|
|
- xSemaphoreTake(pipe->port->port_mux, portMAX_DELAY);
|
|
|
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) {
|
|
|
@@ -2035,7 +2052,6 @@ esp_err_t hcd_pipe_command(hcd_pipe_handle_t pipe_hdl, hcd_pipe_cmd_t command)
|
|
|
pipe->cs_flags.pipe_cmd_processing = 0;
|
|
|
}
|
|
|
HCD_EXIT_CRITICAL();
|
|
|
- xSemaphoreGive(pipe->port->port_mux);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
@@ -2167,6 +2183,7 @@ static void _buffer_fill(pipe_t *pipe)
|
|
|
//Select the inactive buffer
|
|
|
assert(pipe->multi_buffer_control.buffer_num_to_exec <= NUM_BUFFERS);
|
|
|
dma_buffer_block_t *buffer_to_fill = pipe->buffers[pipe->multi_buffer_control.wr_idx];
|
|
|
+ buffer_to_fill->status_flags.val = 0; //Clear the buffer's status flags
|
|
|
assert(buffer_to_fill->urb == NULL);
|
|
|
bool is_in = pipe->ep_char.bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
|
|
|
int mps = pipe->ep_char.mps;
|
|
|
@@ -2419,16 +2436,13 @@ static inline void _buffer_parse_isoc(dma_buffer_block_t *buffer, bool is_in)
|
|
|
|
|
|
static inline void _buffer_parse_error(dma_buffer_block_t *buffer)
|
|
|
{
|
|
|
- //The URB had an error, so we consider that NO bytes were transferred. Set actual_num_bytes to zero
|
|
|
+ //The URB had an error in one of its packet, or a port error), so we the entire URB an error.
|
|
|
usb_transfer_t *transfer = &buffer->urb->transfer;
|
|
|
transfer->actual_num_bytes = 0;
|
|
|
- for (int i = 0; i < transfer->num_isoc_packets; i++) {
|
|
|
- transfer->isoc_packet_desc[i].actual_num_bytes = 0;
|
|
|
- }
|
|
|
- //Update status of URB. Status will depend on the pipe_event
|
|
|
+ //Update the overall status of URB. Status will depend on the pipe_event
|
|
|
switch (buffer->status_flags.pipe_event) {
|
|
|
case HCD_PIPE_EVENT_NONE:
|
|
|
- transfer->status = USB_TRANSFER_STATUS_CANCELED;
|
|
|
+ transfer->status = (buffer->status_flags.was_canceled) ? USB_TRANSFER_STATUS_CANCELED : USB_TRANSFER_STATUS_NO_DEVICE;
|
|
|
break;
|
|
|
case HCD_PIPE_EVENT_ERROR_XFER:
|
|
|
transfer->status = USB_TRANSFER_STATUS_ERROR;
|
|
|
@@ -2496,12 +2510,12 @@ static void _buffer_parse(pipe_t *pipe)
|
|
|
pipe->multi_buffer_control.buffer_num_to_fill++;
|
|
|
}
|
|
|
|
|
|
-static bool _buffer_flush_all(pipe_t *pipe, bool cancelled)
|
|
|
+static bool _buffer_flush_all(pipe_t *pipe, bool canceled)
|
|
|
{
|
|
|
int cur_num_to_mark_done = pipe->multi_buffer_control.buffer_num_to_exec;
|
|
|
for (int i = 0; i < cur_num_to_mark_done; i++) {
|
|
|
//Mark any filled buffers as done
|
|
|
- _buffer_done(pipe, 0, HCD_PIPE_EVENT_NONE);
|
|
|
+ _buffer_done(pipe, 0, HCD_PIPE_EVENT_NONE, canceled);
|
|
|
}
|
|
|
int cur_num_to_parse = pipe->multi_buffer_control.buffer_num_to_parse;
|
|
|
for (int i = 0; i < cur_num_to_parse; i++) {
|