usb: Fix how the HCD handles sudden disconnection

This commit fixes how the USB Host HCD handles sudden disconnections.

Bugs:
- HW channels remain active when the port suddenly disconnects, and
previously the channel would be disabled by setting the disabled bit,
then waiting for a disabled interrupt. However, ISOC channels do not
generate the disabled interrupt when the port is invalid, thus leading
to tasks getting indefinitely blocked in hcd_pipe_command().

Fix:
On a sudden disconnection, forcibly treat all channels as halted even
if their HCCHAR.ChEna bit is still set. We do a soft reset after a port
error anyways, so the channels will eventually be reset.

Closes https://github.com/espressif/esp-idf/issues/7505
This commit is contained in:
Darian Leung
2021-10-25 21:42:09 +08:00
parent a1082dfa59
commit de6bf09f40
8 changed files with 240 additions and 153 deletions

View File

@@ -1,16 +1,8 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD /*
// * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
// Licensed under the Apache License, Version 2.0 (the "License"); *
// you may not use this file except in compliance with the License. * SPDX-License-Identifier: Apache-2.0
// You may obtain a copy of the License at */
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once #pragma once
@@ -50,17 +42,6 @@ typedef struct {
uint32_t ptx_fifo_lines; /**< Size of the Periodic FIFO in terms the number of FIFO lines */ uint32_t ptx_fifo_lines; /**< Size of the Periodic FIFO in terms the number of FIFO lines */
} usbh_hal_fifo_config_t; } usbh_hal_fifo_config_t;
// --------------------- HAL States ------------------------
/**
* @brief Channel states
*/
typedef enum {
USBH_HAL_CHAN_STATE_HALTED = 0, /**< The channel is halted. No transfer descriptor list is being executed */
USBH_HAL_CHAN_STATE_ACTIVE, /**< The channel is active. A transfer descriptor list is being executed */
USBH_HAL_CHAN_STATE_ERROR, /**< The channel is in the error state */
} usbh_hal_chan_state_t;
// --------------------- HAL Events ------------------------ // --------------------- HAL Events ------------------------
/** /**
@@ -153,8 +134,7 @@ typedef struct {
struct { struct {
uint32_t active: 1; /**< Debugging bit to indicate whether channel is enabled */ uint32_t active: 1; /**< Debugging bit to indicate whether channel is enabled */
uint32_t halt_requested: 1; /**< A halt has been requested */ uint32_t halt_requested: 1; /**< A halt has been requested */
uint32_t error_pending: 1; /**< The channel is waiting for the error to be handled */ uint32_t reserved: 2;
uint32_t reserved: 1;
uint32_t chan_idx: 4; /**< The index number of the channel */ uint32_t chan_idx: 4; /**< The index number of the channel */
uint32_t reserved24: 24; uint32_t reserved24: 24;
}; };
@@ -556,23 +536,6 @@ static inline void *usbh_hal_chan_get_context(usbh_hal_chan_t *chan_obj)
return chan_obj->chan_ctx; return chan_obj->chan_ctx;
} }
/**
* @brief Get the current state of a channel
*
* @param chan_obj Channel object
* @return usbh_hal_chan_state_t State of the channel
*/
static inline usbh_hal_chan_state_t usbh_hal_chan_get_state(usbh_hal_chan_t *chan_obj)
{
if (chan_obj->flags.error_pending) {
return USBH_HAL_CHAN_STATE_ERROR;
} else if (chan_obj->flags.active) {
return USBH_HAL_CHAN_STATE_ACTIVE;
} else {
return USBH_HAL_CHAN_STATE_HALTED;
}
}
/** /**
* @brief Set the endpoint information for a particular channel * @brief Set the endpoint information for a particular channel
* *
@@ -602,7 +565,7 @@ void usbh_hal_chan_set_ep_char(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_ob
static inline void usbh_hal_chan_set_dir(usbh_hal_chan_t *chan_obj, bool is_in) static inline void usbh_hal_chan_set_dir(usbh_hal_chan_t *chan_obj, bool is_in)
{ {
//Cannot change direction whilst channel is still active or in error //Cannot change direction whilst channel is still active or in error
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
usbh_ll_chan_set_dir(chan_obj->regs, is_in); usbh_ll_chan_set_dir(chan_obj->regs, is_in);
} }
@@ -621,7 +584,7 @@ static inline void usbh_hal_chan_set_dir(usbh_hal_chan_t *chan_obj, bool is_in)
static inline void usbh_hal_chan_set_pid(usbh_hal_chan_t *chan_obj, int pid) static inline void usbh_hal_chan_set_pid(usbh_hal_chan_t *chan_obj, int pid)
{ {
//Cannot change pid whilst channel is still active or in error //Cannot change pid whilst channel is still active or in error
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
//Update channel object and set the register //Update channel object and set the register
usbh_ll_chan_set_pid(chan_obj->regs, pid); usbh_ll_chan_set_pid(chan_obj->regs, pid);
} }
@@ -638,7 +601,7 @@ static inline void usbh_hal_chan_set_pid(usbh_hal_chan_t *chan_obj, int pid)
*/ */
static inline uint32_t usbh_hal_chan_get_pid(usbh_hal_chan_t *chan_obj) static inline uint32_t usbh_hal_chan_get_pid(usbh_hal_chan_t *chan_obj)
{ {
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
return usbh_ll_chan_get_pid(chan_obj->regs); return usbh_ll_chan_get_pid(chan_obj->regs);
} }
@@ -687,6 +650,25 @@ static inline int usbh_hal_chan_get_qtd_idx(usbh_hal_chan_t *chan_obj)
*/ */
bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj); bool usbh_hal_chan_request_halt(usbh_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):
* - Any previously active channels will remain active (i.e., they will not receive a channel interrupt)
* - Attempting to disable them using usbh_hal_chan_request_halt() will NOT generate an interrupt for ISOC channels
* (probalby 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
* freed).
*
* @param chan_obj Channel object
*/
static inline void usbh_hal_chan_mark_halted(usbh_hal_chan_t *chan_obj)
{
chan_obj->flags.active = 0;
}
/** /**
* @brief Get a channel's error * @brief Get a channel's error
* *
@@ -695,22 +677,9 @@ bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj);
*/ */
static inline usbh_hal_chan_error_t usbh_hal_chan_get_error(usbh_hal_chan_t *chan_obj) static inline usbh_hal_chan_error_t usbh_hal_chan_get_error(usbh_hal_chan_t *chan_obj)
{ {
HAL_ASSERT(chan_obj->flags.error_pending);
return chan_obj->error; return chan_obj->error;
} }
/**
* @brief Clear a channel of it's error
*
* @param chan_obj Channel object
*/
static inline void usbh_hal_chan_clear_error(usbh_hal_chan_t *chan_obj)
{
//Can only clear error when an error has occurred
HAL_ASSERT(chan_obj->flags.error_pending);
chan_obj->flags.error_pending = 0;
}
// -------------------------------------------- Transfer Descriptor List ----------------------------------------------- // -------------------------------------------- Transfer Descriptor List -----------------------------------------------
/** /**

View File

@@ -1,16 +1,8 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD /*
// * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
// Licensed under the Apache License, Version 2.0 (the "License"); *
// you may not use this file except in compliance with the License. * SPDX-License-Identifier: Apache-2.0
// You may obtain a copy of the License at */
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
@@ -238,7 +230,7 @@ void usbh_hal_chan_free(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj)
} }
} }
//Can only free a channel when in the disabled state and descriptor list released //Can only free a channel when in the disabled state and descriptor list released
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
//Disable channel's interrupt //Disable channel's interrupt
usbh_ll_haintmsk_dis_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx); usbh_ll_haintmsk_dis_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx);
//Deallocate channel //Deallocate channel
@@ -252,7 +244,7 @@ void usbh_hal_chan_free(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj)
void usbh_hal_chan_set_ep_char(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, usbh_hal_ep_char_t *ep_char) void usbh_hal_chan_set_ep_char(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, usbh_hal_ep_char_t *ep_char)
{ {
//Cannot change ep_char whilst channel is still active or in error //Cannot change ep_char whilst channel is still active or in error
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
//Set the endpoint characteristics of the pipe //Set the endpoint characteristics of the pipe
usbh_ll_chan_hcchar_init(chan_obj->regs, usbh_ll_chan_hcchar_init(chan_obj->regs,
ep_char->dev_addr, ep_char->dev_addr,
@@ -280,7 +272,7 @@ void usbh_hal_chan_set_ep_char(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_ob
void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, int start_idx) void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int desc_list_len, int start_idx)
{ {
//Cannot activate a channel that has already been enabled or is pending error handling //Cannot activate a channel that has already been enabled or is pending error handling
HAL_ASSERT(!chan_obj->flags.active && !chan_obj->flags.error_pending); HAL_ASSERT(!chan_obj->flags.active);
//Set start address of the QTD list and starting QTD index //Set start address of the QTD list and starting QTD index
usbh_ll_chan_set_dma_addr_non_iso(chan_obj->regs, xfer_desc_list, start_idx); usbh_ll_chan_set_dma_addr_non_iso(chan_obj->regs, xfer_desc_list, start_idx);
usbh_ll_chan_set_qtd_list_len(chan_obj->regs, desc_list_len); usbh_ll_chan_set_qtd_list_len(chan_obj->regs, desc_list_len);
@@ -291,12 +283,14 @@ void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, void *xfer_desc_list, int
bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj) bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj)
{ {
//Cannot request halt on a channel that is pending error handling //Cannot request halt on a channel that is pending error handling
HAL_ASSERT(chan_obj->flags.active && !chan_obj->flags.error_pending);
if (usbh_ll_chan_is_active(chan_obj->regs)) { if (usbh_ll_chan_is_active(chan_obj->regs)) {
//If the register indicates that the channel is still active, the active flag must have been previously set
HAL_ASSERT(chan_obj->flags.active);
usbh_ll_chan_halt(chan_obj->regs); usbh_ll_chan_halt(chan_obj->regs);
chan_obj->flags.halt_requested = 1; chan_obj->flags.halt_requested = 1;
return false; return false;
} else { } else {
//We just clear the active flag here as it could still be set (if we have a pending channel interrupt)
chan_obj->flags.active = 0; chan_obj->flags.active = 0;
return true; return true;
} }
@@ -366,6 +360,7 @@ usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj)
{ {
uint32_t chan_intrs = usbh_ll_chan_intr_read_and_clear(chan_obj->regs); uint32_t chan_intrs = usbh_ll_chan_intr_read_and_clear(chan_obj->regs);
usbh_hal_chan_event_t chan_event; usbh_hal_chan_event_t chan_event;
//Note: We don't assert on (chan_obj->flags.active) here as it could have been already cleared by usbh_hal_chan_request_halt()
if (chan_intrs & CHAN_INTRS_ERROR_MSK) { //Note: Errors are uncommon, so we check against the entire interrupt mask to reduce frequency of entering this call path if (chan_intrs & CHAN_INTRS_ERROR_MSK) { //Note: Errors are uncommon, so we check against the entire interrupt mask to reduce frequency of entering this call path
HAL_ASSERT(chan_intrs & USBH_LL_INTR_CHAN_CHHLTD); //An error should have halted the channel HAL_ASSERT(chan_intrs & USBH_LL_INTR_CHAN_CHHLTD); //An error should have halted the channel
@@ -383,7 +378,6 @@ usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj)
//Update flags //Update flags
chan_obj->error = error; chan_obj->error = error;
chan_obj->flags.active = 0; chan_obj->flags.active = 0;
chan_obj->flags.error_pending = 1;
//Save the error to be handled later //Save the error to be handled later
chan_event = USBH_HAL_CHAN_EVENT_ERROR; chan_event = USBH_HAL_CHAN_EVENT_ERROR;
} else if (chan_intrs & USBH_LL_INTR_CHAN_CHHLTD) { } else if (chan_intrs & USBH_LL_INTR_CHAN_CHHLTD) {

View File

@@ -24,8 +24,6 @@
#include "usb_private.h" #include "usb_private.h"
#include "usb/usb_types_ch9.h" #include "usb/usb_types_ch9.h"
#include "esp_rom_sys.h"
// ----------------------------------------------------- Macros -------------------------------------------------------- // ----------------------------------------------------- Macros --------------------------------------------------------
// --------------------- Constants ------------------------- // --------------------- Constants -------------------------
@@ -214,7 +212,8 @@ typedef struct {
union { union {
struct { struct {
uint32_t executing: 1; //The buffer is currently executing 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 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 hcd_pipe_event_t pipe_event: 8; //The pipe event when the buffer was done
uint32_t reserved8: 8; uint32_t reserved8: 8;
@@ -257,7 +256,7 @@ struct pipe_obj {
//Pipe status/state/events related //Pipe status/state/events related
hcd_pipe_state_t state; hcd_pipe_state_t state;
hcd_pipe_event_t last_event; 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 { union {
struct { struct {
uint32_t waiting_halt: 1; uint32_t waiting_halt: 1;
@@ -290,7 +289,7 @@ struct port_obj {
hcd_port_state_t state; hcd_port_state_t state;
usb_speed_t speed; usb_speed_t speed;
hcd_port_event_t last_event; 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 { union {
struct { struct {
uint32_t event_pending: 1; //The port has an event that needs to be handled 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 pipe Pipe object
* @param stop_idx Descriptor index when the buffer stopped execution * @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 //Store the stop_idx and pipe_event for later parsing
dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx]; dma_buffer_block_t *buffer_done = pipe->buffers[pipe->multi_buffer_control.rd_idx];
buffer_done->status_flags.executing = 0; 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.stop_idx = stop_idx;
buffer_done->status_flags.pipe_event = pipe_event; buffer_done->status_flags.pipe_event = pipe_event;
pipe->multi_buffer_control.rd_idx++; 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. * @note This should only be called on pipes do not have any currently executing buffers.
* *
* @param pipe Pipe object * @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 true One or more buffers were flushed
* @return false There were no buffers that needed to be 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 --------------------------- // ------------------------ 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 //There must NOT be another thread/task already waiting for an internal event
assert(port->task_waiting_port_notif == NULL); assert(port->task_waiting_port_notif == NULL);
port->task_waiting_port_notif = xTaskGetCurrentTaskHandle(); port->task_waiting_port_notif = xTaskGetCurrentTaskHandle();
HCD_EXIT_CRITICAL(); /* We need to loop as task notifications can come from anywhere. If we this
//Wait to be notified from ISR was a port event notification, task_waiting_port_notif will have been cleared
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); by the notifier. */
HCD_ENTER_CRITICAL(); while (port->task_waiting_port_notif != NULL) {
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) static bool _internal_port_event_notify_from_isr(port_t *port)
{ {
//There must be a thread/task waiting for an internal event //There must be a thread/task waiting for an internal event
assert(port->task_waiting_port_notif != NULL); 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 //Unblock the thread/task waiting for the notification
HCD_EXIT_CRITICAL_ISR(); BaseType_t xTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(port->task_waiting_port_notif, &xTaskWoken); //Note: We don't exit the critical section to be atomic. vTaskNotifyGiveFromISR() doesn't block anyways
HCD_ENTER_CRITICAL_ISR(); vTaskNotifyGiveFromISR(task_to_unblock, &xTaskWoken);
return (xTaskWoken == pdTRUE); 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 //There must NOT be another thread/task already waiting for an internal event
assert(pipe->task_waiting_pipe_notif == NULL); assert(pipe->task_waiting_pipe_notif == NULL);
pipe->task_waiting_pipe_notif = xTaskGetCurrentTaskHandle(); pipe->task_waiting_pipe_notif = xTaskGetCurrentTaskHandle();
HCD_EXIT_CRITICAL(); /* We need to loop as task notifications can come from anywhere. If we this
//Wait to be notified from ISR was a pipe event notification, task_waiting_pipe_notif will have been cleared
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); by the notifier. */
HCD_ENTER_CRITICAL(); while (pipe->task_waiting_pipe_notif != NULL) {
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) static bool _internal_pipe_event_notify(pipe_t *pipe, bool from_isr)
{ {
//There must be a thread/task waiting for an internal event //There must be a thread/task waiting for an internal event
assert(pipe->task_waiting_pipe_notif != NULL); 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; bool ret;
if (from_isr) { if (from_isr) {
BaseType_t xTaskWoken = pdFALSE; 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 //Unblock the thread/task waiting for the pipe notification
vTaskNotifyGiveFromISR(pipe->task_waiting_pipe_notif, &xTaskWoken); vTaskNotifyGiveFromISR(task_to_unblock, &xTaskWoken);
HCD_ENTER_CRITICAL_ISR();
ret = (xTaskWoken == pdTRUE); ret = (xTaskWoken == pdTRUE);
} else { } else {
HCD_EXIT_CRITICAL(); HCD_EXIT_CRITICAL();
xTaskNotifyGive(pipe->task_waiting_pipe_notif); xTaskNotifyGive(task_to_unblock);
HCD_ENTER_CRITICAL(); HCD_ENTER_CRITICAL();
ret = false; 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; event = pipe->last_event;
//Mark the buffer as done //Mark the buffer as done
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); 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 //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 (_buffer_can_exec(pipe) && pipe->port->flags.conn_dev_ena) {
//If the next buffer is filled and ready to execute, execute it //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: { case USBH_HAL_CHAN_EVENT_ERROR: {
//Get and store the pipe error event //Get and store the pipe error event
usbh_hal_chan_error_t chan_error = usbh_hal_chan_get_error(chan_obj); 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); pipe->last_event = pipe_decode_error_event(chan_error);
event = pipe->last_event; event = pipe->last_event;
pipe->state = HCD_PIPE_STATE_HALTED; pipe->state = HCD_PIPE_STATE_HALTED;
//Mark the buffer as done with an error //Mark the buffer as done with an error
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); 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 //Parse the buffer
_buffer_parse(pipe); _buffer_parse(pipe);
break; 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 //Halt request event is triggered when packet is successful completed. But just treat all halted transfers as errors
pipe->state = HCD_PIPE_STATE_HALTED; pipe->state = HCD_PIPE_STATE_HALTED;
int stop_idx = usbh_hal_chan_get_qtd_idx(chan_obj); 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 //Parse the buffer
_buffer_parse(pipe); _buffer_parse(pipe);
//Notify the task waiting for the pipe halt //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) static esp_err_t _pipe_cmd_halt(pipe_t *pipe)
{ {
esp_err_t ret; esp_err_t ret;
//Pipe must be in the active state in order to be halted
if (pipe->state != HCD_PIPE_STATE_ACTIVE) { //If pipe is already halted, just return.
ret = ESP_ERR_INVALID_STATE; if (pipe->state == HCD_PIPE_STATE_HALTED) {
ret = ESP_OK;
goto exit; goto exit;
} }
//Request that the channel halts //If the pipe's port is invalid, we just mark the pipe as halted without needing to halt the underlying channel
if (!usbh_hal_chan_request_halt(pipe->chan_obj)) { if (pipe->port->flags.conn_dev_ena //Skip halting the underlying channel if the port is invalid
//We need to wait for channel to be halted. State will be updated in the ISR && !usbh_hal_chan_request_halt(pipe->chan_obj)) { //Check if the channel is already halted
pipe->cs_flags.waiting_halt = 1; //Channel is not halted, we need to request and wait for a haltWe need to wait for channel to be halted.
_internal_pipe_event_wait(pipe); 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 { } 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; pipe->state = HCD_PIPE_STATE_HALTED;
} }
ret = ESP_OK; ret = ESP_OK;
@@ -1743,11 +1761,11 @@ static esp_err_t _pipe_cmd_flush(pipe_t *pipe)
ret = ESP_ERR_INVALID_STATE; ret = ESP_ERR_INVALID_STATE;
goto exit; goto exit;
} }
//Cannot have a currently executing buffer //If the port is still valid, we are canceling transfers. Otherwise, we are flushing due to a port error
assert(!pipe->multi_buffer_control.buffer_is_executing); bool canceled = pipe->port->flags.conn_dev_ena;
bool call_pipe_cb; bool call_pipe_cb;
//Flush any filled buffers //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 //Move all URBs from the pending tailq to the done tailq
if (pipe->num_urb_pending > 0) { if (pipe->num_urb_pending > 0) {
//Process all remaining pending URBs //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) { TAILQ_FOREACH(urb, &pipe->pending_urb_tailq, tailq_entry) {
//Update the URB's current state //Update the URB's current state
urb->hcd_var = URB_HCD_STATE_DONE; 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.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) { if (pipe->ep_char.type == USB_PRIV_XFER_TYPE_ISOCHRONOUS) {
//Update the URB's isoc packet descriptors as well //Update the URB's isoc packet descriptors as well
for (int pkt_idx = 0; pkt_idx < urb->transfer.num_isoc_packets; pkt_idx++) { 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].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; pipe_t *pipe = (pipe_t *)pipe_hdl;
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
xSemaphoreTake(pipe->port->port_mux, portMAX_DELAY);
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.reset_lock) { 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; pipe->cs_flags.pipe_cmd_processing = 0;
} }
HCD_EXIT_CRITICAL(); HCD_EXIT_CRITICAL();
xSemaphoreGive(pipe->port->port_mux);
return ret; return ret;
} }
@@ -2167,6 +2183,7 @@ static void _buffer_fill(pipe_t *pipe)
//Select the inactive buffer //Select the inactive buffer
assert(pipe->multi_buffer_control.buffer_num_to_exec <= NUM_BUFFERS); 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]; 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); assert(buffer_to_fill->urb == NULL);
bool is_in = pipe->ep_char.bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; bool is_in = pipe->ep_char.bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
int mps = pipe->ep_char.mps; 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) 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; usb_transfer_t *transfer = &buffer->urb->transfer;
transfer->actual_num_bytes = 0; transfer->actual_num_bytes = 0;
for (int i = 0; i < transfer->num_isoc_packets; i++) { //Update the overall status of URB. Status will depend on the pipe_event
transfer->isoc_packet_desc[i].actual_num_bytes = 0;
}
//Update status of URB. Status will depend on the pipe_event
switch (buffer->status_flags.pipe_event) { switch (buffer->status_flags.pipe_event) {
case HCD_PIPE_EVENT_NONE: 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; break;
case HCD_PIPE_EVENT_ERROR_XFER: case HCD_PIPE_EVENT_ERROR_XFER:
transfer->status = USB_TRANSFER_STATUS_ERROR; 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++; 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; 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++) { for (int i = 0; i < cur_num_to_mark_done; i++) {
//Mark any filled buffers as done //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; int cur_num_to_parse = pipe->multi_buffer_control.buffer_num_to_parse;
for (int i = 0; i < cur_num_to_parse; i++) { for (int i = 0; i < cur_num_to_parse; i++) {

View File

@@ -68,6 +68,7 @@ typedef enum {
USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */ USB_TRANSFER_STATUS_STALL, /**< The transfer was stalled */
USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */ USB_TRANSFER_STATUS_OVERFLOW, /**< The transfer as more data was sent than was requested */
USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */ USB_TRANSFER_STATUS_SKIPPED, /**< ISOC packets only. The packet was skipped due to system latency or bus overload */
USB_TRANSFER_STATUS_NO_DEVICE, /**< The transfer failed because the target device is gone */
} usb_transfer_status_t; } usb_transfer_status_t;
/** /**
@@ -102,7 +103,10 @@ typedef struct {
* split into multiple packets, and each packet is transferred at the endpoint's specified interval. * split into multiple packets, and each packet is transferred at the endpoint's specified interval.
* - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer * - Isochronous: Represents a stream of bytes that should be transferred to an endpoint at a fixed rate. The transfer
* is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval * is split into packets according to the each isoc_packet_desc. A packet is transferred at each interval
* of the endpoint. * of the endpoint. If an entire ISOC URB was transferred without error (skipped packets do not count as
* errors), the URB's overall status and the status of each packet descriptor will be updated, and the
* actual_num_bytes reflects the total bytes transferred over all packets. If the ISOC URB encounters an
* error, the entire URB is considered erroneous so only the overall status will updated.
* *
* @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS * @note For Bulk/Control/Interrupt IN transfers, the num_bytes must be a integer multiple of the endpoint's MPS
* @note This structure should be allocated via usb_host_transfer_alloc() * @note This structure should be allocated via usb_host_transfer_alloc()

View File

@@ -24,7 +24,10 @@ stop the remainder of the interrupt transfer.
## Channel interrupt on port errors ## Channel interrupt on port errors
- If there are one or more channels active, and a port error interrupt occurs (such as disconnection, over-current), the active channels will not have an interrupt. Each need to be manually disabled to obtain an interrupt. - If there are one or more channels active, and a port error interrupt occurs (such as disconnection, over-current), the active channels will not have an interrupt.
- The channels will remain active (i.e., `HCCHAR.ChEna` will still be set)
- Normal methods of disabling the channel (via setting `HCCHAR.ChDis` and waiting for an interrupt) will not work for ISOC channels (the interrupt will never be generated).
- Therefore, on port errors, just treat all the channels as halted and treat their in-flight transfers as failed. The soft reset that occurs after will reset all the channel's registers.
## Reset ## Reset
@@ -66,7 +69,6 @@ The HAL layer abstracts the DWC_OTG operating in Host Mode using Internal Scatte
- If you need to halt the channel early (such as aborting a transfer), call `usbh_hal_chan_request_halt()` - If you need to halt the channel early (such as aborting a transfer), call `usbh_hal_chan_request_halt()`
- In case of a channel error event: - In case of a channel error event:
- Call `usbh_hal_chan_get_error()` to get the specific channel error that occurred - Call `usbh_hal_chan_get_error()` to get the specific channel error that occurred
- You must call `usbh_hal_chan_clear_error()` after an error to clear the error and allow the channel to continue to be used.
# Host Controller Driver (HCD) # Host Controller Driver (HCD)

View File

@@ -9,13 +9,11 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "test_utils.h" #include "test_utils.h"
#include "soc/gpio_pins.h" #include "soc/usb_wrap_struct.h"
#include "soc/gpio_sig_map.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_rom_gpio.h" #include "esp_rom_gpio.h"
#include "soc/usb_wrap_struct.h"
#include "hcd.h" #include "hcd.h"
#include "usb_private.h" #include "usb_private.h"
#include "usb/usb_types_ch9.h" #include "usb/usb_types_ch9.h"
@@ -139,18 +137,23 @@ int test_hcd_get_num_pipe_events(hcd_pipe_handle_t pipe_hdl)
void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks) void test_hcd_force_conn_state(bool connected, TickType_t delay_ticks)
{ {
vTaskDelay(delay_ticks); if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
usb_wrap_dev_t *wrap = &USB_WRAP; usb_wrap_dev_t *wrap = &USB_WRAP;
if (connected) { if (connected) {
//Swap back to internal PHY that is connected to a device //Disable test mode to return to previous internal PHY configuration
wrap->otg_conf.phy_sel = 0; wrap->test_conf.test_enable = 0;
} else { } else {
//Set external PHY input signals to fixed voltage levels mimicking a disconnected state /*
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VP_IDX, false); Mimic a disconnection by using the internal PHY's test mode.
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_EXTPHY_VM_IDX, false); Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0,
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_EXTPHY_RCV_IDX, false); this will look like a disconnection.
//Swap to the external PHY */
wrap->otg_conf.phy_sel = 1; wrap->test_conf.val = 0;
wrap->test_conf.test_usb_wrap_oe = 1;
wrap->test_conf.test_enable = 1;
} }
} }

View File

@@ -17,6 +17,7 @@
#define NUM_PACKETS_PER_URB 3 #define NUM_PACKETS_PER_URB 3
#define ISOC_PACKET_SIZE MOCK_ISOC_EP_MPS #define ISOC_PACKET_SIZE MOCK_ISOC_EP_MPS
#define URB_DATA_BUFF_SIZE (NUM_PACKETS_PER_URB * ISOC_PACKET_SIZE) #define URB_DATA_BUFF_SIZE (NUM_PACKETS_PER_URB * ISOC_PACKET_SIZE)
#define POST_ENQUEUE_DELAY_US 20
/* /*
Test HCD ISOC pipe URBs Test HCD ISOC pipe URBs
@@ -78,6 +79,9 @@ TEST_CASE("Test HCD isochronous pipe URBs", "[hcd][ignore]")
urb_t *urb = hcd_urb_dequeue(isoc_out_pipe); urb_t *urb = hcd_urb_dequeue(isoc_out_pipe);
TEST_ASSERT_EQUAL(urb_list[urb_idx], urb); TEST_ASSERT_EQUAL(urb_list[urb_idx], urb);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context); TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
//Overall URB status and overall number of bytes
TEST_ASSERT_EQUAL(URB_DATA_BUFF_SIZE, urb->transfer.actual_num_bytes);
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.status);
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) { for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) {
TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.isoc_packet_desc[pkt_idx].status); TEST_ASSERT_EQUAL(USB_TRANSFER_STATUS_COMPLETED, urb->transfer.isoc_packet_desc[pkt_idx].status);
} }
@@ -92,3 +96,98 @@ TEST_CASE("Test HCD isochronous pipe URBs", "[hcd][ignore]")
test_hcd_wait_for_disconn(port_hdl, false); test_hcd_wait_for_disconn(port_hdl, false);
test_hcd_teardown(port_hdl); test_hcd_teardown(port_hdl);
} }
/*
Test a port sudden disconnect with an active ISOC pipe
Purpose: Test that when sudden disconnection happens on an HCD port, the ISOC pipe will
- Remain active after the HCD_PORT_EVENT_SUDDEN_DISCONN port event
- ISOC pipe can be halted
- ISOC pipe can be flushed (and transfers status are updated accordingly)
Procedure:
- Setup HCD and wait for connection
- Allocate default pipe and enumerate the device
- Allocate an isochronous pipe and multiple URBs. Each URB should contain multiple packets to test HCD's ability to
schedule an URB across multiple intervals.
- Enqueue those URBs
- Trigger a disconnect after a short delay
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event.
- Check that both pipes remain in the HCD_PIPE_STATE_ACTIVE after the port error.
- Check that both pipes pipe can be halted.
- Check that the default pipe can be flushed. A HCD_PIPE_EVENT_URB_DONE event should be generated for the ISOC pipe
because it had enqueued URBs.
- Check that all URBs can be dequeued and their status is updated
- Free both pipes
- Teardown
*/
TEST_CASE("Test HCD isochronous pipe sudden disconnect", "[hcd][ignore]")
{
hcd_port_handle_t port_hdl = test_hcd_setup(); //Setup the HCD and port
usb_speed_t port_speed = test_hcd_wait_for_conn(port_hdl); //Trigger a connection
//The MPS of the ISOC OUT pipe is quite large, so we need to bias the FIFO sizing
TEST_ASSERT_EQUAL(ESP_OK, hcd_port_set_fifo_bias(port_hdl, HCD_PORT_FIFO_BIAS_PTX));
vTaskDelay(pdMS_TO_TICKS(100)); //Short delay send of SOF (for FS) or EOPs (for LS)
//Enumerate and reset device
hcd_pipe_handle_t default_pipe = test_hcd_pipe_alloc(port_hdl, NULL, 0, port_speed); //Create a default pipe (using a NULL EP descriptor)
uint8_t dev_addr = test_hcd_enum_device(default_pipe);
//Create ISOC OUT pipe to non-existent device
hcd_pipe_handle_t isoc_out_pipe = test_hcd_pipe_alloc(port_hdl, &mock_isoc_out_ep_desc, dev_addr + 1, port_speed);
//Create URBs
urb_t *urb_list[NUM_URBS];
//Initialize URBs
for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) {
urb_list[urb_idx] = test_hcd_alloc_urb(NUM_PACKETS_PER_URB, URB_DATA_BUFF_SIZE);
urb_list[urb_idx]->transfer.num_bytes = URB_DATA_BUFF_SIZE;
urb_list[urb_idx]->transfer.context = URB_CONTEXT_VAL;
for (int pkt_idx = 0; pkt_idx < NUM_PACKETS_PER_URB; pkt_idx++) {
urb_list[urb_idx]->transfer.isoc_packet_desc[pkt_idx].num_bytes = ISOC_PACKET_SIZE;
//Each packet will consist of the same byte, but each subsequent packet's byte will increment (i.e., packet 0 transmits all 0x0, packet 1 transmits all 0x1)
memset(&urb_list[urb_idx]->transfer.data_buffer[pkt_idx * ISOC_PACKET_SIZE], (urb_idx * NUM_URBS) + pkt_idx, ISOC_PACKET_SIZE);
}
}
//Enqueue URBs
for (int i = 0; i < NUM_URBS; i++) {
TEST_ASSERT_EQUAL(ESP_OK, hcd_urb_enqueue(isoc_out_pipe, urb_list[i]));
}
//Add a short delay to let the transfers run for a bit
esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
test_hcd_force_conn_state(false, 0);
//Disconnect event should have occurred. Handle the port event
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
printf("Sudden disconnect\n");
//Both pipes should still be active
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(isoc_out_pipe));
//Halt both pipes
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(isoc_out_pipe, HCD_PIPE_CMD_HALT));
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(isoc_out_pipe));
//Flush both pipes. ISOC pipe should return completed URBs
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(isoc_out_pipe, HCD_PIPE_CMD_FLUSH));
//Dequeue ISOC URBs
for (int urb_idx = 0; urb_idx < NUM_URBS; urb_idx++) {
urb_t *urb = hcd_urb_dequeue(isoc_out_pipe);
TEST_ASSERT_EQUAL(urb_list[urb_idx], urb);
TEST_ASSERT_EQUAL(URB_CONTEXT_VAL, urb->transfer.context);
//The URB has either completed entirely or is marked as no_device
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE);
}
//Free URB list and pipe
for (int i = 0; i < NUM_URBS; i++) {
test_hcd_free_urb(urb_list[i]);
}
test_hcd_pipe_free(isoc_out_pipe);
test_hcd_pipe_free(default_pipe);
//Cleanup
test_hcd_teardown(port_hdl);
}

View File

@@ -32,7 +32,7 @@ Procedure:
- Start transfers but trigger a disconnect after a short delay - Start transfers but trigger a disconnect after a short delay
- Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event. - Check that HCD_PORT_EVENT_SUDDEN_DISCONN event is generated. Handle that port event.
- Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error. - Check that the default pipe remains in the HCD_PIPE_STATE_ACTIVE after the port error.
- Check that the default pipe can be halted, a HCD_PIPE_EVENT_URB_DONE event should be generated - Check that the default pipe can be halted.
- Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated - Check that the default pipe can be flushed, a HCD_PIPE_EVENT_URB_DONE event should be generated
- Check that all URBs can be dequeued. - Check that all URBs can be dequeued.
- Free default pipe - Free default pipe
@@ -66,7 +66,7 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
//Add a short delay to let the transfers run for a bit //Add a short delay to let the transfers run for a bit
esp_rom_delay_us(POST_ENQUEUE_DELAY_US); esp_rom_delay_us(POST_ENQUEUE_DELAY_US);
test_hcd_force_conn_state(false, 0); test_hcd_force_conn_state(false, 0);
//Disconnect event should have occurred. Handle the event //Disconnect event should have occurred. Handle the port event
test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION); test_hcd_expect_port_event(port_hdl, HCD_PORT_EVENT_DISCONNECTION);
TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_EVENT_DISCONNECTION, hcd_port_handle_event(port_hdl));
TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl)); TEST_ASSERT_EQUAL(HCD_PORT_STATE_RECOVERY, hcd_port_get_state(port_hdl));
@@ -75,17 +75,17 @@ TEST_CASE("Test HCD port sudden disconnect", "[hcd][ignore]")
//We should be able to halt then flush the pipe //We should be able to halt then flush the pipe
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe)); TEST_ASSERT_EQUAL(HCD_PIPE_STATE_ACTIVE, hcd_pipe_get_state(default_pipe));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT)); TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_HALT));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); printf("Pipe halted\n");
TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe)); TEST_ASSERT_EQUAL(HCD_PIPE_STATE_HALTED, hcd_pipe_get_state(default_pipe));
TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH)); TEST_ASSERT_EQUAL(ESP_OK, hcd_pipe_command(default_pipe, HCD_PIPE_CMD_FLUSH));
test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE); test_hcd_expect_pipe_event(default_pipe, HCD_PIPE_EVENT_URB_DONE);
printf("Pipe halted and flushed\n"); printf("Pipe flushed\n");
//Dequeue URBs //Dequeue URBs
for (int i = 0; i < NUM_URBS; i++) { for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe); urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb); TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE);
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
//We must have transmitted at least the setup packet, but device may return less than bytes requested //We must have transmitted at least the setup packet, but device may return less than bytes requested
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);
@@ -259,7 +259,9 @@ TEST_CASE("Test HCD port disable", "[hcd][ignore]")
for (int i = 0; i < NUM_URBS; i++) { for (int i = 0; i < NUM_URBS; i++) {
urb_t *urb = hcd_urb_dequeue(default_pipe); urb_t *urb = hcd_urb_dequeue(default_pipe);
TEST_ASSERT_EQUAL(urb_list[i], urb); TEST_ASSERT_EQUAL(urb_list[i], urb);
TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || urb->transfer.status == USB_TRANSFER_STATUS_CANCELED); TEST_ASSERT(urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED || //The transfer completed before the pipe halted
urb->transfer.status == USB_TRANSFER_STATUS_CANCELED || //The transfer was stopped mid-way by the halt
urb->transfer.status == USB_TRANSFER_STATUS_NO_DEVICE); //The transfer was never started
if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) { if (urb->transfer.status == USB_TRANSFER_STATUS_COMPLETED) {
//We must have transmitted at least the setup packet, but device may return less than bytes requested //We must have transmitted at least the setup packet, but device may return less than bytes requested
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes); TEST_ASSERT_GREATER_OR_EQUAL(sizeof(usb_setup_packet_t), urb->transfer.actual_num_bytes);