Add USB HCD

This commit adds the USB HCD (Host Controller Driver) and accompanying unit tests.
This commit is contained in:
Darian Leung
2021-02-09 10:29:01 +08:00
parent bf0c05064c
commit 424e1e1886
11 changed files with 3580 additions and 52 deletions

View File

@@ -44,6 +44,7 @@ NOTE: Thread safety is the responsibility fo the HAL user. All USB Host HAL
/**
* @brief Channel states
*
*/
typedef enum {
USBH_HAL_CHAN_STATE_HALTED = 0, /**< The channel is halted. No transfer descriptor list is being executed */
@@ -57,6 +58,7 @@ typedef enum {
* @brief Host port HAL events
*/
typedef enum {
USBH_HAL_PORT_EVENT_NONE, /**< No event occurred, or could not decode interrupt */
USBH_HAL_PORT_EVENT_CHAN, /**< A channel event has occurred. Call the the channel event handler instead */
USBH_HAL_PORT_EVENT_CONN, /**< The host port has detected a connection */
USBH_HAL_PORT_EVENT_DISCONN, /**< The host port has been disconnected */
@@ -74,7 +76,6 @@ typedef enum {
USBH_HAL_CHAN_EVENT_SLOT_HALT, /**< The channel as completed execution of a single transfer descriptor in a list. Channel is now halted */
USBH_HAL_CHAN_EVENT_ERROR, /**< The channel has encountered an error. Channel is now halted. */
USBH_HAL_CHAN_EVENT_HALT_REQ, /**< The channel has been successfully halted as requested */
USBH_HAL_CHAN_EVENT_SUDDEN_HLT, /**< The channel was suddenly halted (e.g. due to a disconnect). */
} usbh_hal_chan_event_t;
// ------------------------------- HAL Errors ----------------------------------
@@ -87,7 +88,6 @@ typedef enum {
USBH_HAL_CHAN_ERROR_BNA, /**< Buffer Not Available error (i.e., transfer slot is unfilled */
USBH_HAL_CHAN_ERROR_PKT_BBL, /**< Packet babbler error (packet exceeded MPS) */
USBH_HAL_CHAN_ERROR_STALL, /**< STALL response received */
USBH_HAL_CHAN_ERROR_AHB, /**< AHB error */
} usbh_hal_chan_error_t;
// ----------------------- Transfer Descriptor Related -------------------------
@@ -102,6 +102,11 @@ typedef enum {
/**
* @brief Status value of a transfer descriptor
*
* A transfer descriptor's status remains unexecuted until the entire transfer
* descriptor completes (either successfully or an error). Therefore, if a
* channel halt is requested before a transfer descriptor completes, the
* transfer descriptoor remains unexecuted.
*/
#define USBH_HAL_XFER_DESC_STS_SUCCESS USBH_LL_QTD_STATUS_SUCCESS
#define USBH_HAL_XFER_DESC_STS_PKTERR USBH_LL_QTD_STATUS_PKTERR
@@ -120,7 +125,9 @@ typedef struct {
uint32_t bEndpointAddress: 8; /**< Endpoint address (containing endpoint number and direction) */
uint32_t mps: 11; /**< Maximum Packet Size */
uint32_t dev_addr: 8; /**< Device Address */
uint32_t reserved3: 3;
uint32_t ls_via_fs_hub: 1; /**< The endpoint is on a LS device that is routed through an FS hub.
Setting this bit will lead to the addition of the PREamble packet */
uint32_t reserved2: 2;
};
uint32_t val;
};
@@ -148,7 +155,7 @@ typedef struct {
struct {
union {
struct {
bool slot_acquired: 1; /**< The transfer descriptor list slot has been acquired */
uint32_t slot_acquired: 1; /**< The transfer descriptor list slot has been acquired */
uint32_t reserved7: 7;
uint32_t cur_qtd_idx: 8; /**< Index of the first QTD in chain of QTDs being executed */
uint32_t qtd_list_len: 8; /**< Length of QTD list in number of QTDs */
@@ -178,8 +185,8 @@ typedef struct {
} flags;
//Channel related
struct {
int num_allocd; /**< Number of channels currently allocated */
int chan_pend_intrs_msk; /**< Bit mask of channels with pending interrupts */
int num_allocd; /**< Number of channels currently allocated */
uint32_t chan_pend_intrs_msk; /**< Bit mask of channels with pending interrupts */
usbh_hal_chan_t *hdls[USBH_HAL_NUM_CHAN]; /**< Handles of each channel. Set to NULL if channel has not been allocated */
} channels;
} usbh_hal_context_t;
@@ -417,13 +424,17 @@ static inline usb_speed_t usbh_hal_port_get_conn_speed(usbh_hal_context_t *hal)
* @brief Disable the debounce lock
*
* This function should be called after calling usbh_hal_port_check_if_connected()
* and will allow connection/disconnection events to occur again.
* and will allow connection/disconnection events to occur again. Any pending
* connection or disconenction interrupts are cleared.
*
* @param hal Context of the HAL layer
*/
static inline void usbh_hal_disable_debounce_lock(usbh_hal_context_t *hal)
{
hal->flags.dbnc_lock_enabled = 0;
//Clear Conenction and disconenction interrupt in case it triggered again
usb_ll_intr_clear(hal->dev, USB_LL_INTR_CORE_DISCONNINT);
usbh_ll_hprt_intr_clear(hal->dev, USBH_LL_INTR_HPRT_PRTENCHNG);
//Reenable the hprt (connection) and disconnection interrupts
usb_ll_en_intrs(hal->dev, USB_LL_INTR_CORE_PRTINT | USB_LL_INTR_CORE_DISCONNINT);
}
@@ -659,7 +670,7 @@ static inline void usbh_hal_chan_slot_acquire(usbh_hal_chan_t *chan_obj, void *x
chan_obj->slot.owner_ctx = owner_ctx;
chan_obj->slot.flags.cur_qtd_idx = 0; //Start from the first descriptor
chan_obj->slot.flags.qtd_list_len = desc_list_len;
chan_obj->slot.flags.slot_acquired = true;
chan_obj->slot.flags.slot_acquired = 1;
//Store the descriptor list length in the HCTSIZ register. Address of desc list is set when channel is activated
usbh_ll_chan_set_qtd_list_len(chan_obj->regs, desc_list_len);
}
@@ -696,7 +707,7 @@ static inline void usbh_hal_chan_slot_release(usbh_hal_chan_t *chan_obj, void **
assert(chan_obj->slot.flags.slot_acquired);
*xfer_desc_list = (void *)chan_obj->slot.xfer_desc_list;
*desc_list_len = chan_obj->slot.flags.qtd_list_len;
chan_obj->slot.flags.slot_acquired = false;
chan_obj->slot.flags.slot_acquired = 0;
}
/**
@@ -734,11 +745,16 @@ static inline int usbh_hal_chan_get_next_desc_index(usbh_hal_chan_t *chan_obj)
* active, this function will return false and users must wait for the
* USBH_HAL_CHAN_EVENT_HALT_REQ event before treating the channel as halted.
*
* @note When a transfer is in progress (i.e., the channel is active) and a halt
* is requested, the channel will halt after the next USB packet is completed.
* If the transfer has more pending packets, the transfer will just be
* marked as USBH_HAL_XFER_DESC_STS_NOT_EXECUTED.
*
* @param chan_obj Channel object
* @return true The channel is already halted
* @return false The halt was requested, wait for USBH_HAL_CHAN_EVENT_HALT_REQ
*/
bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj);
bool usbh_hal_chan_slot_request_halt(usbh_hal_chan_t *chan_obj);
/* -----------------------------------------------------------------------------
-------------------------------- Event Handling --------------------------------

View File

@@ -283,8 +283,8 @@ static inline bool usb_ll_check_core_soft_reset(usbh_dev_t *hw)
/**
* @brief Reads and clears the global interrupt register
*
* @param hw
* @return uint32_t
* @param hw Start address of the DWC_OTG registers
* @return uint32_t Mask of interrupts
*/
static inline uint32_t usb_ll_intr_read_and_clear(usbh_dev_t *hw)
{
@@ -294,6 +294,18 @@ static inline uint32_t usb_ll_intr_read_and_clear(usbh_dev_t *hw)
return gintsts.val;
}
/**
* @brief Clear specific interrupts
*
* @param hw Start address of the DWC_OTG registers
* @param intr_msk Mask of interrupts to clear
*/
static inline void usb_ll_intr_clear(usbh_dev_t *hw, uint32_t intr_msk)
{
//All GINTSTS fields are either W1C or read only. So safe to write directly
hw->gintsts_reg.val = intr_msk;
}
// --------------------------- GINTMSK Register --------------------------------
static inline void usb_ll_en_intrs(usbh_dev_t *hw, uint32_t intr_mask)
@@ -403,26 +415,36 @@ static inline void usbh_ll_hcfg_set_fsls_pclk_sel(usbh_dev_t *hw)
}
/**
* @brief Sets some default values to HCFG to operate in Host mode wiht scatter/gather DMA
* @brief Sets some default values to HCFG to operate in Host mode with scatter/gather DMA
*
* @param hw
*/
static inline void usbh_ll_hcfg_set_defaults(usbh_dev_t *hw)
static inline void usbh_ll_hcfg_set_defaults(usbh_dev_t *hw, usb_speed_t speed)
{
hw->hcfg_reg.descdma = 1; //Enable scatt/gatt
hw->hcfg_reg.fslssupp = 1; //FS/LS supp only
hw->hcfg_reg.fslspclksel = 1; //48MHz PHY clock
/*
Indicate to the OTG core what speed the PHY clock is at
Note: It seems like our PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
hw->hcfg_reg.fslspclksel = (speed == USB_SPEED_FULL) ? 1 : 2;
hw->hcfg_reg.perschedena = 0; //Disable perio sched
}
// ----------------------------- HFIR Register ---------------------------------
static inline void usbh_ll_hfir_set_defaults(usbh_dev_t *hw)
static inline void usbh_ll_hfir_set_defaults(usbh_dev_t *hw, usb_speed_t speed)
{
usb_hfir_reg_t hfir;
hfir.val = hw->hfir_reg.val;
hfir.hfirrldctrl = 0; //Disable dynamic loading
hfir.frint = 48000; //Set frame interval to 48000 cycles of 48MHz clock (i.e. equals to 1ms)
/*
Set frame interval to be equal to 1ms
Note: It seems like our PHY has an implicit 8 divider applied when in LS mode,
so the values of FSLSPclkSel and FrInt have to be adjusted accordingly.
*/
hfir.frint = (speed == USB_SPEED_FULL) ? 48000 : 6000;
hw->hfir_reg.val = hfir.val;
}
@@ -611,6 +633,13 @@ static inline uint32_t usbh_ll_hprt_intr_read_and_clear(usbh_dev_t *hw)
return (hprt.val & (USBH_LL_HPRT_W1C_MSK & ~(USBH_LL_HPRT_ENA_MSK)));
}
static inline void usbh_ll_hprt_intr_clear(usbh_dev_t *hw, uint32_t intr_mask)
{
usb_hprt_reg_t hprt;
hprt.val = hw->hprt_reg.val;
hw->hprt_reg.val = ((hprt.val & ~USBH_LL_HPRT_ENA_MSK) & ~USBH_LL_HPRT_W1C_MSK) | intr_mask;
}
//Per Channel registers
// --------------------------- HCCHARi Register --------------------------------
@@ -665,9 +694,11 @@ static inline void usbh_ll_chan_set_ep_type(volatile usb_host_chan_regs_t *chan,
}
}
static inline void usbh_ll_chan_set_ls(volatile usb_host_chan_regs_t *chan)
//Indicates whether channel is commuunicating with a LS device connected via a FS hub. Setting this bit to 1 will cause
//each packet to be preceded by a PREamble packet
static inline void usbh_ll_chan_set_lspddev(volatile usb_host_chan_regs_t *chan, bool is_ls)
{
chan->hcchar_reg.lspddev = 1;
chan->hcchar_reg.lspddev = is_ls;
}
static inline void usbh_ll_chan_set_dir(volatile usb_host_chan_regs_t *chan, bool is_in)
@@ -685,11 +716,12 @@ static inline void usbh_ll_chan_set_mps(volatile usb_host_chan_regs_t *chan, uin
chan->hcchar_reg.mps = mps;
}
static inline void usbh_ll_chan_hcchar_init(volatile usb_host_chan_regs_t *chan, int dev_addr, int ep_num, int mps, usb_xfer_type_t type, bool is_in)
static inline void usbh_ll_chan_hcchar_init(volatile usb_host_chan_regs_t *chan, int dev_addr, int ep_num, int mps, usb_xfer_type_t type, bool is_in, bool is_ls)
{
//Sets all persistent fields of the channel over its lifetime
usbh_ll_chan_set_dev_addr(chan, dev_addr);
usbh_ll_chan_set_ep_type(chan, type);
usbh_ll_chan_set_lspddev(chan, is_ls);
usbh_ll_chan_set_dir(chan, is_in);
usbh_ll_chan_set_ep_num(chan, ep_num);
usbh_ll_chan_set_mps(chan, mps);

View File

@@ -84,22 +84,29 @@ _Static_assert((RX_FIFO_LEN + NPTX_FIFO_LEN + PTX_FIFO_LEN + REG_FIFO_LEN) <= HW
* The following channel interrupt bits are currently checked (in order LSB to MSB)
* - USBH_LL_INTR_CHAN_XFERCOMPL
* - USBH_LL_INTR_CHAN_CHHLTD
* - USBH_LL_INTR_CHAN_AHBERR
* - USBH_LL_INTR_CHAN_STALL
* - USBH_LL_INTR_CHAN_BBLEER
* - USBH_LL_INTR_CHAN_BNAINTR
* - USBH_LL_INTR_CHAN_XCS_XACT_ERR
*
* Note the following points about channel interrupts:
* - Not all bits are unmaskable under scatter/gather
* - Those bits proxy their interrupt through the USBH_LL_INTR_CHAN_CHHLTD bit
* - USBH_LL_INTR_CHAN_XCS_XACT_ERR is always unmasked
* - When USBH_LL_INTR_CHAN_BNAINTR occurs, USBH_LL_INTR_CHAN_CHHLTD will NOT.
* - USBH_LL_INTR_CHAN_AHBERR doesn't actually ever happen on our system )i.e., ESP32S2 and later):
* - If the QTD list's starting address is an invalid address (e.g., NULL), the core will attempt to fetch that
* address for a transfer descriptor and probably gets all zeroes. It will interpret the zero as a bad QTD and
* return a USBH_LL_INTR_CHAN_BNAINTR instead.
* - If the QTD's buffer pointer is an invalid address, the core will attempt to read/write data to/from that
* invalid buffer address with NO INDICATION OF ERROR. The transfer will be acknowledged and treated as
* successful. Bad buffer pointers MUST BE CHECKED FROM HIGHER LAYERS INSTEAD.
*/
#define CHAN_INTRS_EN_MSK (USBH_LL_INTR_CHAN_XFERCOMPL | \
USBH_LL_INTR_CHAN_CHHLTD | \
USBH_LL_INTR_CHAN_BNAINTR)
#define CHAN_INTRS_ERROR_MSK (USBH_LL_INTR_CHAN_AHBERR | \
USBH_LL_INTR_CHAN_STALL | \
#define CHAN_INTRS_ERROR_MSK (USBH_LL_INTR_CHAN_STALL | \
USBH_LL_INTR_CHAN_BBLEER | \
USBH_LL_INTR_CHAN_BNAINTR | \
USBH_LL_INTR_CHAN_XCS_XACT_ERR)
@@ -164,8 +171,11 @@ void usbh_hal_core_soft_reset(usbh_hal_context_t *hal)
}
//Set the default bits
set_defaults(hal);
//Clear all the flags
//Clear all the flags and channels
hal->flags.val = 0;
hal->channels.num_allocd = 0;
hal->channels.chan_pend_intrs_msk = 0;
memset(hal->channels.hdls, 0, sizeof(usbh_hal_chan_t *) * USBH_HAL_NUM_CHAN);
}
/* -----------------------------------------------------------------------------
@@ -181,11 +191,12 @@ static inline void debounce_lock_enable(usbh_hal_context_t *hal)
void usbh_hal_port_enable(usbh_hal_context_t *hal)
{
usb_speed_t speed = usbh_ll_hprt_get_speed(hal->dev);
//Host Configuration
usbh_ll_hcfg_set_defaults(hal->dev);
usbh_ll_hcfg_set_defaults(hal->dev, speed);
//Todo: Set frame list entries and ena per sched
//Configure HFIR
usbh_ll_hfir_set_defaults(hal->dev);
usbh_ll_hfir_set_defaults(hal->dev, speed);
//Config FIFO sizes
usb_ll_set_rx_fifo_size(hal->dev, RX_FIFO_LEN);
usb_ll_set_nptx_fifo_size(hal->dev, RX_FIFO_LEN, NPTX_FIFO_LEN);
@@ -225,7 +236,7 @@ bool usbh_hal_chan_alloc(usbh_hal_context_t *hal, usbh_hal_chan_t *chan_obj, voi
usbh_ll_chan_intr_read_and_clear(chan_obj->regs); //Clear the interrupt bits for that channel
usbh_ll_haintmsk_en_chan_intr(hal->dev, 1 << chan_obj->flags.chan_idx);
usbh_ll_chan_set_intr_mask(chan_obj->regs, CHAN_INTRS_EN_MSK); //Unmask interrupts for this channel
usbh_ll_chan_set_pid(chan_obj->regs, 0); //Set the initial PID to zero
usbh_ll_chan_set_pid(chan_obj->regs, 0); //Set the initial PID to zero
usbh_ll_chan_hctsiz_init(chan_obj->regs); //Set the non changing parts of the HCTSIZ registers (e.g., do_ping and sched info)
return true;
}
@@ -235,8 +246,8 @@ 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
assert(!chan_obj->slot.flags.slot_acquired
&& !chan_obj->flags.active
&& !chan_obj->flags.error_pending);
&& !chan_obj->flags.active
&& !chan_obj->flags.error_pending);
//Deallocate channel
hal->channels.hdls[chan_obj->flags.chan_idx] = NULL;
hal->channels.num_allocd--;
@@ -255,7 +266,8 @@ void usbh_hal_chan_set_ep_char(usbh_hal_chan_t *chan_obj, usbh_hal_ep_char_t *ep
ep_char->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK,
ep_char->mps,
ep_char->type,
ep_char->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK);
ep_char->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK,
ep_char->ls_via_fs_hub);
}
/* -----------------------------------------------------------------------------
@@ -278,11 +290,11 @@ void usbh_hal_chan_activate(usbh_hal_chan_t *chan_obj, int num_to_skip)
usbh_ll_chan_start(chan_obj->regs);
}
bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj)
bool usbh_hal_chan_slot_request_halt(usbh_hal_chan_t *chan_obj)
{
//Cannot request halt on a channel that is pending error handling
assert(!chan_obj->flags.error_pending);
if (usbh_ll_chan_is_active(chan_obj->regs)) {
if (usbh_ll_chan_is_active(chan_obj->regs) || chan_obj->flags.active) {
usbh_ll_chan_halt(chan_obj->regs);
chan_obj->flags.halt_requested = 1;
return false;
@@ -294,6 +306,16 @@ bool usbh_hal_chan_request_halt(usbh_hal_chan_t *chan_obj)
-------------------------------- Event Handling --------------------------------
----------------------------------------------------------------------------- */
//When a device on the port is no longer valid (e.g., disconnect, port error). All channels are no longer valid
static void chan_all_halt(usbh_hal_context_t *hal)
{
for (int i = 0; i < USBH_HAL_NUM_CHAN; i++) {
if (hal->channels.hdls[i] != NULL) {
hal->channels.hdls[i]->flags.active = 0;
}
}
}
usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
{
uint32_t intrs_core = usb_ll_intr_read_and_clear(hal->dev); //Read and clear core interrupts
@@ -304,7 +326,7 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
}
//Note: Do not change order of checks. Regressing events (e.g. enable -> disabled, connected -> connected)
//always take precendance. ENABLED < DISABLED < CONN < DISCONN < OVRCUR
usbh_hal_port_event_t event = -1;
usbh_hal_port_event_t event = USBH_HAL_PORT_EVENT_NONE;
//Check if this is a core or port event
if ((intrs_core & CORE_EVENTS_INTRS_MSK) || (intrs_port & PORT_EVENTS_INTRS_MSK)) {
@@ -312,11 +334,13 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
if (intrs_core & USB_LL_INTR_CORE_DISCONNINT) {
event = USBH_HAL_PORT_EVENT_DISCONN;
debounce_lock_enable(hal);
chan_all_halt(hal); //All channels are halted on a disconnect
//Mask the port connection and disconnection interrupts to prevent repeated triggering
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTOVRCURRCHNG) {
//Check if this is an overcurrent or an overcurrent cleared
if (usbh_ll_hprt_get_port_overcur(hal->dev)) {
event = USBH_HAL_PORT_EVENT_OVRCUR;
chan_all_halt(hal); //All channels are halted on an overcurrent
} else {
event = USBH_HAL_PORT_EVENT_OVRCUR_CLR;
}
@@ -325,13 +349,15 @@ usbh_hal_port_event_t usbh_hal_decode_intr(usbh_hal_context_t *hal)
event = USBH_HAL_PORT_EVENT_ENABLED;
} else { //Host port has been disabled
event = USBH_HAL_PORT_EVENT_DISABLED;
chan_all_halt(hal); //All channels are halted when the port is disabled
}
} else if (intrs_port & USBH_LL_INTR_HPRT_PRTCONNDET && !hal->flags.dbnc_lock_enabled) {
event = USBH_HAL_PORT_EVENT_CONN;
debounce_lock_enable(hal);
}
}
if (intrs_core & USB_LL_INTR_CORE_HCHINT) {
//Port events always take precendance over channel events
if (event == USBH_HAL_PORT_EVENT_NONE && (intrs_core & USB_LL_INTR_CORE_HCHINT)) {
//One or more channels have pending interrupts. Store the mask of those channels
hal->channels.chan_pend_intrs_msk = usbh_ll_get_chan_intrs_msk(hal->dev);
event = USBH_HAL_PORT_EVENT_CHAN;
@@ -355,17 +381,15 @@ 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);
usbh_hal_chan_event_t chan_event;
//Currently, all cases where channel interrupts occur will also halt the channel
assert(chan_intrs & USBH_LL_INTR_CHAN_CHHLTD);
//Currently, all cases where channel interrupts occur will also halt the channel, except for BNA
assert(chan_intrs & (USBH_LL_INTR_CHAN_CHHLTD | USBH_LL_INTR_CHAN_BNAINTR));
chan_obj->flags.active = 0;
//Note: Do not change the current checking order of checks. Certain interrupts (e.g., errors) have precedence over others
if (chan_intrs & CHAN_INTRS_ERROR_MSK) { //One of the error interrupts has occurred.
//Note: Errors are uncommon, so we check against the entire interrupt mask to reduce frequency of entering this call path
//Store the error in hal context
usbh_hal_chan_error_t error;
if (chan_intrs & USBH_LL_INTR_CHAN_AHBERR) {
error = USBH_HAL_CHAN_ERROR_AHB;
} else if (chan_intrs & USBH_LL_INTR_CHAN_STALL) {
if (chan_intrs & USBH_LL_INTR_CHAN_STALL) {
error = USBH_HAL_CHAN_ERROR_STALL;
} else if (chan_intrs & USBH_LL_INTR_CHAN_BBLEER) {
error = USBH_HAL_CHAN_ERROR_PKT_BBL;
@@ -393,8 +417,8 @@ usbh_hal_chan_event_t usbh_hal_chan_decode_intr(usbh_hal_chan_t *chan_obj)
chan_event = USBH_HAL_CHAN_EVENT_SLOT_HALT;
}
} else {
//Channel halted suddenly (i.e,, a disconnect)
chan_event = USBH_HAL_CHAN_EVENT_SUDDEN_HLT;
//Should never reach this point
abort();
}
return chan_event;
}