diff options
Diffstat (limited to 'src/portable/synopsys')
| -rw-r--r-- | src/portable/synopsys/dwc2/hcd_dwc2.c | 171 |
1 files changed, 78 insertions, 93 deletions
diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index f495dec2a..d82785eab 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -69,7 +69,7 @@ typedef struct { uint32_t next_pid : 2; // PID for next transfer uint32_t next_do_ping : 1; // Do PING for next transfer if possible (highspeed OUT) uint32_t closing : 1; // endpoint is closing - uint32_t aborting : 1; // periodic DMA channel is waiting for its automatic halt + uint32_t aborting : 1; // endpoint has an active channel waiting to be retired uint32_t periodic_phase : 1; // periodic transfer phase is established uint32_t xfer_pending : 1; // periodic transfer waiting for its service interval }; @@ -102,7 +102,7 @@ typedef struct { uint8_t ls_waiting : 1; // affected low-speed channel is parked until SOF grants it a frame #endif }; - volatile bool aborting; // periodic DMA abort waiting for the channel's automatic halt + volatile bool aborting; // close/abort is waiting for the channel to halt } hcd_xfer_t; typedef struct { @@ -451,21 +451,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool channel_xfer_enable(dwc2_regs_t* dwc2, return true; } -// Find currently enabled channel. Note: EP0 is bidirectional -TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_find_enabled(dwc2_regs_t* dwc2, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) { - const uint8_t max_channel = dwc2_channel_count(dwc2); - for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { - if (_hcd_data.xfer[ch_id].allocated) { - const dwc2_channel_char_t hcchar = {.value = dwc2->channel[ch_id].hcchar}; - if (hcchar.dev_addr == dev_addr && hcchar.ep_num == ep_num && (ep_num == 0 || hcchar.ep_dir == ep_dir)) { - return ch_id; - } - } - } - return TUSB_INDEX_INVALID_8; -} - - // Allocate a new endpoint TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_alloc(void) { for (uint32_t i = 0; i < CFG_TUH_DWC2_ENDPOINT_MAX; i++) { @@ -483,42 +468,84 @@ TU_ATTR_ALWAYS_INLINE static inline void edpt_dealloc(hcd_endpoint_t *edpt) { edpt->hcchar_bm.enable = 0; } -// close an opened endpoint -static void edpt_close(dwc2_regs_t *dwc2, uint8_t ep_id) { +// Start retiring an endpoint's queued or active transfer. Keep this transition atomic with the SOF dispatcher, which +// may otherwise grant a parked low-speed channel while it is being removed. Slave mode may need to wait for +// request-queue space, so let channel/disconnect interrupts run while waiting. +static void edpt_channel_retire(dwc2_regs_t *dwc2, uint8_t ep_id, bool closing) { hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; - edpt->closing = 1; // mark endpoint as closing - -#if DWC2_LS_ONE_XACT_PER_FRAME const uint32_t gahbcfg = dwc2->gahbcfg; dwc2->gahbcfg = gahbcfg & ~GAHBCFG_GINT; -#endif - // disable active channel belong to this endpoint - for (uint8_t ch_id = 0; ch_id < DWC2_CHANNEL_COUNT_MAX; ch_id++) { + if (closing) { + edpt->closing = 1; + } else if (edpt->xfer_pending) { + edpt->xfer_pending = 0; + edpt->uframe_countdown = 0; + dwc2->gahbcfg = gahbcfg; + return; + } else { + edpt->aborting = 1; + } + + while (true) { + uint8_t ch_id = TUSB_INDEX_INVALID_8; + const uint8_t max_channel = dwc2_channel_count(dwc2); + for (uint8_t i = 0; i < max_channel; i++) { + const hcd_xfer_t *xfer = &_hcd_data.xfer[i]; + if (xfer->allocated && xfer->ep_id == ep_id) { + ch_id = i; + break; + } + } + + if (ch_id == TUSB_INDEX_INVALID_8) { + if (closing) { + edpt_dealloc(edpt); + } else { + edpt->aborting = 0; + } + dwc2->gahbcfg = gahbcfg; + return; + } + hcd_xfer_t *xfer = &_hcd_data.xfer[ch_id]; - if (xfer->allocated && xfer->ep_id == ep_id) { #if DWC2_LS_ONE_XACT_PER_FRAME - if (xfer->ls_waiting) { - channel_dealloc(dwc2, ch_id); + if (xfer->ls_waiting) { + edpt->uframe_countdown = 0; + channel_dealloc(dwc2, ch_id); + if (closing) { edpt_dealloc(edpt); - dwc2->gahbcfg = gahbcfg; - return; + } else { + edpt->aborting = 0; } -#endif - dwc2_channel_t *channel = &dwc2->channel[ch_id]; - xfer->closing = 1; -#if DWC2_LS_ONE_XACT_PER_FRAME dwc2->gahbcfg = gahbcfg; + return; + } #endif - channel_disable(dwc2, channel); - return; // only 1 active channel per endpoint + + dwc2_channel_t *channel = &dwc2->channel[ch_id]; + const bool is_period = channel_is_periodic(channel->hcchar); + xfer->closing = closing; + xfer->aborting = true; + + if (!dma_host_enabled(dwc2) && 0 == req_queue_avail(dwc2, is_period)) { + // The halt request itself needs queue space. Interrupts may retire the channel naturally while we wait. + dwc2->gahbcfg = gahbcfg; + while (xfer->allocated && 0 == req_queue_avail(dwc2, is_period)) { + } + dwc2->gahbcfg = gahbcfg & ~GAHBCFG_GINT; + continue; } + + channel_disable(dwc2, channel); + dwc2->gahbcfg = gahbcfg; + return; } +} -#if DWC2_LS_ONE_XACT_PER_FRAME - dwc2->gahbcfg = gahbcfg; -#endif - edpt_dealloc(edpt); // no active channel, safe to de-alloc now +// close an opened endpoint +static void edpt_close(dwc2_regs_t *dwc2, uint8_t ep_id) { + edpt_channel_retire(dwc2, ep_id, true); } // Find an endpoint that is opened previously with hcd_edpt_open() @@ -1124,61 +1151,14 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * return edpt_xfer_kickoff(dwc2, ep_id); } -// Abort a queued transfer. Note: it can only abort transfer that has not been started -// Return true if a queued transfer is aborted, false if there is no transfer to abort +// Abort a queued or active transfer. bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const uint8_t ep_num = tu_edpt_number(ep_addr); const uint8_t ep_dir = tu_edpt_dir(ep_addr); const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir, false); TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); - hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; - - hcd_int_disable(rhport); - - const bool xfer_pending = edpt->xfer_pending; - if (xfer_pending) { - edpt->xfer_pending = 0; - edpt->uframe_countdown = 0; - } - - if (xfer_pending) { - hcd_int_enable(rhport); - return true; - } - -#if DWC2_LS_ONE_XACT_PER_FRAME - const uint8_t waiting_ch = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir); - if (waiting_ch < 16 && _hcd_data.xfer[waiting_ch].ls_waiting) { - edpt->uframe_countdown = 0; - channel_dealloc(dwc2, waiting_ch); - hcd_int_enable(rhport); - return true; - } -#endif - - // A periodic DMA channel must halt naturally at the next service boundary. Prevent a replacement transfer until the - // halt ISR retires the channel, and suppress completion for the aborted transfer. - if (dma_host_enabled(dwc2) && channel_is_periodic(edpt->hcchar)) { - const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir); - if (ch_id < 16) { - hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; - edpt->aborting = 1; - xfer->aborting = true; - hcd_int_enable(rhport); - return true; - } - } - - hcd_int_enable(rhport); - - // Channel disable may wait for request-queue space in slave mode. - // Find enabled channel and disable it; the interrupt handler will de-allocate it. - const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir); - if (ch_id < 16) { - dwc2_channel_t* channel = &dwc2->channel[ch_id]; - channel_disable(dwc2, channel); - } + edpt_channel_retire(dwc2, ep_id, false); return true; } @@ -1296,8 +1276,8 @@ static void handle_rxflvl_irq(uint8_t rhport) { // In packet received, pop this entry --> ACK interrupt const uint16_t byte_count = grxstsp.byte_count; hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; - if (!xfer->allocated) { - // Discard data for a channel retired by disconnect. + if (!xfer->allocated || xfer->aborting) { + // Discard data for a channel retired by disconnect, close, or abort. for (uint16_t count = 0; count < byte_count; count += sizeof(uint32_t)) { (void) dwc2->fifo[0][0]; } @@ -1370,7 +1350,7 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; dwc2_channel_t* channel = &dwc2->channel[ch_id]; const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; - if (xfer->allocated && channel_is_periodic(hcchar.value) == is_periodic && + if (xfer->allocated && !xfer->aborting && channel_is_periodic(hcchar.value) == is_periodic && 0 == (channel->hcintmsk & HCINT_HALTED) && hcchar.ep_dir == TUSB_DIR_OUT) { if (channel_txfifo_write(dwc2, ch_id, is_periodic)) { return true; @@ -1853,7 +1833,11 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { const uint32_t hcint_clear = (!is_dma && (hcint & ~HCINT_HALTED)) ? (hcint & ~HCINT_HALTED) : hcint; channel->hcint = hcint_clear; - if (is_dma && xfer->aborting && (hcint & HCINT_HALTED)) { + if (xfer->aborting) { + if (!(hcint & HCINT_HALTED)) { + continue; + } + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; const bool closing = xfer->closing; // channel_xfer_start() predicts the PID after all requested packets; @@ -1863,6 +1847,7 @@ static void handle_channel_irq(uint8_t rhport, bool in_isr) { edpt->next_pid = hctsiz.pid; } xfer->aborting = false; + channel->hcint = HCINT_HALTED; channel_dealloc(dwc2, ch_id); if (closing) { edpt_dealloc(edpt); |
