diff options
| author | HiFiPhile <[email protected]> | 2026-06-23 23:12:51 +0200 |
|---|---|---|
| committer | HiFiPhile <[email protected]> | 2026-06-23 23:12:51 +0200 |
| commit | 033dc6bb77e8a9e1c1172d74d47707848b5c5bf5 (patch) | |
| tree | e7672f9bda54aa1a243b2a1c0f74a48055569c63 /src/portable | |
| parent | b5e63ca44c846813ece9ad9df684cae0d1d5b542 (diff) | |
| parent | cd3561bf158afd5a5718904b8139a338d1e3b67c (diff) | |
Merge remote-tracking branch 'tinyusb/master' into fix-stm32-usbc
Diffstat (limited to 'src/portable')
26 files changed, 1575 insertions, 907 deletions
diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c index 312a98299..62df1a6d5 100644 --- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c @@ -360,7 +360,7 @@ static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size, unsigned val = USB_ENDPT_EPCTLDIS_MASK; val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0; val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK; - CI_REG->EP[epn].CTL |= val; + CI_REG->EP[epn].CTL |= (uint8_t)val; if (xfer != TUSB_XFER_ISOCHRONOUS) { bd[odd].dts = 1; @@ -434,11 +434,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1; /* When total_bytes is greater than the max packet size, * it prepares to the next transfer to avoid NAK in advance. */ - next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps; + next->bc = (total_bytes >= 2 * mps) ? mps : (total_bytes - mps); next->addr = buffer + mps; next->own = 1; } - bd->bc = total_bytes >= mps ? mps: total_bytes; + bd->bc = (total_bytes >= mps ? mps : total_bytes); bd->addr = buffer; __DSB(); bd->own = 1; /* This bit must be set last */ @@ -506,16 +506,16 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) //--------------------------------------------------------------------+ void dcd_int_handler(uint8_t rhport) { - uint32_t is = CI_REG->INT_STAT; - uint32_t msk = CI_REG->INT_EN; + uint8_t is = CI_REG->INT_STAT; + uint8_t msk = CI_REG->INT_EN; // clear non-enabled interrupts - CI_REG->INT_STAT = is & ~msk; + CI_REG->INT_STAT = (uint8_t)(is & ~msk); is &= msk; if (is & USB_ISTAT_ERROR_MASK) { /* TODO: */ - uint32_t es = CI_REG->ERR_STAT; + uint8_t es = CI_REG->ERR_STAT; CI_REG->ERR_STAT = es; CI_REG->INT_STAT = is; /* discard any pending events */ } diff --git a/src/portable/dialog/da146xx/dcd_da146xx.c b/src/portable/dialog/da146xx/dcd_da146xx.c index a283c8362..7d90b1f94 100644 --- a/src/portable/dialog/da146xx/dcd_da146xx.c +++ b/src/portable/dialog/da146xx/dcd_da146xx.c @@ -148,8 +148,8 @@ typedef struct #ifndef TU_DA146XX_DMA_RX_CHANNEL #define TU_DA146XX_DMA_RX_CHANNEL 6 #endif -#define DA146XX_DMA_USB_MUX (0x6 << (TU_DA146XX_DMA_RX_CHANNEL * 2)) -#define DA146XX_DMA_USB_MUX_MASK (0xF << (TU_DA146XX_DMA_RX_CHANNEL * 2)) +#define DA146XX_DMA_USB_MUX (0x6u << (TU_DA146XX_DMA_RX_CHANNEL * 2)) +#define DA146XX_DMA_USB_MUX_MASK (0xFu << (TU_DA146XX_DMA_RX_CHANNEL * 2)) typedef struct { @@ -311,12 +311,12 @@ static void fill_tx_fifo(xfer_ctl_t * xfer) // Max packet size is set to value greater then FIFO. Enable fifo level warning // to handle larger packets. regs->txc |= (3 << USB_USB_TXC1_REG_USB_TFWL_Pos); - USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos); + USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos); } else { regs->txc &= ~USB_USB_TXC1_REG_USB_TFWL_Msk; - USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos)); + USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos)); // Whole packet already in fifo, no need to refill it later. Mark last. regs->txc |= USB_USB_TXC1_REG_USB_LAST_Msk; } @@ -371,14 +371,14 @@ static void start_rx_packet(xfer_ctl_t *xfer) // For endpoint size greater than FIFO size enable FIFO level warning interrupt // when FIFO has less than 17 bytes free. regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk; - USB->USB_FWMSK_REG |= 1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos); + USB->USB_FWMSK_REG |= 1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos); } } else if (epnum != 0) { // If max_packet_size would fit in FIFO no need for FIFO level warning interrupt. regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk; - USB->USB_FWMSK_REG &= ~(1 << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos)); + USB->USB_FWMSK_REG &= ~(1u << (epnum - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos)); } regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk; } @@ -388,7 +388,7 @@ static void start_tx_dma(void *src, volatile void *dst, uint16_t size) // Setup SRC and DST registers TX_DMA_REGS->DMAx_A_START_REG = (uint32_t)src; TX_DMA_REGS->DMAx_B_START_REG = (uint32_t)dst; - // Interrupt not needed + // Interrupt is not needed TX_DMA_REGS->DMAx_INT_REG = size; TX_DMA_REGS->DMAx_LEN_REG = size - 1; TX_DMA_REGS->DMAx_CTRL_REG = TX_DMA_START; @@ -430,7 +430,9 @@ static uint16_t read_rx_fifo(xfer_ctl_t *xfer, uint16_t bytes_in_fifo) uint8_t *buf = xfer->buffer + xfer->transferred + xfer->last_packet_size; - for (int i = 0; i < receive_this_time; ++i) buf[i] = regs->rxd; + for (int i = 0; i < receive_this_time; ++i) { + buf[i] = (uint8_t)regs->rxd; + } xfer->last_packet_size += receive_this_time; @@ -449,7 +451,9 @@ static void handle_ep0_rx(void) { xfer_ctl_t *xfer_in = XFER_CTL_BASE(0, TUSB_DIR_IN); // Setup packet is in - for (int i = 0; i < fifo_bytes; ++i) _setup_packet[i] = USB->USB_RXD0_REG; + for (int i = 0; i < fifo_bytes; ++i) { + _setup_packet[i] = (uint8_t)USB->USB_RXD0_REG; + } xfer->stall = 0; xfer->data1 = 1; @@ -469,7 +473,7 @@ static void handle_ep0_rx(void) } else { - read_rx_fifo(xfer, fifo_bytes); + read_rx_fifo(xfer, (uint16_t)fifo_bytes); if (rxs0 & USB_USB_RXS0_REG_USB_RX_LAST_Msk) { xfer->transferred += xfer->last_packet_size; @@ -553,7 +557,7 @@ static void handle_epx_rx_ev(uint8_t ep) { // Disable DMA and update last_packet_size with what DMA reported. RX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA0_CTRL_REG_DMA_ON_Msk; - xfer->last_packet_size = RX_DMA_REGS->DMAx_IDX_REG; + xfer->last_packet_size = (uint16_t)RX_DMA_REGS->DMAx_IDX_REG; // When DMA did not finished (packet was smaller then MPS), DMAx_IDX_REG holds exact number of bytes transmitted. // When DMA finished value in DMAx_IDX_REG is one less then actual number of transmitted bytes. if (xfer->last_packet_size == RX_DMA_REGS->DMAx_LEN_REG) xfer->last_packet_size++; @@ -564,7 +568,7 @@ static void handle_epx_rx_ev(uint8_t ep) // FIFO maybe empty if DMA read it before or it's final iteration and function already read all that was to read. if (fifo_bytes > 0) { - fifo_bytes = read_rx_fifo(xfer, fifo_bytes); + fifo_bytes = read_rx_fifo(xfer, (uint16_t)fifo_bytes); } if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST)) { @@ -624,7 +628,7 @@ static void handle_epx_tx_ev(xfer_ctl_t *xfer) { // Disable DMA and update last_packet_size with what DMA reported. TX_DMA_REGS->DMAx_CTRL_REG &= ~DMA_DMA1_CTRL_REG_DMA_ON_Msk; - xfer->last_packet_size = TX_DMA_REGS->DMAx_IDX_REG + 1; + xfer->last_packet_size = (uint16_t)(TX_DMA_REGS->DMAx_IDX_REG + 1); // Release DMA to used by other endpoints. _dcd.dma_ep[TUSB_DIR_IN] = 0; } @@ -954,13 +958,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) if (dir == TUSB_DIR_OUT) { regs->epc_out = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask; - USB->USB_RXMSK_REG |= 0x11 << (epnum - 1); + USB->USB_RXMSK_REG |= 0x11u << (epnum - 1); REG_SET_BIT(USB_MAMSK_REG, USB_M_RX_EV); } else { regs->epc_in = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask; - USB->USB_TXMSK_REG |= 0x11 << (epnum - 1); + USB->USB_TXMSK_REG |= 0x11u << (epnum - 1); REG_SET_BIT(USB_MAMSK_REG, USB_M_TX_EV); } } @@ -974,8 +978,8 @@ void dcd_edpt_close_all (uint8_t rhport) for (int epnum = 1; epnum < EP_MAX; ++epnum) { - dcd_edpt_close(0, epnum | TUSB_DIR_OUT); - dcd_edpt_close(0, epnum | TUSB_DIR_IN); + dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_OUT)); + dcd_edpt_close(0, (uint8_t)(epnum | TUSB_DIR_IN)); } } @@ -1001,7 +1005,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { regs->rxc = USB_USB_RXC1_REG_USB_FLUSH_Msk; regs->epc_out = 0; - USB->USB_RXMSK_REG &= ~(0x11 << (epnum - 1)); + USB->USB_RXMSK_REG &= ~(0x11u << (epnum - 1)); // Release DMA if needed if (_dcd.dma_ep[TUSB_DIR_OUT] == epnum) { @@ -1013,7 +1017,7 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { regs->txc = USB_USB_TXC1_REG_USB_FLUSH_Msk; regs->epc_in = 0; - USB->USB_TXMSK_REG &= ~(0x11 << (epnum - 1)); + USB->USB_TXMSK_REG &= ~(0x11u << (epnum - 1)); // Release DMA if needed if (_dcd.dma_ep[TUSB_DIR_IN] == epnum) { diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 64f9ebacf..1d1280bf4 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -50,36 +50,133 @@ * MACRO TYPEDEF CONSTANT ENUM DECLARATION *------------------------------------------------------------------*/ -#define REQUEST_TYPE_INVALID (0xFFu) - typedef union { volatile uint8_t u8; volatile uint16_t u16; volatile uint32_t u32; } hw_fifo_t; -typedef struct TU_ATTR_PACKED -{ - void *buf; /* the start address of a transfer data buffer */ +typedef struct { + union { + uint8_t *buf; /* the start address of a transfer data buffer */ + tu_fifo_t *fifo; + }; uint16_t length; /* the number of bytes in the buffer */ uint16_t remaining; /* the number of bytes remaining in the buffer */ + bool armed; /* true while a transfer is posted */ + bool use_fifo; /* true: buf is tu_fifo_t*; false: buf is plain byte pointer. */ } pipe_state_t; -typedef struct -{ - union { - tusb_control_request_t setup_packet; - uint32_t setup_buffer[2]; - }; - uint16_t remaining_ctrl; /* The number of bytes remaining in data stage of control transfer. */ - int8_t status_out; - pipe_state_t pipe0; - pipe_state_t pipe[2][TUP_DCD_ENDPOINT_MAX-1]; /* pipe[direction][endpoint number - 1] */ - uint16_t pipe_buf_is_fifo[2]; /* Bitmap. Each bit means whether 1:TU_FIFO or 0:POD. */ +// Pipe array layout (N = TUP_DCD_ENDPOINT_MAX). EP0 has its own scalars in +// dcd_data_t and does not occupy a pipe slot. +// One-direction-only IPs (CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY=1): +// [0..N-2] : EP1..N-1 (single slot per endpoint) +// Bidirectional-capable IPs: +// [0..N-2 ] : EP1..N-1 OUT +// [N-1..2*N-3 ] : EP1..N-1 IN +#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY + #define MUSB_PIPE_COUNT (TUP_DCD_ENDPOINT_MAX - 1u) +#else + #define MUSB_PIPE_COUNT (2u * (TUP_DCD_ENDPOINT_MAX - 1u)) +#endif + +enum { + PIPE0_STATE_IDLE = 0, // no active control transfer + PIPE0_STATE_DATA_IN, // DATA IN stage + PIPE0_STATE_DATA_OUT, // DATA OUT stage + PIPE0_STATE_STATUS_IN, // STATUS IN โ device sends IN-ZLP; awaits send-ACK IRQ + PIPE0_STATE_STATUS_OUT, // post-DATAEND, neither edpt0_xfer(STATUS OUT) nor confirmation IRQ has happened yet + PIPE0_STATE_STATUS_OUT_PENDING_XFER, // edpt0_xfer(STATUS OUT) called first; the confirmation IRQ fires xfer_complete + PIPE0_STATE_STATUS_OUT_PENDING_IRQ, // confirmation IRQ seen (or synthesized) first; edpt0_xfer(STATUS OUT) fires xfer_complete +}; + +// EP0 control-transfer state (own scalars, not a pipe[] slot). +typedef struct { + uint8_t *buf; // DATA OUT drain target (only valid while EP0 is in DATA OUT stage) + uint16_t xact_len; // DATA IN chunk length armed via edpt0_xfer; reported in its xfer_complete (OUT reports count0) + uint16_t remain_wlength; // bytes remaining in the control transfer's DATA stage + uint8_t state; + uint8_t pending_addr; // new USB address latched by dcd_set_address; applied when STATUS IN completes + bool rxrdy_consumed; // RxPktRdy left set in hw for an already-consumed packet (NAK flow control); + // RXRDY events are stale while set. Cleared when RXRDYC is written. + bool deferred_setup_valid; + uint32_t deferred_setup[2]; // raw SETUP words, replayed via pipe0_start_setup +} pipe0_state_t; + +typedef struct { + pipe0_state_t pipe0; + pipe_state_t pipe[MUSB_PIPE_COUNT]; } dcd_data_t; static dcd_data_t _dcd; +// Read the 8-byte SETUP packet (2 words) from the EP0 FIFO into setup[]. Does not ack RxPktRdy. +static bool pipe0_read_setup(musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr, uint32_t setup[2]) { + TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0); + setup[0] = musb_regs->fifo[0]; + setup[1] = musb_regs->fifo[0]; + return true; +} + +static void pipe0_start_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, + const uint32_t setup[2], bool is_isr) { + tusb_control_request_t const* req = (tusb_control_request_t const*) setup; + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->remain_wlength = req->wLength; + + if (req->wLength == 0) { + // Leave RXRDY set; edpt0_xfer(STATUS IN) acks it together with DATAEND. + pipe0->state = PIPE0_STATE_STATUS_IN; + pipe0->rxrdy_consumed = true; + } else { + if (req->bmRequestType & TUSB_DIR_IN_MASK) { + pipe0->state = PIPE0_STATE_DATA_IN; + // On a deferred replay the packet's RXRDY stays parked until the edpt0_xfer(DATA IN) arm + // acks it โ a stale latched EP0 IRQ in between is gated by rxrdy_consumed. + if (!pipe0->rxrdy_consumed) { + ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + } + } else { + // If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data. + // Deliberate deviation from the databook's canonical flow (ack right after unload), + // used as NAK flow control until usbd arms the drain buffer. + pipe0->state = PIPE0_STATE_DATA_OUT; + pipe0->rxrdy_consumed = true; + } + } + + dcd_event_setup_received(rhport, (const uint8_t *) setup, is_isr); +} + +// Replay a previously deferred SETUP, if any. +static void pipe0_try_deferred_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, bool is_isr) { + pipe0_state_t* pipe0 = &_dcd.pipe0; + if (!pipe0->deferred_setup_valid) { + return; + } + + pipe0->deferred_setup_valid = false; + pipe0_start_setup(rhport, ep_csr, pipe0->deferred_setup, is_isr); +} + +// Last DATA packet: wLength satisfied, or a short packet (incl. ZLP) ends the stage. +TU_ATTR_ALWAYS_INLINE static inline bool pipe0_data_stage_done(uint16_t xfer_len) { + return _dcd.pipe0.remain_wlength == 0 || xfer_len < CFG_TUD_ENDPOINT0_SIZE; +} + +// EP0 must not call this โ it has its own scalars in dcd_data_t. +TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_dir_t epdir) { + size_t idx = epnum - 1u; +#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY + (void) epdir; +#else + if (epdir == TUSB_DIR_IN) { + idx += TUP_DCD_ENDPOINT_MAX - 1u; + } +#endif + return &_dcd.pipe[idx]; +} + //-------------------------------------------------------------------- // HW FIFO Helper // Note: Index register is already set by caller @@ -110,7 +207,6 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps, bool double_packet) { - (void) epnum; uint8_t ffsize = hwfifo_byte2size(mps); mps = 8 << ffsize; // round up to the next power of 2 @@ -123,6 +219,13 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign musb->fifo_addr[is_rx] = alloced_fifo_bytes / 8; musb->fifo_size[is_rx] = ffsize; + volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable; + if (double_packet) { + *dp_disable &= ~(1u << epnum); + } else { + *dp_disable |= (1u << epnum); + } + alloced_fifo_bytes += mps; return true; } @@ -136,18 +239,29 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigne TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps, bool double_packet) { - (void) epnum; (void) mps; - if (!double_packet) { - #if defined(TUP_USBIP_MUSB_ADI) - musb->indexed_csr.maxp_csr[is_rx].csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET(is_rx); - #else - if (is_rx) { - musb->rx_doulbe_packet_disable |= 1u << epnum; - } else { - musb->tx_double_packet_disable |= 1u << epnum; - } - #endif + (void) mps; + + #if defined(TUP_USBIP_MUSB_ADI) + // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B. + // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies. + // Force single-buffered on EP < 8 even if the caller requested DPB. + if (epnum < 8 && (musb->power & MUSB_POWER_HSMODE)) { + double_packet = false; + } + volatile uint8_t* csrh = &musb->indexed_csr.maxp_csr[is_rx].csrh; + if (double_packet) { + *csrh &= ~MUSB_CSRH_DISABLE_DOUBLE_PACKET; + } else { + *csrh |= MUSB_CSRH_DISABLE_DOUBLE_PACKET; } + #else + volatile uint16_t* dp_disable = is_rx ? &musb->rx_doulbe_packet_disable : &musb->tx_double_packet_disable; + if (double_packet) { + *dp_disable &= ~(1u << epnum); + } else { + *dp_disable |= (1u << epnum); + } + #endif return true; } @@ -167,322 +281,359 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigne } } -static void process_setup_packet(uint8_t rhport) { - musb_regs_t* musb_regs = MUSB_REGS(rhport); - - // Read setup packet - _dcd.setup_buffer[0] = musb_regs->fifo[0]; - _dcd.setup_buffer[1] = musb_regs->fifo[0]; - - _dcd.pipe0.buf = NULL; - _dcd.pipe0.length = 0; - _dcd.pipe0.remaining = 0; - dcd_event_setup_received(rhport, (const uint8_t*)(uintptr_t)&_dcd.setup_packet, true); - - const unsigned len = _dcd.setup_packet.wLength; - _dcd.remaining_ctrl = len; - const unsigned dir_in = tu_edpt_dir(_dcd.setup_packet.bmRequestType); - /* Clear RX FIFO and reverse the transaction direction */ - if (len && dir_in) { - musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); - ep_csr->csr0l = MUSB_CSRL0_RXRDYC; +// write to txfifo using pipe_state_t info +static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) { + musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; + const uint16_t mps = ep_csr->tx_maxp & MUSB_TXMAXP_PACKET_SIZE_M; + const uint16_t xact_len = tu_min16(mps, pipe->remaining); + volatile void *hwfifo = &musb_regs->fifo[epnum]; + if (xact_len) { + if (pipe->use_fifo) { + tu_hwfifo_write_from_fifo(hwfifo, pipe->fifo, xact_len, NULL); + } else { + tu_hwfifo_write(hwfifo, pipe->buf, xact_len, NULL); + pipe->buf += xact_len; + } + pipe->remaining -= xact_len; } + ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY; } -static bool handle_xfer_in(uint8_t rhport, uint_fast8_t ep_addr) { - unsigned epnum = tu_edpt_number(ep_addr); - unsigned epnum_minus1 = epnum - 1; - pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1]; - const unsigned rem = pipe->remaining; +// Called from the TX interrupt. If the last queued packet finished the transfer, +// signal completion; otherwise queue the next packet. +static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum) { + musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); + const uint_fast8_t csrl = ep_csr->tx_csrl; + if (csrl & MUSB_TXCSRL1_STALLED) { + ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN); + return; // sent STALL, do nothing + } - if (rem == 0 && pipe->length > 0) { + pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN); + if (pipe->remaining > 0) { + pipe_write(musb_regs, pipe, epnum); + } else { + // All bytes have been loaded into the FIFO. With double-packet buffering a + // second packet may still be waiting in the FIFO when this IRQ fires (the + // hardware signals TXRDY clear as soon as a slot frees, not when the wire + // transfer finishes). Defer completion until FIFONE == 0 so we don't emit + // a duplicate xfer_complete before the final packet has been sent. + if (csrl & MUSB_TXCSRL1_FIFONE) { + return; + } + const uint16_t xferred_len = pipe->length; pipe->buf = NULL; - return true; + pipe->armed = false; + dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, TUSB_DIR_IN), xferred_len, XFER_RESULT_SUCCESS, true); } +} - musb_regs_t* musb_regs = MUSB_REGS(rhport); - musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); - const unsigned mps = ep_csr->tx_maxp; - const unsigned len = TU_MIN(mps, rem); - void *buf = pipe->buf; - volatile void *fifo_ptr = &musb_regs->fifo[epnum]; - // TU_LOG1(" %p mps %d len %d rem %d\r\n", buf, mps, len, rem); - if (len) { - if (_dcd.pipe_buf_is_fifo[TUSB_DIR_IN] & TU_BIT(epnum_minus1)) { - tu_hwfifo_write_from_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL); +// Drain one packet from the Rx FIFO into pipe->buf/fifo, update pipe state, and +// release the FIFO slot by clearing RXRDY. return true if short packet +static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) { + musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; // index already set in process_epout_isr() + const uint16_t mps = ep_csr->rx_maxp & MUSB_RXMAXP_PACKET_SIZE_M; + const uint16_t rx_count = ep_csr->rx_count; + const uint16_t xact_len = tu_min16(tu_min16(pipe->remaining, mps), rx_count); + volatile void *hwfifo = &musb_regs->fifo[epnum]; + if (xact_len) { + if (pipe->use_fifo) { + tu_hwfifo_read_to_fifo(hwfifo, pipe->fifo, xact_len, NULL); } else { - tu_hwfifo_write(fifo_ptr, buf, len, NULL); - pipe->buf = (uint8_t*)buf + len; + tu_hwfifo_read(hwfifo, pipe->buf, xact_len, NULL); + pipe->buf += xact_len; } - pipe->remaining = rem - len; + pipe->remaining -= xact_len; } - ep_csr->tx_csrl = MUSB_TXCSRL1_TXRDY; - // TU_LOG1(" TXCSRL%d = %x %d\r\n", epnum, ep_csr->tx_csrl, rem - len); - return false; + ep_csr->rx_csrl = 0; /* Clear RXRDY - release this FIFO slot */ + + return (xact_len < mps); } -static bool handle_xfer_out(uint8_t rhport, uint_fast8_t ep_addr) -{ - unsigned epnum = tu_edpt_number(ep_addr); - unsigned epnum_minus1 = epnum - 1; - pipe_state_t *pipe = &_dcd.pipe[tu_edpt_dir(ep_addr)][epnum_minus1]; - musb_regs_t* musb_regs = MUSB_REGS(rhport); +static void process_epout_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum, bool is_isr) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); - // TU_LOG1(" RXCSRL%d = %x\r\n", epnum_minus1 + 1, ep_csr->rx_csrl); - - //Fail gracefully. Spurious interrupt. - if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) return false; + if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) { + ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER); + return; // sent STALL, do nothing + } - void *buf = pipe->buf; - if (buf == NULL) { - ep_csr->rx_csrl = MUSB_RXCSRL1_FLUSH; - return false; + // Fail gracefully. Spurious interrupt. + if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) { + return; } - const unsigned mps = ep_csr->rx_maxp; - const unsigned rem = pipe->remaining; - const unsigned vld = ep_csr->rx_count; - const unsigned len = TU_MIN(TU_MIN(rem, mps), vld); - volatile void *fifo_ptr = &musb_regs->fifo[epnum]; - if (len) { - if (_dcd.pipe_buf_is_fifo[TUSB_DIR_OUT] & TU_BIT(epnum_minus1)) { - tu_hwfifo_read_to_fifo(fifo_ptr, (tu_fifo_t *)buf, len, NULL); - } else { - tu_hwfifo_read(fifo_ptr, buf, len, NULL); - pipe->buf = (uint8_t*)buf + len; - } - pipe->remaining = rem - len; + pipe_state_t *pipe = pipe_get(epnum, TUSB_DIR_OUT); + if (!pipe->armed) { + // Packet is already ACK'd by hardware and sitting in the Rx FIFO, but no transfer is + // posted. Do NOT flush (per MUSB spec ยง3.3.11 FlushFIFO) - that would silently drop + // acknowledged data. Mask this endpoint's Rx interrupt so the ISR stops re-firing; + // the FIFO stays occupied so hardware NAKs further OUT tokens (natural backpressure). + // The next dcd_edpt_xfer() on this endpoint will drain the staged packet. + musb_regs->intr_rxen &= (uint16_t) ~TU_BIT(epnum); + return; } - ep_csr->rx_csrl = 0; /* Always Clear RXRDY bit */ - if ((len < mps) || (rem == len)) { + const bool is_short = pipe_read(musb_regs, pipe, epnum); + + // Transfer completes on a short packet or when the rx buffer is filled. + if (is_short || pipe->remaining == 0) { + const uint16_t xferred_len = pipe->length - pipe->remaining; pipe->buf = NULL; - return NULL != buf; + pipe->armed = false; + dcd_event_xfer_complete(rhport, epnum, xferred_len, XFER_RESULT_SUCCESS, is_isr); } - return false; } -static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - unsigned epnum = tu_edpt_number(ep_addr); - unsigned epnum_minus1 = epnum - 1; - unsigned dir_in = tu_edpt_dir(ep_addr); +static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t total_bytes, bool use_fifo, bool is_isr) { + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir_in = tu_edpt_dir(ep_addr); + + pipe_state_t *pipe = pipe_get(epnum, dir_in); + if (use_fifo) { + pipe->fifo = (tu_fifo_t *)buffer; + } else { + pipe->buf = (uint8_t *)buffer; + } + pipe->length = total_bytes; + pipe->remaining = total_bytes; + pipe->use_fifo = use_fifo; + pipe->armed = true; - pipe_state_t *pipe = &_dcd.pipe[dir_in][epnum_minus1]; - pipe->buf = buffer; - pipe->length = total_bytes; - pipe->remaining = total_bytes; + musb_regs_t *musb_regs = MUSB_REGS(rhport); + musb_ep_csr_t *ep_csr = get_ep_csr(musb_regs, epnum); if (dir_in) { - handle_xfer_in(rhport, ep_addr); + pipe_write(musb_regs, pipe, epnum); } else { - musb_regs_t* musb_regs = MUSB_REGS(rhport); - musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epnum); - if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) ep_csr->rx_csrl = 0; + // Re-enable Rx interrupt (may have been masked by the no-buffer path in process_epout_isr) + musb_regs->intr_rxen |= (uint16_t)TU_BIT(epnum); + + // Drain any packet staged in the Rx FIFO from a prior no-buffer interrupt. + // process_epout_isr() fires dcd_event_xfer_complete() itself if the drain completes. + if (ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY) { + process_epout_isr(rhport, musb_regs, epnum, is_isr); + } } return true; } -static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void)rhport; - TU_ASSERT(total_bytes <= 64); /* Current implementation supports for only up to 64 bytes. */ +static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + TU_ASSERT(total_bytes <= CFG_TUD_ENDPOINT0_SIZE); /* EP0 only supports 1 packet per dcd_edpt_xfer()*/ musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); - const unsigned req = _dcd.setup_packet.bmRequestType; - TU_ASSERT(req != REQUEST_TYPE_INVALID || total_bytes == 0); - - if (req == REQUEST_TYPE_INVALID || _dcd.status_out) { - /* STATUS OUT stage. - * MUSB controller automatically handles STATUS OUT packets without - * software helps. We do not have to do anything. And STATUS stage - * may have already finished and received the next setup packet - * without calling this function, so we have no choice but to - * invoke the callback function of status packet here. */ - // TU_LOG1(" STATUS OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l); - _dcd.status_out = 0; - if (req == REQUEST_TYPE_INVALID) { - dcd_event_xfer_complete(rhport, ep_addr, total_bytes, XFER_RESULT_SUCCESS, false); - } else { - /* The next setup packet has already been received, it aborts - * invoking callback function to avoid confusing TUSB stack. */ - TU_LOG1("Drop CONTROL_STAGE_ACK\r\n"); - } - return true; - } + pipe0_state_t* pipe0 = &_dcd.pipe0; const unsigned dir_in = tu_edpt_dir(ep_addr); - if (tu_edpt_dir(req) == dir_in) { /* DATA stage */ - TU_ASSERT(total_bytes <= _dcd.remaining_ctrl); - const unsigned rem = _dcd.remaining_ctrl; - const unsigned len = TU_MIN(TU_MIN(rem, 64), total_bytes); - volatile void *fifo_ptr = &musb_regs->fifo[0]; - if (dir_in) { - tu_hwfifo_write(fifo_ptr, buffer, len, NULL); - _dcd.pipe0.buf = buffer + len; - _dcd.pipe0.length = len; - _dcd.pipe0.remaining = 0; - - _dcd.remaining_ctrl = rem - len; - if ((len < 64) || (rem == len)) { - _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; /* Change to STATUS/SETUP stage */ - _dcd.status_out = 1; - /* Flush TX FIFO and reverse the transaction direction. */ + switch (pipe0->state) { + // DATA stage exits on its last packet, so state matches the call direction here. + case PIPE0_STATE_DATA_IN: + TU_ASSERT(dir_in); + pipe0->xact_len = total_bytes; + if (pipe0->rxrdy_consumed) { // replayed SETUP: ack its parked RXRDY before loading the FIFO + ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + pipe0->rxrdy_consumed = false; + } + tu_hwfifo_write(&musb_regs->fifo[0], buffer, total_bytes, NULL); + pipe0->remain_wlength -= total_bytes; + // Add DATAEND on the last packet to end the data stage. + if (pipe0_data_stage_done(total_bytes)) { ep_csr->csr0l = MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND; } else { - ep_csr->csr0l = MUSB_CSRL0_TXRDY; /* Flush TX FIFO to return ACK. */ + ep_csr->csr0l = MUSB_CSRL0_TXRDY; } - // TU_LOG1(" IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l); - } else { - // TU_LOG1(" OUT ep_csr->csr0l = %x\r\n", ep_csr->csr0l); - _dcd.pipe0.buf = buffer; - _dcd.pipe0.length = len; - _dcd.pipe0.remaining = len; - ep_csr->csr0l = MUSB_CSRL0_RXRDYC; /* Clear RX FIFO to return ACK. */ - } - } else if (dir_in) { - // TU_LOG1(" STATUS IN ep_csr->csr0l = %x\r\n", ep_csr->csr0l); - _dcd.pipe0.buf = NULL; - _dcd.pipe0.length = 0; - _dcd.pipe0.remaining = 0; - /* Clear RX FIFO and reverse the transaction direction */ - ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND; + break; + + case PIPE0_STATE_DATA_OUT: + TU_ASSERT(!dir_in); + pipe0->xact_len = total_bytes; + pipe0->buf = buffer; // arm drain target, ack RXRDY so host can send DATA OUT + ep_csr->csr0l = MUSB_CSRL0_RXRDYC; + pipe0->rxrdy_consumed = false; + break; + + case PIPE0_STATE_STATUS_IN: + TU_ASSERT(dir_in && total_bytes == 0); // only STATUS IN allowed + ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND; + pipe0->rxrdy_consumed = false; + break; + + case PIPE0_STATE_STATUS_OUT: + TU_ASSERT(!dir_in && total_bytes == 0); // only STATUS OUT allowed + // First event of the STATUS OUT pair โ wait for the IRQ to fire complete. + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_XFER; + break; + + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + // Second event โ IRQ already arrived, fire complete now. The old transfer is retired here, + // so a deferred SETUP can be replayed safely. + pipe0->state = PIPE0_STATE_IDLE; + dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, is_isr); + pipe0_try_deferred_setup(rhport, ep_csr, is_isr); + break; + + default: break; } + return true; } -static void process_ep0(uint8_t rhport) -{ +// Advance EP0's status-stage state machine on a tail event: the csrl==0 confirmation IRQ, or such a +// confirmation combined with a new SETUP (caller sets deferred_setup_valid first). ISR context only. +static void pipe0_process_xfer_state_isr(uint8_t rhport, musb_regs_t* musb_regs, musb_ep_csr_t* ep_csr) { + pipe0_state_t* pipe0 = &_dcd.pipe0; + switch (pipe0->state) { + case PIPE0_STATE_DATA_IN: + if (pipe0_data_stage_done(pipe0->xact_len)) { + if (pipe0->deferred_setup_valid) { + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ; // status confirm coalesced with deferred SETUP + } else { + pipe0->state = PIPE0_STATE_STATUS_OUT; // await host's STATUS-OUT ZLP IRQ + } + } + dcd_event_xfer_complete(rhport, TU_EP0_IN, pipe0->xact_len, XFER_RESULT_SUCCESS, true); + break; + + case PIPE0_STATE_STATUS_OUT: + // Confirmation seen โ await edpt0_xfer(STATUS OUT) to fire complete. + pipe0->state = PIPE0_STATE_STATUS_OUT_PENDING_IRQ; + break; + + case PIPE0_STATE_STATUS_OUT_PENDING_XFER: + // edpt0_xfer(STATUS OUT) already called โ fire complete and replay now. + pipe0->state = PIPE0_STATE_IDLE; + dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true); + pipe0_try_deferred_setup(rhport, ep_csr, true); + break; + + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + // Confirmation already accounted for โ the pairing edpt0_xfer(STATUS OUT) fires complete. + break; + + case PIPE0_STATE_STATUS_IN: + if (pipe0->pending_addr) { + musb_regs->faddr = pipe0->pending_addr; + pipe0->pending_addr = 0; + } + pipe0->state = PIPE0_STATE_IDLE; + dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true); + pipe0_try_deferred_setup(rhport, ep_csr, true); + break; + + default: break; + } +} + +// 21.1.5: endpoint 0 service routine as peripheral +static void process_ep0_isr(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); + pipe0_state_t* pipe0 = &_dcd.pipe0; uint_fast8_t csrl = ep_csr->csr0l; - // TU_LOG1(" EP0 ep_csr->csr0l = %x\r\n", csrl); - // 21.1.5: endpoint 0 service routine as peripheral - + // 21.1.5: SentStall and SetupEnd must be checked before anything else. if (csrl & MUSB_CSRL0_STALLED) { - /* Returned STALL packet to HOST. */ - ep_csr->csr0l = 0; /* Clear STALL */ + ep_csr->csr0l = 0; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; return; } - unsigned req = _dcd.setup_packet.bmRequestType; if (csrl & MUSB_CSRL0_SETEND) { - TU_LOG1(" ABORT by the next packets\r\n"); + // Host aborted the current control transfer (new SETUP or premature STATUS). + // do nothing, it is probably another setup packet, usbd will reset its state. ep_csr->csr0l = MUSB_CSRL0_SETENDC; - if (req != REQUEST_TYPE_INVALID && _dcd.pipe0.buf) { - /* DATA stage was aborted by receiving STATUS or SETUP packet. */ - _dcd.pipe0.buf = NULL; - _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; - dcd_event_xfer_complete(rhport, - req & TUSB_DIR_IN_MASK, - _dcd.pipe0.length - _dcd.pipe0.remaining, - XFER_RESULT_SUCCESS, true); + pipe0->state = PIPE0_STATE_IDLE; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; + if (!(csrl & MUSB_CSRL0_RXRDY)) { + return; /* no SETUP waiting behind it */ } - req = REQUEST_TYPE_INVALID; - if (!(csrl & MUSB_CSRL0_RXRDY)) return; /* Received SETUP packet */ } + // Receive Data (Setup or OUT) if (csrl & MUSB_CSRL0_RXRDY) { - /* Received SETUP or DATA OUT packet */ - if (req == REQUEST_TYPE_INVALID) { - /* SETUP */ - TU_ASSERT(sizeof(tusb_control_request_t) == ep_csr->count0,); - process_setup_packet(rhport); - return; + if (pipe0->rxrdy_consumed) { + return; // stale latched IRQ: this RXRDY's packet was already drained } - if (_dcd.pipe0.buf) { - /* DATA OUT */ - const unsigned vld = ep_csr->count0; - const unsigned rem = _dcd.pipe0.remaining; - const unsigned len = TU_MIN(TU_MIN(rem, 64), vld); - volatile void *fifo_ptr = &musb_regs->fifo[0]; - tu_hwfifo_read(fifo_ptr, _dcd.pipe0.buf, len, NULL); + switch (pipe0->state) { + case PIPE0_STATE_IDLE: { + uint32_t setup[2]; + TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, setup), ); + pipe0_start_setup(rhport, ep_csr, setup, true); + break; + } - _dcd.pipe0.remaining = rem - len; - _dcd.remaining_ctrl -= len; + case PIPE0_STATE_DATA_OUT: { + // EP0 OUT is single-packet (TU_ASSERT total_bytes <= EP0_SIZE in edpt0_xfer) + // so the whole packet drains in one shot. + const uint16_t count0 = ep_csr->count0; + if (count0) { + TU_ASSERT(pipe0->buf, ); + tu_hwfifo_read(&musb_regs->fifo[0], pipe0->buf, count0, NULL); + pipe0->remain_wlength -= tu_min16(count0, pipe0->remain_wlength); // clamp: host may overrun + } + // RXRDY stays set until the next edpt0_xfer arm acks it (NAK flow control): + // edpt0_xfer(DATA OUT) for a mid-stream packet, edpt0_xfer(STATUS IN) for the last. + pipe0->rxrdy_consumed = true; + if (pipe0_data_stage_done(count0)) { + pipe0->state = PIPE0_STATE_STATUS_IN; + } + dcd_event_xfer_complete(rhport, TU_EP0_OUT, count0, XFER_RESULT_SUCCESS, true); + break; + } - _dcd.pipe0.buf = NULL; - dcd_event_xfer_complete(rhport, - tu_edpt_addr(0, TUSB_DIR_OUT), - _dcd.pipe0.length - _dcd.pipe0.remaining, - XFER_RESULT_SUCCESS, true); - } - return; - } + // New SETUP arrived while the old control transfer's tail events are still in flight (IRQs + // combined under high CPU load): the old transfer's status confirm and this SETUP land together. + case PIPE0_STATE_DATA_IN: + case PIPE0_STATE_STATUS_OUT: + case PIPE0_STATE_STATUS_OUT_PENDING_XFER: + case PIPE0_STATE_STATUS_OUT_PENDING_IRQ: + case PIPE0_STATE_STATUS_IN: + // Save it, then finish the old transfer's tail event; deferred_setup_valid makes + // pipe0_process_xfer_state_isr() synthesize the coalesced status confirm and replay the SETUP + // once the old transfer is retired. Its RXRDY stays parked so a stale IRQ can't re-process it. + TU_VERIFY(pipe0_read_setup(musb_regs, ep_csr, pipe0->deferred_setup), ); + pipe0->deferred_setup_valid = true; + pipe0->rxrdy_consumed = true; + pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr); + break; - /* When CSRL0 is zero, it means that completion of sending a any length packet - * or receiving a zero length packet. */ - if (req != REQUEST_TYPE_INVALID && !tu_edpt_dir(req)) { - /* STATUS IN */ - if (*(const uint16_t*)(uintptr_t)&_dcd.setup_packet == 0x0500) { - /* The address must be changed on completion of the control transfer. */ - musb_regs->faddr = (uint8_t)_dcd.setup_packet.wValue; + default: break; } - _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; - dcd_event_xfer_complete(rhport, - tu_edpt_addr(0, TUSB_DIR_IN), - _dcd.pipe0.length - _dcd.pipe0.remaining, - XFER_RESULT_SUCCESS, true); + return; } - if (_dcd.pipe0.buf) { - /* DATA IN */ - _dcd.pipe0.buf = NULL; - dcd_event_xfer_complete(rhport, - tu_edpt_addr(0, TUSB_DIR_IN), - _dcd.pipe0.length - _dcd.pipe0.remaining, - XFER_RESULT_SUCCESS, true); - } -} -static void process_edpt_n(uint8_t rhport, uint_fast8_t ep_addr) -{ - bool completed; - const unsigned dir_in = tu_edpt_dir(ep_addr); - const unsigned epn = tu_edpt_number(ep_addr); - const unsigned epn_minus1 = epn - 1; - - musb_regs_t* musb_regs = MUSB_REGS(rhport); - musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); - if (dir_in) { - // TU_LOG1(" TX CSRL%d = %x\r\n", epn, ep_csr->tx_csrl); - if (ep_csr->tx_csrl & MUSB_TXCSRL1_STALLED) { - ep_csr->tx_csrl &= ~(MUSB_TXCSRL1_STALLED | MUSB_TXCSRL1_UNDRN); - return; - } - completed = handle_xfer_in(rhport, ep_addr); - } else { - // TU_LOG1(" RX CSRL%d = %x\r\n", epn, ep_csr->rx_csrl); - if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) { - ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER); - return; - } - completed = handle_xfer_out(rhport, ep_addr); + if (csrl & MUSB_CSRL0_DATAEND) { + // Last DATA IN chunk / STATUS IN arm wrote TXRDY|DATAEND and the status stage has not completed + // yet โ nothing to service. DataEnd is CPU-set-only per the CSR access table; whether it ever + // reads back 1 is vendor-dependent (on cores where it reads 0 this guard is dead code). + return; } - if (completed) { - pipe_state_t *pipe = &_dcd.pipe[dir_in][epn_minus1]; - dcd_event_xfer_complete(rhport, ep_addr, - pipe->length - pipe->remaining, - XFER_RESULT_SUCCESS, true); - } + /* When CSRL0 is zero, it means that either + * - completion of sending any length packet TxPktRdy clear + * - or status stage is complete (ZLP) after DataEnd is set */ + pipe0_process_xfer_state_isr(rhport, musb_regs, ep_csr); } // Upon BUS RESET is detected, hardware havs already done: // faddr = 0, index = 0, flushes all ep fifos, clears all ep csr, enabled all ep interrupts -static void process_bus_reset(uint8_t rhport) { +static void process_bus_reset_isr(uint8_t rhport) { musb_regs_t* musb = MUSB_REGS(rhport); #if MUSB_CFG_DYNAMIC_FIFO alloced_fifo_bytes = CFG_TUD_ENDPOINT0_SIZE; #endif - /* When bmRequestType is REQUEST_TYPE_INVALID(0xFF), a control transfer state is SETUP or STATUS stage. */ - _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; - _dcd.status_out = 0; - /* When pipe0.buf has not NULL, DATA stage works in progress. */ - _dcd.pipe0.buf = NULL; + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->buf = NULL; + pipe0->xact_len = 0; + pipe0->remain_wlength = 0; + pipe0->deferred_setup_valid = false; + pipe0->rxrdy_consumed = false; musb->intr_txen = 1; /* Enable only EP0 */ musb->intr_rxen = 0; @@ -544,18 +695,22 @@ void dcd_int_disable(uint8_t rhport) { musb_dcd_int_disable(rhport); } -// Receive Set Address request, mcu port must also include status IN response +// Receive Set Address request. Stash the new address here; hardware faddr is +// latched from pending_addr in process_ep0_isr once the STATUS IN completes (per +// USB spec, address must only take effect after the status stage). void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - (void)dev_addr; musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0); - _dcd.pipe0.buf = NULL; - _dcd.pipe0.length = 0; - _dcd.pipe0.remaining = 0; - /* Clear RX FIFO to return ACK. */ + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->pending_addr = dev_addr; + pipe0->buf = NULL; + pipe0->xact_len = 0; + pipe0->state = PIPE0_STATE_STATUS_IN; + /* Send STATUS IN ZLP with DATAEND; host ACK fires the confirmation IRQ. */ ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND; + pipe0->rxrdy_consumed = false; } // Wake up host @@ -595,39 +750,36 @@ void dcd_sof_enable(uint8_t rhport, bool en) //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ -// static void edpt_setup(musb_regs_t* musb, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_size){ -// const unsigned epn = tu_edpt_number(ep_addr); -// const unsigned dir_in = tu_edpt_dir(ep_addr); -// } // Configure endpoint's registers according to descriptor bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { const unsigned ep_addr = ep_desc->bEndpointAddress; const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir_in = tu_edpt_dir(ep_addr); + const tusb_dir_t epdir = tu_edpt_dir(ep_addr); const unsigned mps = tu_edpt_packet_size(ep_desc); - pipe_state_t *pipe = &_dcd.pipe[dir_in][epn - 1]; + pipe_state_t *pipe = pipe_get(epn, epdir); pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; + pipe->armed = false; musb_regs_t* musb = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn); - const uint8_t is_rx = 1 - dir_in; + const uint8_t is_rx = (1 - epdir); musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx]; maxp_csr->maxp = mps; maxp_csr->csrh = 0; #if MUSB_CFG_SHARED_FIFO - if (dir_in) { + if (epdir) { maxp_csr->csrh |= MUSB_CSRH_TX_MODE; } #endif hwfifo_flush(musb, epn, is_rx, true); - TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, false)); + TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK)); musb->intren_ep[is_rx] |= TU_BIT(epn); return true; @@ -646,16 +798,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) { const unsigned ep_addr = ep_desc->bEndpointAddress; const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir_in = tu_edpt_dir(ep_addr); + const tusb_dir_t dir_in = tu_edpt_dir(ep_addr); const unsigned mps = tu_edpt_packet_size(ep_desc); unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); - pipe_state_t *pipe = &_dcd.pipe[dir_in][epn - 1]; + pipe_state_t *pipe = pipe_get(epn, dir_in); pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; + pipe->armed = false; musb_regs_t* musb = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn); @@ -713,22 +866,22 @@ void dcd_edpt_close_all(uint8_t rhport) // Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; (void)rhport; bool ret; - // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes); unsigned const epnum = tu_edpt_number(ep_addr); unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); if (epnum) { - _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] &= ~TU_BIT(epnum - 1); - ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes); + ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes, false, is_isr); } else { - ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes); + (void) is_isr; + ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes, is_isr); } - if (ie) musb_dcd_int_enable(rhport); + if (ie) { + musb_dcd_int_enable(rhport); + } return ret; } @@ -736,16 +889,13 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t // - optional, however, must be listed in usbd.c bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr) { - (void) is_isr; (void)rhport; bool ret; - // TU_LOG1("X %x %d\r\n", ep_addr, total_bytes); unsigned const epnum = tu_edpt_number(ep_addr); TU_ASSERT(epnum); unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); - _dcd.pipe_buf_is_fifo[tu_edpt_dir(ep_addr)] |= TU_BIT(epnum - 1); - ret = edpt_n_xfer(rhport, ep_addr, (uint8_t*)ff, total_bytes); + ret = edpt_n_xfer(rhport, ep_addr, ff, total_bytes, true, is_isr); if (ie) musb_dcd_int_enable(rhport); return ret; } @@ -760,14 +910,27 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); if (0 == epn) { - if (!ep_addr) { /* Ignore EP80 */ - _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID; - _dcd.pipe0.buf = NULL; - ep_csr->csr0l = MUSB_CSRL0_STALL; + if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 IN */ + pipe0_state_t* pipe0 = &_dcd.pipe0; + pipe0->state = PIPE0_STATE_IDLE; + pipe0->buf = NULL; + if (pipe0->deferred_setup_valid) { + // A deferred SETUP means the stalled transfer already ended on the wire and the host's next + // request was ACKed โ SendStall would hit that innocent request. Replay it instead of stalling. + pipe0_try_deferred_setup(rhport, ep_csr, false); + } else { + // Forcing EP0 to IDLE: any RXRDY parked by the aborted transfer's flow control is stale, + // clear it so the next SETUP IRQ is not gated off. + pipe0->rxrdy_consumed = false; + ep_csr->csr0l = MUSB_CSRL0_STALL; + } } } else { - const uint8_t is_rx = 1 - tu_edpt_dir(ep_addr); + const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr); + const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u); ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_SEND_STALL(is_rx); + pipe_state_t* pipe = pipe_get(epn, ep_dir); + pipe->armed = false; } if (ie) musb_dcd_int_enable(rhport); @@ -812,7 +975,7 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); } if (intr_usb & MUSB_IS_RESET) { - process_bus_reset(rhport); + process_bus_reset_isr(rhport); } if (intr_usb & MUSB_IS_RESUME) { dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); @@ -822,21 +985,35 @@ void dcd_int_handler(uint8_t rhport) { } intr_tx &= musb_regs->intr_txen; /* Clear disabled interrupts */ - if (intr_tx & TU_BIT(0)) { - process_ep0(rhport); - intr_tx &= ~TU_BIT(0); - } + while (intr_tx) { - unsigned const num = __builtin_ctz(intr_tx); - process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_IN)); - intr_tx &= ~TU_BIT(num); + const unsigned epnum = __builtin_ctz(intr_tx); + if (epnum == 0) { + process_ep0_isr(rhport); // EP0 has its own state machine (control transfers) + } else { + process_epin_isr(rhport, musb_regs, epnum); + } + intr_tx &= ~TU_BIT(epnum); + + // Double packet endpoint: TxPktRdy is clear, and interrupt is generated immediately when 1st packet is written. + // Also catches EP0 SETUP arriving during bulk processing. + uint_fast8_t new_intr_tx = musb_regs->intr_tx; + new_intr_tx &= musb_regs->intr_txen; + + intr_tx |= new_intr_tx; } intr_rx &= musb_regs->intr_rxen; /* Clear disabled interrupts */ while (intr_rx) { - unsigned const num = __builtin_ctz(intr_rx); - process_edpt_n(rhport, tu_edpt_addr(num, TUSB_DIR_OUT)); - intr_rx &= ~TU_BIT(num); + unsigned const epnum = __builtin_ctz(intr_rx); + process_epout_isr(rhport, musb_regs, epnum, true); + intr_rx &= ~TU_BIT(epnum); + + // Double packet endpoint: RxPktRdy is set and interrupt is generated immediately if 2nd packet is received + uint_fast8_t new_intr_rx = musb_regs->intr_rx; + new_intr_rx &= musb_regs->intr_rxen; + + intr_rx |= new_intr_rx; } musb_regs->index = saved_index; // restore endpoint index diff --git a/src/portable/mentor/musb/musb_max32.h b/src/portable/mentor/musb/musb_max32.h index 599de2ca1..134b47122 100644 --- a/src/portable/mentor/musb/musb_max32.h +++ b/src/portable/mentor/musb/musb_max32.h @@ -47,7 +47,7 @@ extern "C" { #define MUSB_CFG_SHARED_FIFO 1 // shared FIFO for TX and RX endpoints #define MUSB_CFG_DYNAMIC_FIFO 0 // dynamic EP FIFO sizing -const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS }; +static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS }; #if CFG_TUD_ENABLED #define USBHS_M31_CLOCK_RECOVERY diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h index 68e89d77d..deaea8017 100644 --- a/src/portable/mentor/musb/musb_ti.h +++ b/src/portable/mentor/musb/musb_ti.h @@ -49,7 +49,7 @@ #define MUSB_CFG_DYNAMIC_FIFO 1 #define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096 -const uintptr_t MUSB_BASES[] = { USB0_BASE }; +static const uintptr_t MUSB_BASES[] = { USB0_BASE }; // Header supports both device and host modes. Only include what's necessary #if CFG_TUD_ENABLED diff --git a/src/portable/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h index b2f6492fa..3d3c3c834 100644 --- a/src/portable/mentor/musb/musb_type.h +++ b/src/portable/mentor/musb/musb_type.h @@ -300,7 +300,7 @@ TU_VERIFY_STATIC(sizeof(musb_regs_t) == 0x350, "size is not correct"); // Helper //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_regs, unsigned epnum) { - musb_regs->index = epnum; + musb_regs->index = (uint8_t)epnum; return &musb_regs->indexed_csr; } @@ -336,7 +336,7 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_ #define MUSB_CSRL_CLEAR_DATA_TOGGLE(_rx) (1u << ((_rx) ? 7 : 6)) // 0x13, 0x17: TX/RX CSRH -#define MUSB_CSRH_DISABLE_DOUBLE_PACKET(_rx) (1u << 1) +#define MUSB_CSRH_DISABLE_DOUBLE_PACKET (1u << 1) #define MUSB_CSRH_TX_MODE (1u << 5) // 1 = TX, 0 = RX. only relevant for SHARED FIFO #define MUSB_CSRH_ISO (1u << 6) @@ -568,6 +568,16 @@ TU_ATTR_ALWAYS_INLINE static inline musb_ep_csr_t* get_ep_csr(musb_regs_t* musb_ //***************************************************************************** // +// The following are defines for the bit fields in the MUSB_O_TXMAXP / MUSB_O_RXMAXP +// registers. Bits [10:0] carry the maximum packet size; bits [15:11] carry +// numpackminus1 (HB-iso / HS-bulk multiplier - 1). +// +//***************************************************************************** +#define MUSB_TXMAXP_PACKET_SIZE_M 0x07FFu +#define MUSB_RXMAXP_PACKET_SIZE_M 0x07FFu + +//***************************************************************************** +// // The following are defines for the bit fields in the MUSB_O_TXCSRL1 register. // //***************************************************************************** diff --git a/src/portable/microchip/samg/dcd_samg.c b/src/portable/microchip/samg/dcd_samg.c index 4115eecc5..f8980b775 100644 --- a/src/portable/microchip/samg/dcd_samg.c +++ b/src/portable/microchip/samg/dcd_samg.c @@ -352,8 +352,8 @@ void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) csr_clear(epnum, UDP_CSR_FORCESTALL_Msk); // must also reset EP to clear data toggle - UDP->UDP_RST_EP |= (1 << epnum); - UDP->UDP_RST_EP &= ~(1 << epnum); + UDP->UDP_RST_EP |= (1u << epnum); + UDP->UDP_RST_EP &= ~(1u << epnum); } //--------------------------------------------------------------------+ diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c index 8a41c4790..befbaa338 100644 --- a/src/portable/nordic/nrf5x/dcd_nrf5x.c +++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -36,6 +36,8 @@ #pragma GCC diagnostic ignored "-Wcast-qual" #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" #endif #include "nrf.h" @@ -461,7 +463,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t to xfer->actual_len = 0; // Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage - bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE)); + bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir((uint8_t)NRF_USBD->BMREQUESTTYPE)); if (control_status) { // The nRF doesn't interrupt on status transmit so we queue up a success response. @@ -1047,6 +1049,12 @@ void tusb_hal_nrf_power_event(uint32_t event) { NVIC_EnableIRQ(USBD_IRQn); } + // Ensure HFCLK is requested in the current context. The hfclk_enable() in + // USB_EVT_DETECTED may have been pre-SoftDevice. After Softdevice is + // enabled, HFXO is physically off again. So any caller that fires + // USB_EVT_READY post-SD would hang here. + hfclk_enable(); + // Wait for HFCLK while (!hfclk_running()) {} diff --git a/src/portable/nuvoton/nuc120/dcd_nuc120.c b/src/portable/nuvoton/nuc120/dcd_nuc120.c index d9a0e3fa8..2edb1bc7a 100644 --- a/src/portable/nuvoton/nuc120/dcd_nuc120.c +++ b/src/portable/nuvoton/nuc120/dcd_nuc120.c @@ -253,13 +253,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) /* mine the data for the information we need */ int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = tu_edpt_packet_size(p_endpoint_desc); + uint16_t const size = tu_edpt_packet_size(p_endpoint_desc); tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer; struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; /* allocate buffer from USB RAM */ ep->BUFSEG = bufseg_addr; - bufseg_addr += size; + bufseg_addr += (uint32_t)size; TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE); /* construct USB Configuration Register value and then write it */ @@ -435,7 +435,7 @@ void dcd_int_handler(uint8_t rhport) /* given ACK from host has happened, we can now set the address (if not already done) */ if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) USBD->FADDR = assigned_address; - uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD; + uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD; active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size); @@ -453,7 +453,7 @@ void dcd_int_handler(uint8_t rhport) { USBD->INTSTS = mask; - uint16_t const available_bytes = ep->MXPLD; + uint16_t const available_bytes = (uint16_t)ep->MXPLD; uint8_t const ep_addr = decode_ep_addr(ep); bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK); diff --git a/src/portable/nuvoton/nuc121/dcd_nuc121.c b/src/portable/nuvoton/nuc121/dcd_nuc121.c index 42fb58a0a..008c9df6b 100644 --- a/src/portable/nuvoton/nuc121/dcd_nuc121.c +++ b/src/portable/nuvoton/nuc121/dcd_nuc121.c @@ -42,6 +42,8 @@ #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" #endif #include "NuMicro.h" @@ -291,7 +293,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) /* mine the data for the information we need */ int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = tu_edpt_packet_size(p_endpoint_desc); + uint16_t const size = tu_edpt_packet_size(p_endpoint_desc); tusb_xfer_type_t const type = (tusb_xfer_type_t) p_endpoint_desc->bmAttributes.xfer; struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; @@ -478,7 +480,7 @@ void dcd_int_handler(uint8_t rhport) { if (status & USBD_INTSTS_EPEVT0_Msk) /* PERIPH_EP0 (EP0_IN) event: this is treated separately from the rest */ { - uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD; + uint16_t const available_bytes = (uint16_t)USBD->EP[PERIPH_EP0].MXPLD; active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size); @@ -496,7 +498,7 @@ void dcd_int_handler(uint8_t rhport) { USBD->INTSTS = mask; - uint16_t const available_bytes = ep->MXPLD; + uint16_t const available_bytes = (uint16_t)ep->MXPLD; uint8_t const ep_addr = decode_ep_addr(ep); bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK); diff --git a/src/portable/nuvoton/nuc505/dcd_nuc505.c b/src/portable/nuvoton/nuc505/dcd_nuc505.c index ca17d6251..a0f3d4c3f 100644 --- a/src/portable/nuvoton/nuc505/dcd_nuc505.c +++ b/src/portable/nuvoton/nuc505/dcd_nuc505.c @@ -42,6 +42,8 @@ #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" #endif #include "NUC505Series.h" @@ -338,13 +340,13 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) /* mine the data for the information we need */ int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = tu_edpt_packet_size(p_endpoint_desc); + uint16_t const size = tu_edpt_packet_size(p_endpoint_desc); tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer; struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; /* allocate buffer from USB RAM */ ep->EPBUFSTART = bufseg_addr; - bufseg_addr += size; + bufseg_addr += (uint32_t)size; ep->EPBUFEND = bufseg_addr - 1; TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE); diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c index 349229c8d..2840c6d5e 100644 --- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c @@ -131,7 +131,7 @@ static uint8_t sie_read (uint8_t cmd_code) //--------------------------------------------------------------------+ static inline uint8_t ep_addr2idx(uint8_t ep_addr) { - return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0); + return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0)); } static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) @@ -243,7 +243,7 @@ void dcd_sof_enable(uint8_t rhport, bool en) //--------------------------------------------------------------------+ static inline uint8_t byte2dword(uint8_t bytes) { - return (bytes + 3) / 4; // length in dwords + return (uint8_t)((bytes + 3) / 4); // length in dwords } static void control_ep_write(void const * buffer, uint8_t len) diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index 5f4a441dc..8adf0f840 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -243,7 +243,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer) } TU_ATTR_ALWAYS_INLINE static inline uint8_t ep_addr2id(uint8_t ep_addr) { - return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0); + return (uint8_t)(2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0)); } TU_ATTR_ALWAYS_INLINE static inline bool ep_is_iso(ep_cmd_sts_t* ep_cs, bool is_highspeed) { @@ -539,8 +539,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) { uint16_t buf_nbytes; if ( rhport_is_highspeed(rhport) ) { - buf_offset = ep_cs->buffer_hs.offset; - buf_nbytes = ep_cs->buffer_hs.nbytes; + buf_offset = (uint16_t)ep_cs->buffer_hs.offset; + buf_nbytes = (uint16_t)ep_cs->buffer_hs.nbytes; #if TU_CHECK_MCU(OPT_MCU_LPC54) // LPC54 Errata USB.2: In USB high-speed device mode, the NBytes field is not correct after BULK IN transfer @@ -550,8 +550,8 @@ static void process_xfer_isr(uint8_t rhport, uint32_t int_status) { } #endif } else { - buf_offset = ep_cs->buffer_fs.offset; - buf_nbytes = ep_cs->buffer_fs.nbytes; + buf_offset = (uint16_t)ep_cs->buffer_fs.offset; + buf_nbytes = (uint16_t)ep_cs->buffer_fs.nbytes; } xfer_dma->xferred_bytes += xfer_dma->nbytes - buf_nbytes; diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 02a4e055e..064834efb 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -617,10 +617,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *b io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); } else { - // Control endpoint can change direction 0x00 <-> 0x80 when changing stages - if (ep_addr != ep->ep_addr) { + // Control transfer data and status stages always start with DATA1, regardless of + // whether the direction changed since the previous stage. SET_REPORT (and any other + // host-to-device class request with an OUT data stage) keeps the same direction + // across SETUP -> DATA, so we cannot key off "direction changed" -- we must reset + // next_pid every time hcd_edpt_xfer is invoked on ep 0. Without this, the data stage + // of SET_REPORT goes out as DATA0 because ep->next_pid is still 0 from hcd_edpt_open(), + // which strict devices treat as a protocol violation and disconnect. + if (tu_edpt_number(ep_addr) == 0) { ep->ep_addr = ep_addr; - ep->next_pid = 1; // data and status stage start with DATA1 + ep->next_pid = 1; } // If EPX is busy with another transfer, mark as pending diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c index e2a51a5ca..adbb53787 100644 --- a/src/portable/renesas/rusb2/dcd_rusb2.c +++ b/src/portable/renesas/rusb2/dcd_rusb2.c @@ -93,7 +93,9 @@ static unsigned find_pipe(unsigned xfer_type) { const uint8_t idx_last = pipe_idx_arr[xfer_type][1]; for (int i = idx_last; i >= idx_first; i--) { - if (0 == _dcd.pipe[i].ep) return i; + if (0 == _dcd.pipe[i].ep) { + return (unsigned)i; + } } return 0; @@ -117,10 +119,10 @@ static volatile reg_pipetre_t* get_pipetre(rusb2_reg_t *rusb, unsigned num) { static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) { rusb2_reg_t *rusb = RUSB2_REG(rhport); - const unsigned epn = tu_edpt_number(ep_addr); + const unsigned epn = tu_edpt_number((uint8_t)ep_addr); if (epn) { - const unsigned dir = tu_edpt_dir(ep_addr); + const unsigned dir = tu_edpt_dir((uint8_t)ep_addr); const unsigned num = _dcd.ep[dir][epn]; return get_pipectr(rusb, num); } else { @@ -129,11 +131,11 @@ static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) { } static uint16_t edpt0_max_packet_size(rusb2_reg_t* rusb) { - return rusb->DCPMAXP_b.MXPS; + return (uint16_t)rusb->DCPMAXP_b.MXPS; } static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) { - rusb->PIPESEL = num; + rusb->PIPESEL = (uint16_t)num; return rusb->PIPEMAXP; } @@ -285,7 +287,7 @@ static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num) const uint16_t mps = edpt_max_packet_size(rusb, num); pipe_wait_for_ready(rusb, num); - const uint16_t vld = rusb->D0FIFOCTR_b.DTLN; + const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN; const uint16_t len = tu_min16(tu_min16(rem, mps), vld); void *buf = pipe->buf; @@ -498,7 +500,7 @@ static void process_bus_reset(uint8_t rhport) volatile uint16_t *ctr = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_CTR[0])); volatile uint16_t *tre = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_TR[0].E)); - for (int i = 1; i <= 5; ++i) { + for (uint16_t i = 1; i <= 5; ++i) { rusb->PIPESEL = i; rusb->PIPECFG = 0; *ctr = RUSB2_PIPE_CTR_ACLRM_Msk; @@ -508,7 +510,7 @@ static void process_bus_reset(uint8_t rhport) tre += 2; } - for (int i = 6; i <= 9; ++i) { + for (uint16_t i = 6; i <= 9; ++i) { rusb->PIPESEL = i; rusb->PIPECFG = 0; *ctr = RUSB2_PIPE_CTR_ACLRM_Msk; @@ -542,7 +544,7 @@ static void process_bus_reset(uint8_t rhport) static void process_set_address(uint8_t rhport) { rusb2_reg_t* rusb = RUSB2_REG(rhport); - const uint16_t addr = rusb->USBADDR_b.USBADDR; + const uint16_t addr = (uint16_t)rusb->USBADDR_b.USBADDR; if (!addr) { return; } @@ -706,7 +708,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) (void)rhport; rusb2_reg_t * rusb = RUSB2_REG(rhport); - const unsigned ep_addr = ep_desc->bEndpointAddress; + const uint8_t ep_addr = ep_desc->bEndpointAddress; const unsigned epn = tu_edpt_number(ep_addr); const unsigned dir = tu_edpt_dir(ep_addr); const unsigned xfer = ep_desc->bmAttributes.xfer; @@ -770,8 +772,10 @@ void dcd_edpt_close_all(uint8_t rhport) dcd_int_disable(rhport); while (--i) { /* Close all pipes except 0 */ const unsigned ep_addr = _dcd.pipe[i].ep; - if (!ep_addr) continue; - dcd_edpt_close(rhport, ep_addr); + if (!ep_addr) { + continue; + } + dcd_edpt_close(rhport, (uint8_t)ep_addr); } dcd_int_enable(rhport); } @@ -783,10 +787,10 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) const unsigned dir = tu_edpt_dir(ep_addr); const unsigned num = _dcd.ep[dir][epn]; - rusb->BRDYENB &= ~TU_BIT(num); + rusb->BRDYENB &= (uint16_t)~TU_BIT(num); volatile uint16_t *ctr = get_pipectr(rusb, num); *ctr = 0; - rusb->PIPESEL = num; + rusb->PIPESEL = (uint16_t)num; rusb->PIPECFG = 0; _dcd.pipe[num].ep = 0; _dcd.ep[dir][epn] = 0; @@ -860,7 +864,7 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) *ctr = RUSB2_PIPE_CTR_PID_BUF; } else { const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)]; - rusb->PIPESEL = num; + rusb->PIPESEL = (uint16_t)num; if (rusb->PIPECFG_b.TYPE != 1) { *ctr = RUSB2_PIPE_CTR_PID_BUF; } diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index 8b4719b21..6f7f490a8 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -41,6 +41,7 @@ * F302xB/C, F303xB/C, F373 512 byte buffer; no internal D+ pull-up * F302x6/8, F302xD/E2, F303xD/E 1024 byte buffer; no internal D+ pull-up * C0 2048 byte buffer; 32-bit bus; host mode + * C5 2048 byte buffer; 32-bit bus; host mode * G0 2048 byte buffer; 32-bit bus; host mode * G4 1024 byte buffer * H5 2048 byte buffer; 32-bit bus; host mode @@ -259,7 +260,7 @@ static void handle_ctr_tx(uint32_t ep_id) { } if (xfer->total_len != xfer->queued_len) { - dcd_transmit_packet(xfer, ep_id); + dcd_transmit_packet(xfer, (uint16_t)ep_id); } else { dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); } @@ -267,7 +268,7 @@ static void handle_ctr_tx(uint32_t ep_id) { static void handle_ctr_setup(uint32_t ep_id) { uint16_t rx_count = btable_get_count(ep_id, BTABLE_BUF_RX); - uint16_t rx_addr = btable_get_addr(ep_id, BTABLE_BUF_RX); + uint16_t rx_addr = (uint16_t)btable_get_addr(ep_id, BTABLE_BUF_RX); uint8_t setup_packet[8] TU_ATTR_ALIGNED(4); tu_hwfifo_read(PMA_BUF_AT(rx_addr), setup_packet, rx_count, NULL); @@ -342,7 +343,7 @@ void dcd_int_handler(uint8_t rhport) { uint32_t int_status = FSDEV_REG->ISTR; /* Put SOF flag at the beginning of ISR in case to get least amount of jitter if it is used for timing purposes */ - if (int_status & U_ISTR_SOF) { + if ((int_status & U_ISTR_SOF) && (FSDEV_REG->CNTR & U_CNTR_SOFM)) { FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF; dcd_event_sof(0, FSDEV_REG->FNR & U_FNR_FN, true); } @@ -393,26 +394,8 @@ void dcd_int_handler(uint8_t rhport) { const uint32_t ep_reg = ep_read(ep_id); if (ep_reg & U_EP_CTR_RX) { - #ifdef CFG_TUSB_FSDEV_32BIT - /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf - * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf - * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers - * Description: - * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM - * accesses have completed. If the software responds quickly to the interrupt, the full buffer contents may not be - * correct. Workaround: - * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay - * should be 800 ns in Full Speed mode and 6.4 ฮผs in Low Speed mode - * - Since H5 can run up to 250Mhz -> 1 cycle = 4ns. Per errata, we need to wait 200 cycles. Though executing code - * also takes time, so we'll wait 60 cycles (count = 20). - * - Since Low Speed mode is not supported/popular, we will ignore it for now. - * - * Note: this errata may also apply to G0, U5, H5 etc. - */ - volatile uint32_t cycle_count = 20; // defined as PCD_RX_PMA_CNT in stm32 hal_driver - while (cycle_count > 0U) { - cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare) - } + #if defined(TUP_USBIP_FSDEV_STM32) && defined(CFG_TUSB_FSDEV_32BIT) + fsdev_btable_workaround_delay(false); #endif if (ep_reg & U_EP_SETUP) { @@ -531,8 +514,8 @@ void edpt0_open(uint8_t rhport) { xfer_status[0][1].max_packet_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][1].ep_idx = 0; - uint16_t pma_addr0 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); - uint16_t pma_addr1 = dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); + uint16_t pma_addr0 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); + uint16_t pma_addr1 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); btable_set_addr(0, BTABLE_BUF_RX, pma_addr0); btable_set_addr(0, BTABLE_BUF_TX, pma_addr1); @@ -574,7 +557,7 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { } /* Create a packet memory buffer area. */ - uint16_t pma_addr = dcd_pma_alloc(packet_size, false); + uint16_t pma_addr = (uint16_t)dcd_pma_alloc(packet_size, false); btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); @@ -624,17 +607,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet #if CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP != 0 uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, true); - uint16_t pma_addr2 = pma_addr >> 16; + uint16_t pma_addr2 = (uint16_t)(pma_addr >> 16); #else uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, false); - uint16_t pma_addr2 = pma_addr; + uint16_t pma_addr2 = (uint16_t)pma_addr; #endif #if FSDEV_USE_SBUF_ISO == 0 - btable_set_addr(ep_idx, 0, pma_addr); + btable_set_addr(ep_idx, 0, (uint16_t)pma_addr); btable_set_addr(ep_idx, 1, pma_addr2); #else - btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); + btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, (uint16_t)pma_addr); (void)pma_addr2; #endif diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c index 003bcd069..7c4572a1e 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.c +++ b/src/portable/st/stm32_fsdev/fsdev_common.c @@ -81,11 +81,11 @@ uint16_t pma_align_buffer_size(uint16_t size, uint8_t* blsize, uint8_t* num_bloc if (size > 62) { block_in_bytes = 32; *blsize = 1; - *num_block = tu_div_ceil(size, 32); + *num_block = (uint8_t)tu_div_ceil(size, 32); } else { block_in_bytes = 2; *blsize = 0; - *num_block = tu_div_ceil(size, 2); + *num_block = (uint8_t)tu_div_ceil(size, 2); } return (*num_block) * block_in_bytes; diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h index 140ff1d61..af84b8b97 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.h +++ b/src/portable/st/stm32_fsdev/fsdev_common.h @@ -345,7 +345,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ep_write_clear_ctr(uint32_t ep_id, tusb uint32_t reg = FSDEV_REG->ep[ep_id].reg; reg |= U_EP_CTR_TX | U_EP_CTR_RX; reg &= U_EPREG_MASK; - reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0 : 8))); + reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0u : 8u))); ep_write(ep_id, reg, false); } @@ -378,7 +378,7 @@ TU_ATTR_ALWAYS_INLINE static inline void ch_write_clear_ctr(uint32_t ch_id, tusb uint32_t reg = FSDEV_REG->ep[ch_id].reg; reg |= U_EP_CTR_TX | U_EP_CTR_RX; reg &= U_EPREG_MASK; - reg &= ~(1 << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8 : 0))); + reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8u : 0u))); ep_write(ch_id, reg, false); } diff --git a/src/portable/st/stm32_fsdev/fsdev_stm32.h b/src/portable/st/stm32_fsdev/fsdev_stm32.h index a63592c5d..93cdac808 100644 --- a/src/portable/st/stm32_fsdev/fsdev_stm32.h +++ b/src/portable/st/stm32_fsdev/fsdev_stm32.h @@ -36,6 +36,10 @@ #include "stm32c0xx.h" #define FSDEV_HAS_SBUF_ISO 1 +#elif CFG_TUSB_MCU == OPT_MCU_STM32C5 + #include "stm32c5xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + #elif CFG_TUSB_MCU == OPT_MCU_STM32F0 #include "stm32f0xx.h" #define FSDEV_HAS_SBUF_ISO 0 @@ -164,20 +168,24 @@ #define FSDEV_USE_SBUF_ISO 0 #endif -//--------------------------------------------------------------------+ -// -//--------------------------------------------------------------------+ - +// STM32L1 calls it USB_FS_WKUP_IRQn; alias so the commented USBWakeUp_IRQn below +// can be uncommented as-is. #if TU_CHECK_MCU(OPT_MCU_STM32L1) && !defined(USBWakeUp_IRQn) #define USBWakeUp_IRQn USB_FS_WKUP_IRQn #endif +// USB interrupt vectors to enable in NVIC. The EXTI-line USB wakeup interrupt +// (USBWakeUp_IRQn, and USBWakeUp_RMP_IRQn on F3) is left commented out: resume is +// handled in-band via ISTR.WKUP in the USB_LP/HP ISR; the EXTI line is only needed to +// wake the core from STOP mode, which this driver does not implement (it never arms or +// clears that EXTI line, so enabling its NVIC vector can only spuriously fire/freeze). +// TODO: uncomment USBWakeUp_IRQn (+ arm/clear its EXTI line) when adding STOP-mode wakeup. static const IRQn_Type fsdev_irq[] = { #if TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32L0, OPT_MCU_STM32L4, OPT_MCU_STM32U5) USB_IRQn, #elif TU_CHECK_MCU(OPT_MCU_STM32L5, OPT_MCU_STM32U3) USB_FS_IRQn, - #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32H5, OPT_MCU_STM32U0) + #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32C5, OPT_MCU_STM32H5, OPT_MCU_STM32U0) USB_DRD_FS_IRQn, #elif CFG_TUSB_MCU == OPT_MCU_STM32G0 #ifdef STM32G0B0xx @@ -188,15 +196,15 @@ static const IRQn_Type fsdev_irq[] = { #elif CFG_TUSB_MCU == OPT_MCU_STM32F1 USB_HP_CAN1_TX_IRQn, USB_LP_CAN1_RX0_IRQn, - USBWakeUp_IRQn, + //USBWakeUp_IRQn, #elif CFG_TUSB_MCU == OPT_MCU_STM32F3 USB_HP_CAN_TX_IRQn, USB_LP_CAN_RX0_IRQn, - USBWakeUp_IRQn, + //USBWakeUp_IRQn, #elif TU_CHECK_MCU(OPT_MCU_STM32G4, OPT_MCU_STM32L1) USB_HP_IRQn, USB_LP_IRQn, - USBWakeUp_IRQn, + //USBWakeUp_IRQn, #elif CFG_TUSB_MCU == OPT_MCU_STM32WB USB_HP_IRQn, USB_LP_IRQn, @@ -219,7 +227,7 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) { if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) { NVIC_EnableIRQ(USB_HP_IRQn); NVIC_EnableIRQ(USB_LP_IRQn); - NVIC_EnableIRQ(USBWakeUp_RMP_IRQn); + //NVIC_EnableIRQ(USBWakeUp_RMP_IRQn); } else #endif { @@ -239,7 +247,7 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) { NVIC_DisableIRQ(USB_HP_IRQn); NVIC_DisableIRQ(USB_LP_IRQn); - NVIC_DisableIRQ(USBWakeUp_RMP_IRQn); + //NVIC_DisableIRQ(USBWakeUp_RMP_IRQn); } else #endif { @@ -252,6 +260,64 @@ TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { } //--------------------------------------------------------------------+ +// STM32 FSDEV PMA Buffer Description Table errata workaround +//--------------------------------------------------------------------+ + +#ifdef CFG_TUSB_FSDEV_32BIT +/* Errata: Buffer description table update completes after CTR interrupt triggers + * https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf + * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf + * + * CTR may trigger before final PMA SRAM accesses complete on OUT transfers. + * Insert delay before reading PMA count/data. + * Max CPU frequency in Hz, used to derive conservative FSDEV PMA delay defaults. + */ +#if CFG_TUSB_MCU == OPT_MCU_STM32H5 + #define FSDEV_STM32_CPU_HZ 250000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U5 + #define FSDEV_STM32_CPU_HZ 160000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U3 + #define FSDEV_STM32_CPU_HZ 96000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U0 + #define FSDEV_STM32_CPU_HZ 56000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32G0 + #define FSDEV_STM32_CPU_HZ 64000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32C0 + #define FSDEV_STM32_CPU_HZ 48000000U +#elif CFG_TUSB_MCU == OPT_MCU_STM32C5 + #define FSDEV_STM32_CPU_HZ 144000000U +#endif + +// 11 cycles / 800ns = ~13750000 cycles per second, used to derive conservative FSDEV PMA delay defaults +#ifndef CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT + #define CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 13750000U) +#endif + +// 11 cycles / 6.4us = ~1718750 cycles per second, used to derive conservative FSDEV PMA delay defaults +#ifndef CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT + #define CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT (FSDEV_STM32_CPU_HZ / 1718750U) +#endif + +/** + * LDR from SP-relative: 2 cycles + * SUBS: 1 cycle + * STR to SP-relative: 2 cycles + * LDR from SP-relative: 2 cycles + * CMP: 1 cycle + * BNE: + * taken: 3 cycles total (often shown as 1 + pipeline refill) + * not taken: 1 cycle + * Total cycles if delay is needed: 11 cycles + */ +TU_ATTR_ALWAYS_INLINE static inline void fsdev_btable_workaround_delay(bool low_speed) { + volatile uint32_t cycle_count = low_speed ? CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT : CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT; + while (cycle_count > 0U) { + cycle_count--; + } +} +#endif + +//--------------------------------------------------------------------+ // Connect / Disconnect //--------------------------------------------------------------------+ diff --git a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c index 18685dbdc..f9201651a 100644 --- a/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c @@ -58,20 +58,6 @@ TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index"); -#if CFG_TUSB_MCU == OPT_MCU_STM32H5 - #define CPU_FREQUENCY_MHZ 250U -#elif CFG_TUSB_MCU == OPT_MCU_STM32U5 - #define CPU_FREQUENCY_MHZ 160U -#elif CFG_TUSB_MCU == OPT_MCU_STM32U3 - #define CPU_FREQUENCY_MHZ 96U -#elif CFG_TUSB_MCU == OPT_MCU_STM32G0 - #define CPU_FREQUENCY_MHZ 64U -#elif CFG_TUSB_MCU == OPT_MCU_STM32C0 - #define CPU_FREQUENCY_MHZ 48U -#else - #error "CPU_FREQUENCY_MHZ not defined for this STM32 MCU" -#endif - enum { HCD_XFER_ERROR_MAX = 3, HCD_XFER_NAK_MAX = 15, @@ -165,35 +151,9 @@ static inline void channel_write_status(uint8_t ch_id, uint32_t ch_reg, tusb_dir } static inline uint16_t channel_get_rx_count(uint8_t ch_id) { - /* https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf - * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf - * From H503/U535 errata: Buffer description table update completes after CTR interrupt triggers - * Description: - * - During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM accesses - * have completed. If the software responds quickly to the interrupt, the full buffer contents may not be correct. - * Workaround: - * - Software should ensure that a small delay is included before accessing the SRAM contents. This delay - * should be 800 ns in Full Speed mode and 6.4 ฮผs in Low Speed mode - * - * Note: this errata may also apply to G0, U5, H5 etc. - * - * We choose the delay count based on max CPU frequency (in MHz) to ensure the delay is at least the required time. - */ - uint32_t ch_reg = ch_read(ch_id); - if (FSDEV_REG->ISTR & U_ISTR_LS_DCONN || ch_reg & U_EP_LSEP) { - // Low speed mode: 6.4 us delay -> about 2 cycles per MHz - volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ * 2U; - while (cycle_count > 0U) { - cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare) - } - } else { - // Full speed mode: 800 ns delay -> about 0.25 cycles per MHz - volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U; - while (cycle_count > 0U) { - cycle_count--; // each count take 3 cycles (1 for sub, jump, and compare) - } - } + const bool is_low_speed = (FSDEV_REG->ISTR & U_ISTR_LS_DCONN) || (ch_reg & U_EP_LSEP); + fsdev_btable_workaround_delay(is_low_speed); return btable_get_count(ch_id, BTABLE_BUF_RX); } @@ -237,11 +197,7 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { // If DCON_STAT is already set, the controller sometimes misses the initial connection interrupt if (FSDEV_REG->ISTR & U_ISTR_DCON_STAT) { - // Wait DP/DM stabilize time - volatile uint32_t cycle_count = CPU_FREQUENCY_MHZ / 4U; - while (cycle_count > 0U) { - cycle_count--; - } + tusb_time_delay_ms_api(2); port_status_handler(rhport, false); } diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 9a9c734a0..e1a2f6cf2 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -73,8 +73,15 @@ typedef struct { static dcd_data_t _dcd_data; +// DMA receives up to 3 back-to-back SETUP packets (3 x 8 bytes), Slave mode only needs 1 packet (8 bytes) +#if CFG_TUD_DWC2_DMA_ENABLE + #define DWC2_SETUP_BUFFER_SIZE 24 +#else + #define DWC2_SETUP_BUFFER_SIZE 8 +#endif + CFG_TUD_MEM_SECTION static struct { - TUD_EPBUF_DEF(setup_packet, 8); + TUD_EPBUF_DEF(setup_buffer, DWC2_SETUP_BUFFER_SIZE); } _dcd_usbbuf; static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; @@ -136,9 +143,9 @@ static void dma_setup_prepare(uint8_t rhport) { } } - // Receive only 1 packet - dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos); - dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet; + // Receive back-to-back setup packets + dwc2->epout[0].doeptsiz = (3 << DOEPTSIZ_STUPCNT_Pos); + dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_buffer; dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP; } @@ -191,7 +198,7 @@ static void dma_setup_prepare(uint8_t rhport) { */ TU_ATTR_ALWAYS_INLINE static inline uint16_t calc_device_grxfsiz(uint16_t largest_ep_size, uint8_t ep_count) { - return 13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count; + return (uint16_t)(13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count); } static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, bool is_bulk) { @@ -203,7 +210,7 @@ static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, b TU_ASSERT(epnum < ep_count); - uint16_t fifo_size = tu_div_ceil(packet_size, 4); + uint16_t fifo_size = (uint16_t)tu_div_ceil(packet_size, 4); if (dir == TUSB_DIR_OUT) { // Calculate required size of RX FIFO const uint16_t new_sz = calc_device_grxfsiz(4 * fifo_size, ep_count); @@ -371,7 +378,7 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin num_packets = 1; } else { total_bytes = xfer->total_len; - num_packets = tu_div_ceil(total_bytes, xfer->max_size); + num_packets = (uint16_t)tu_div_ceil(total_bytes, xfer->max_size); if (num_packets == 0) { num_packets = 1; // zero length packet still count as 1 } @@ -541,8 +548,9 @@ void dcd_remote_wakeup(uint8_t rhport) { void dcd_connect(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); -#ifdef TUP_USBIP_DWC2_ESP32 - // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY +#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31) + // S31 is excluded at compile time (no USB_WRAP peripheral). + // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY. if (rhport == 0) { usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; conf.pad_pull_override = 0; @@ -560,8 +568,9 @@ void dcd_connect(uint8_t rhport) { void dcd_disconnect(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); -#ifdef TUP_USBIP_DWC2_ESP32 - // On ESP32-P4 HS PHY, do not write to USB_WRAP register which belongs to FS PHY +#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31) + // S31 is excluded at compile time (no USB_WRAP peripheral). + // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY. if (rhport == 0) { usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; conf.pad_pull_override = 1; @@ -791,13 +800,15 @@ static void handle_bus_reset(uint8_t rhport) { xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + uint32_t gintmsk = GINTMSK_OTGINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; if(dma_device_enabled(dwc2)) { + gintmsk |= GINTMSK_OEPINT; dma_setup_prepare(rhport); } else { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= gintmsk; } static void handle_enum_done(uint8_t rhport) { @@ -881,31 +892,47 @@ static void handle_rxflvl_irq(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const volatile uint32_t* rx_fifo = dwc2->fifo[0]; + // DWC2 v3.10a (e.g. STM32L476) emits an extra EP0 RX_COMPLETE that is NOT a real OUT data transfer completion, in two + // situations - each flagged by a DOEPINT bit set on that word: + // - DOEPINT.STPKTRX (Setup Packet Received): pushed between SETUP_RX and SETUP_DONE of every control transfer. + // - DOEPINT.STSPHSRX (Status Phase Received for control write): pushed after the OUT data stage when the host + // starts the IN status phase. + // Both are dropped in the RX_COMPLETE case below, clearing the flag (W1C) so a latched STSPHSRX + // does not block the core from emitting the next SETUP_DONE. usbd still processes the real OUT data + // and queues the IN status ZLP itself - the core does not auto-complete the control-write status. + const bool quirk_v310a = (dwc2->gsnpsid == DWC2_CORE_REV_3_10a); + // Pop control word off FIFO const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp}; + const uint8_t packet_status = grxstsp.packet_status; const uint8_t epnum = grxstsp.ep_ch_num; dwc2_dep_t* epout = &dwc2->epout[epnum]; - switch (grxstsp.packet_status) { + switch (packet_status) { case GRXSTS_PKTSTS_GLOBAL_OUT_NAK: // Global OUT NAK: do nothing break; case GRXSTS_PKTSTS_SETUP_RX: { // Setup packet received - uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet; + uint32_t * setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); break; } - case GRXSTS_PKTSTS_SETUP_DONE: - // Setup packet done: - // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq() + case GRXSTS_PKTSTS_SETUP_DONE: { + // Pop this word causes the Setup interrupt epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + epout->doepint = DOEPINT_SETUP | DOEPINT_STPKTRX; // Clear SETUP interrupt, required for core to re-write this control word + if (edpt_is_enabled(&dwc2->epin[0])) { + edpt_disable(rhport, 0x80, false); + } + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); break; + } case GRXSTS_PKTSTS_RX_DATA: { // Out packet received @@ -933,41 +960,31 @@ static void handle_rxflvl_irq(uint8_t rhport) { break; } - case GRXSTS_PKTSTS_RX_COMPLETE: - // Out packet done - // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on - // the specified OUT endpoint which will be handled by handle_epout_irq() - break; + case GRXSTS_PKTSTS_RX_COMPLETE: { + // Pop this word causes the xfer complete interrupt + const uint32_t doepint = epout->doepint; + epout->doepint = DOEPINT_XFRC; - default: break; // nothing to do - } -} - -static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { - if (doepint_bm.setup_phase_done) { - // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; - if (edpt_is_enabled(epin0)) { - edpt_disable(rhport, 0x80, false); - } - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); - return; - } + // v3.10a quirk (see top of function): the extra RX_COMPLETE flagged with Setup Packet Received (STPKTRX) or + // Status Phase Received for control write (STSPHSRX) is not a real OUT completion. Drop it + if (quirk_v310a) { + if (doepint & (DOEPINT_STPKTRX | DOEPINT_STSPHSRX)) { + epout->doepint = DOEPINT_STPKTRX | DOEPINT_STSPHSRX; + break; + } + } - // Normal OUT transfer complete - if (doepint_bm.xfer_complete) { - // only handle data skip if it is setup or status related - // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they - // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done - if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); - if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { - // EP0 can only handle one packet, Schedule another packet to be received. - edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); + if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) { + // EP0 can only handle one packet, schedule another packet to be received. + edpt_schedule_packets(rhport, 0, TUSB_DIR_OUT); } else { dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } + break; } + + default: break; // nothing to do } } @@ -1006,13 +1023,23 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi if (doepint_bm.setup_phase_done) { // Cleanup previous pending EP0 IN transfer if any - dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; + dwc2_dep_t* epin0 = &dwc2->epin[0]; + dwc2_dep_t* epout0 = &dwc2->epout[0]; if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } - dma_setup_prepare(rhport); - dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + + dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, sizeof(_dcd_usbbuf.setup_buffer)); + + // DOEPDMA0 has advanced past the last received SETUP packet; back up one packet to the latest valid one + // (Programming Guide v4.20a section 9.1.2.1: "DOEPDMAn-8 provides the pointer to the last valid SETUP data") + tusb_control_request_t *setup_packet = (tusb_control_request_t *) (uintptr_t) (epout0->doepdma - sizeof(tusb_control_request_t)); + dcd_event_setup_received(rhport, (uint8_t*)setup_packet, true); + + // Prepare EP0 for next setup if this setup has no data stage + if (setup_packet->wLength == 0) { + dma_setup_prepare(rhport); + } return; } @@ -1033,9 +1060,8 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; - // this is ZLP, so prepare EP0 for next setup - // TODO use status phase rx - if(epnum == 0 && xfer->total_len == 0) { + // prepare EP0 for next setup + if(epnum == 0) { dma_setup_prepare(rhport); } @@ -1054,9 +1080,6 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin // EP0 can only handle one packet. Schedule another packet to be transmitted. edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); } else { - if(epnum == 0) { - dma_setup_prepare(rhport); - } dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1097,7 +1120,7 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { if (dir == TUSB_DIR_IN) { handle_epin_slave(rhport, epnum, intr.diepint_bm); } else { - handle_epout_slave(rhport, epnum, intr.doepint_bm); + // epout is handled in handle_rxflvl_irq } #endif } @@ -1134,8 +1157,9 @@ static void handle_incomplete_iso_in(uint8_t rhport) { } epin->diepctl = depctl.value; } else { - // too many retries, give up + // too many retries, give up, but keep endpoint activated edpt_disable(rhport, epnum | TUSB_DIR_IN_MASK, false); + epin->diepctl |= DIEPCTL_USBAEP; dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true); } } @@ -1204,7 +1228,7 @@ void dcd_int_handler(uint8_t rhport) { dwc2->gotgint = otg_int; } - if(gintsts & GINTSTS_SOF) { + if(gintsts & GINTSTS_SOF && dwc2->gintmsk & GINTMSK_SOFM) { dwc2->gintsts = GINTSTS_SOF; dwc2->gintmsk |= GINTMSK_USBSUSPM; const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; @@ -1217,6 +1241,12 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_sof(rhport, frame, true); } + // IN endpoint interrupt handling. + if (gintsts & GINTSTS_IEPINT) { + // IEPINT bit read-only, clear using DIEPINTn + handle_ep_irq(rhport, TUSB_DIR_IN); + } + #if CFG_TUD_DWC2_SLAVE_ENABLE // RxFIFO non-empty interrupt handling. if (gintsts & GINTSTS_RXFLVL) { @@ -1231,17 +1261,13 @@ void dcd_int_handler(uint8_t rhport) { } #endif +#if CFG_TUD_DWC2_DMA_ENABLE // OUT endpoint interrupt handling. if (gintsts & GINTSTS_OEPINT) { // OEPINT is read-only, clear using DOEPINTn handle_ep_irq(rhport, TUSB_DIR_OUT); } - - // IN endpoint interrupt handling. - if (gintsts & GINTSTS_IEPINT) { - // IEPINT bit read-only, clear using DIEPINTn - handle_ep_irq(rhport, TUSB_DIR_IN); - } +#endif // Incomplete isochronous IN transfer interrupt handling. if (gintsts & GINTSTS_IISOIXFR) { diff --git a/src/portable/synopsys/dwc2/dwc2_esp32.h b/src/portable/synopsys/dwc2/dwc2_esp32.h index 6a10dc7f8..436f8dc30 100644 --- a/src/portable/synopsys/dwc2/dwc2_esp32.h +++ b/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -37,7 +37,11 @@ #include "esp_intr_alloc.h" #include "soc/periph_defs.h" + +// ESP32-S31 does not have USB_WRAP peripheral (HS-only with UTMI PHY) +#if !TU_CHECK_MCU(OPT_MCU_ESP32S31) #include "soc/usb_wrap_struct.h" +#endif #if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) #define DWC2_FS_REG_BASE 0x60080000UL @@ -75,6 +79,14 @@ static const dwc2_controller_t _dwc2_controller[] = { { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }, { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } }; + +#elif TU_CHECK_MCU(OPT_MCU_ESP32S31) +#define DWC2_HS_REG_BASE 0x20300000UL +#define DWC2_EP_MAX 16 + +static const dwc2_controller_t _dwc2_controller[] = { + { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTGHS_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } +}; #endif //--------------------------------------------------------------------+ diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index 6098d6eaa..84a0c6afd 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -104,6 +104,7 @@ typedef struct { uint16_t xferred_bytes; // bytes that accumulate transferred though USB bus for the whole hcd_edpt_xfer(), which can // be composed of multiple channel_xfer_start() (retry with NAK/NYET) uint16_t fifo_bytes; // bytes written/read from/to FIFO (may not be transferred on USB bus). + uint8_t retry_disabled; // 1: channel was disabled to throttle a split retry (NAK in / XactErr out); re-arm on its halt } hcd_xfer_t; typedef struct { @@ -903,9 +904,6 @@ static void handle_rxflvl_irq(uint8_t rhport) { // return true if there is still pending data and need more ISR static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { - // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) - const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; - const uint8_t max_channel = dwc2_channel_count(dwc2); for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { dwc2_channel_t* channel = &dwc2->channel[ch_id]; @@ -923,6 +921,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { // skip if there is not enough space in FIFO and RequestQueue. // Packet's last word written to FIFO will trigger a request queue + // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) + const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) { return true; } @@ -1138,7 +1138,16 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci // TU_LOG1("in hcint = %02lX\r\n", hcint); if (hcint & HCINT_HALTED) { - if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { + if (xfer->retry_disabled) { + // Halt from our split-NAK throttle disable (below): re-arm the start-split, or let teardown finish + // if the endpoint is closing. Programming Guide 3.5 "Halting a Channel" (p73). + xfer->retry_disabled = 0; + if (xfer->closing) { + is_done = true; + } else { + channel_send_in_token(dwc2, channel); + } + } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { const uint16_t remain_bytes = (uint16_t) hctsiz.xfer_size; const uint16_t remain_packets = hctsiz.packet_count; const uint16_t actual_len = edpt->buflen - remain_bytes; @@ -1204,7 +1213,15 @@ static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hci channel->hcintmsk &= ~(HCINT_NAK | HCINT_DATATOGGLE_ERR); hcsplt.split_compl = 0; // restart with start-split channel->hcsplt = hcsplt.value; - channel_xfer_in_retry(dwc2, ch_id, hcint); + // Persistent split bulk/control IN NAK (e.g. idle polled endpoint): re-enabling immediately storms + // the ISR and starves the task. Disable + re-arm on the resulting halt to throttle (like the slave + // path); no frame deferral. Programming Guide 3.5 (p73) Note permits disable on NAK/FrmOvrn splits. + if ((hcint & HCINT_NAK) && hcsplt.split_en && !channel_is_periodic(channel->hcchar)) { + xfer->retry_disabled = 1; + channel_disable(dwc2, channel); + } else { + channel_xfer_in_retry(dwc2, ch_id, hcint); + } } else if (hcint & HCINT_FARME_OVERRUN) { // retry start-split in next binterval channel_xfer_in_retry(dwc2, ch_id, hcint); @@ -1229,7 +1246,16 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc // TU_LOG1("out hcint = %02lX\r\n", hcint); if (hcint & HCINT_HALTED) { - if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) { + if (xfer->retry_disabled) { + // Halt from our split-XactErr throttle disable (below): re-issue the start-split (pointers already + // rewound), giving the hub TT a recovery gap. Programming Guide 3.5 "Halting a Channel" (p73). + xfer->retry_disabled = 0; + if (xfer->closing) { + is_done = true; + } else { + channel_xfer_start(dwc2, ch_id); + } + } else if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) { is_done = true; xfer->err_count = 0; if (hcint & HCINT_XFER_COMPLETE) { @@ -1252,9 +1278,17 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc xfer->result = XFER_RESULT_FAILED; is_done = true; } else { - // clean up transfer so far and start again + // Rewind, then retry the start-split. Non-periodic SPLIT throttles via channel_disable + re-arm on + // the halt (immediate re-fire exhausts the retry budget; the disable gives the hub TT a recovery + // gap, like slave). Periodic split is excluded: channel_disable() is a no-op for it, so the halt + // never fires and the channel would wedge. Non-split re-inits immediately (Programming Guide 5.1.2.3). channel_xfer_out_wrapup(dwc2, ch_id); - channel_xfer_start(dwc2, ch_id); + if (hcsplt.split_en && !channel_is_periodic(channel->hcchar)) { + xfer->retry_disabled = 1; + channel_disable(dwc2, channel); + } else { + channel_xfer_start(dwc2, ch_id); + } } } } else if (hcint & HCINT_NYET) { @@ -1272,6 +1306,12 @@ static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hc channel->hcsplt = hcsplt.value; channel->hcchar |= HCCHAR_CHENA; } + } else if ((hcint & HCINT_NAK) && hcsplt.split_en) { + // Split OUT NAK: rewind + retry the start-split, else the channel stalls (Programming Guide 5.1.4.2). + // Non-split OUT NAK is core-handled (5.1.2.2), so this is split-only. + xfer->err_count = 0; + channel_xfer_out_wrapup(dwc2, ch_id); + channel_xfer_start(dwc2, ch_id); } if (xfer->closing == 1) { diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h index 68be64f5e..415a015dc 100644 --- a/src/portable/wch/ch32_usbfs_reg.h +++ b/src/portable/wch/ch32_usbfs_reg.h @@ -39,64 +39,163 @@ #include <ch32f20x.h> #elif CFG_TUSB_MCU == OPT_MCU_CH32V103 #include <ch32v10x.h> + // Newer-IP layout (separate UEPn_TX_CTRL/UEPn_RX_CTRL). The older IP (CH32V103) has a single + // combined control register at the UEPn_TX_CTRL offset, with UEPn_RX_CTRL reserved; the union + // exposes that same byte as UEPn_CTRL. Offsets are byte offsets from the peripheral base. + // TODO unify into a single struct shared by all WCH USBFS parts. typedef struct { - __IO uint8_t BASE_CTRL; - __IO uint8_t UDEV_CTRL; - __IO uint8_t INT_EN; - __IO uint8_t DEV_ADDR; - __IO uint8_t Reserve0; - __IO uint8_t MIS_ST; - __IO uint8_t INT_FG; - __IO uint8_t INT_ST; - __IO uint32_t RX_LEN; - __IO uint8_t UEP4_1_MOD; - __IO uint8_t UEP2_3_MOD; - __IO uint8_t UEP5_6_MOD; - __IO uint8_t UEP7_MOD; - __IO uint32_t UEP0_DMA; - __IO uint32_t UEP1_DMA; - __IO uint32_t UEP2_DMA; - __IO uint32_t UEP3_DMA; - __IO uint32_t UEP4_DMA; - __IO uint32_t UEP5_DMA; - __IO uint32_t UEP6_DMA; - __IO uint32_t UEP7_DMA; - __IO uint16_t UEP0_TX_LEN; - __IO uint8_t UEP0_TX_CTRL; - __IO uint8_t UEP0_RX_CTRL; - __IO uint16_t UEP1_TX_LEN; - __IO uint8_t UEP1_TX_CTRL; - __IO uint8_t UEP1_RX_CTRL; - __IO uint16_t UEP2_TX_LEN; - __IO uint8_t UEP2_TX_CTRL; - __IO uint8_t UEP2_RX_CTRL; - __IO uint16_t UEP3_TX_LEN; - __IO uint8_t UEP3_TX_CTRL; - __IO uint8_t UEP3_RX_CTRL; - __IO uint16_t UEP4_TX_LEN; - __IO uint8_t UEP4_TX_CTRL; - __IO uint8_t UEP4_RX_CTRL; - __IO uint16_t UEP5_TX_LEN; - __IO uint8_t UEP5_TX_CTRL; - __IO uint8_t UEP5_RX_CTRL; - __IO uint16_t UEP6_TX_LEN; - __IO uint8_t UEP6_TX_CTRL; - __IO uint8_t UEP6_RX_CTRL; - __IO uint16_t UEP7_TX_LEN; - __IO uint8_t UEP7_TX_CTRL; - __IO uint8_t UEP7_RX_CTRL; - __IO uint32_t Reserve1; - __IO uint32_t OTG_CR; - __IO uint32_t OTG_SR; + __IO uint8_t BASE_CTRL; // 0x00 + __IO uint8_t UDEV_CTRL; // 0x01 + __IO uint8_t INT_EN; // 0x02 + __IO uint8_t DEV_ADDR; // 0x03 + __IO uint8_t Reserve0; // 0x04 + __IO uint8_t MIS_ST; // 0x05 + __IO uint8_t INT_FG; // 0x06 + __IO uint8_t INT_ST; // 0x07 + __IO uint32_t RX_LEN; // 0x08 + __IO uint8_t UEP4_1_MOD; // 0x0C + __IO uint8_t UEP2_3_MOD; // 0x0D + __IO uint8_t UEP5_6_MOD; // 0x0E + __IO uint8_t UEP7_MOD; // 0x0F + __IO uint32_t UEP0_DMA; // 0x10 + __IO uint32_t UEP1_DMA; // 0x14 + __IO uint32_t UEP2_DMA; // 0x18 + __IO uint32_t UEP3_DMA; // 0x1C + __IO uint32_t UEP4_DMA; // 0x20 + __IO uint32_t UEP5_DMA; // 0x24 + __IO uint32_t UEP6_DMA; // 0x28 + __IO uint32_t UEP7_DMA; // 0x2C + __IO uint16_t UEP0_TX_LEN; // 0x30 + union { + __IO uint8_t UEP0_TX_CTRL; + __IO uint8_t UEP0_CTRL; + }; // 0x32 (TX_CTRL: IN | CTRL: combined) + __IO uint8_t UEP0_RX_CTRL; // 0x33 (OUT ctrl; reserved on combined IP) + __IO uint16_t UEP1_TX_LEN; // 0x34 + union { + __IO uint8_t UEP1_TX_CTRL; + __IO uint8_t UEP1_CTRL; + }; // 0x36 + __IO uint8_t UEP1_RX_CTRL; // 0x37 + __IO uint16_t UEP2_TX_LEN; // 0x38 + union { + __IO uint8_t UEP2_TX_CTRL; + __IO uint8_t UEP2_CTRL; + }; // 0x3A + __IO uint8_t UEP2_RX_CTRL; // 0x3B + __IO uint16_t UEP3_TX_LEN; // 0x3C + union { + __IO uint8_t UEP3_TX_CTRL; + __IO uint8_t UEP3_CTRL; + }; // 0x3E + __IO uint8_t UEP3_RX_CTRL; // 0x3F + __IO uint16_t UEP4_TX_LEN; // 0x40 + union { + __IO uint8_t UEP4_TX_CTRL; + __IO uint8_t UEP4_CTRL; + }; // 0x42 + __IO uint8_t UEP4_RX_CTRL; // 0x43 + __IO uint16_t UEP5_TX_LEN; // 0x44 + union { + __IO uint8_t UEP5_TX_CTRL; + __IO uint8_t UEP5_CTRL; + }; // 0x46 + __IO uint8_t UEP5_RX_CTRL; // 0x47 + __IO uint16_t UEP6_TX_LEN; // 0x48 + union { + __IO uint8_t UEP6_TX_CTRL; + __IO uint8_t UEP6_CTRL; + }; // 0x4A + __IO uint8_t UEP6_RX_CTRL; // 0x4B + __IO uint16_t UEP7_TX_LEN; // 0x4C + union { + __IO uint8_t UEP7_TX_CTRL; + __IO uint8_t UEP7_CTRL; + }; // 0x4E + __IO uint8_t UEP7_RX_CTRL; // 0x4F + __IO uint32_t Reserve1; // 0x50 + __IO uint32_t OTG_CR; // 0x54 + __IO uint32_t OTG_SR; // 0x58 } USBOTG_FS_TypeDef; #define USBOTG_FS ((USBOTG_FS_TypeDef *) 0x40023400) + + // CH32V103 has the older USBFS IP: a single combined control register per endpoint + // (UEPn_CTRL) instead of separate TX_CTRL/RX_CTRL bytes. The struct's UEPn_TX_CTRL field + // aliases that combined register (same address); UEPn_RX_CTRL maps to unused padding. + #define CH32_USBFS_EP_CTRL_COMBINED 1 #elif CFG_TUSB_MCU == OPT_MCU_CH32V20X #include <ch32v20x.h> #elif CFG_TUSB_MCU == OPT_MCU_CH32V307 #include <ch32v30x.h> #define USBHD_IRQn OTG_FS_IRQn +#elif CFG_TUSB_MCU == OPT_MCU_CH583 + #include "CH58x_common.h" + // CH582/583 USBFS device controller: same combined per-endpoint control register as + // CH32V103 (IN response bits[1:0], OUT response bits[3:2]) but a different register map - + // the EP control/length block sits lower (EP0_CTRL @ +0x22), EP5-7 are split out, EP4 + // shares EP0's DMA buffer, and EP5/6/7 mode bits live in one UEP567_MOD. The control/status + // block matches CH32. Two FS controllers exist (USB @ 0x40008000, USB2 @ 0x40008400); the + // device uses USB0. Full register map per CH583/582 datasheet Table 17-2; the parameterized + // EP_* macros below index off these named fields. + #define CH58X_USBFS_BASE 0x40008000u + // Per-endpoint register slots, 4-byte stride each; the EP_* macros index arrays of these. + typedef struct { + __IO uint16_t DMA; // R16_UEPn_DMA: endpoint n buffer start address + __IO uint16_t reserved; + } ch58x_ep_dma_t; + typedef struct { + __IO uint8_t T_LEN; // R8_UEPn_T_LEN (+0): transmit length + __IO uint8_t reserved0; + __IO uint8_t CTRL; // R8_UEPn_CTRL (+2): endpoint control + __IO uint8_t reserved1; + } ch58x_ep_ctrl_t; + typedef struct { + __IO uint8_t BASE_CTRL; // 0x00 R8_USB_CTRL + __IO uint8_t UDEV_CTRL; // 0x01 R8_UDEV_CTRL + __IO uint8_t INT_EN; // 0x02 R8_USB_INT_EN + __IO uint8_t DEV_ADDR; // 0x03 R8_USB_DEV_AD + __IO uint8_t Reserve0; // 0x04 + __IO uint8_t MIS_ST; // 0x05 R8_USB_MIS_ST + __IO uint8_t INT_FG; // 0x06 R8_USB_INT_FG + __IO uint8_t INT_ST; // 0x07 R8_USB_INT_ST + __IO uint8_t RX_LEN; // 0x08 R8_USB_RX_LEN (8-bit on CH58X) + __IO uint8_t Reserve1[3]; // 0x09..0x0B + __IO uint8_t UEP4_1_MOD; // 0x0C R8_UEP4_1_MOD + __IO uint8_t UEP2_3_MOD; // 0x0D R8_UEP2_3_MOD + __IO uint8_t UEP567_MOD; // 0x0E R8_UEP567_MOD + __IO uint8_t Reserve2; // 0x0F + ch58x_ep_dma_t EP_DMA_0_3[4]; // 0x10 EP0-3 DMA (EP4 has no DMA reg; it shares EP0's, index 0) + ch58x_ep_ctrl_t EP_CTRL_0_4[5]; // 0x20 EP0-4 length/control + __IO uint8_t Reserve3[0x54u - 0x34u]; // 0x34..0x53 + ch58x_ep_dma_t EP_DMA_5_7[3]; // 0x54 EP5-7 DMA + __IO uint8_t Reserve4[0x64u - 0x60u]; // 0x60..0x63 + ch58x_ep_ctrl_t EP_CTRL_5_7[3]; // 0x64 EP5-7 length/control + } USBOTG_FS_TypeDef; + #define USBOTG_FS ((USBOTG_FS_TypeDef *) CH58X_USBFS_BASE) + + // 4-byte slot stride + these block offsets pin every EP register to its datasheet address. + TU_VERIFY_STATIC(sizeof(ch58x_ep_dma_t) == 4, "CH58x EP DMA slot must be 4 bytes"); + TU_VERIFY_STATIC(sizeof(ch58x_ep_ctrl_t) == 4, "CH58x EP ctrl slot must be 4 bytes"); + TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_0_3) == 0x10, "CH58x EP_DMA_0_3 @0x10"); + TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_0_4) == 0x20, "CH58x EP_CTRL_0_4 @0x20"); + TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_DMA_5_7) == 0x54, "CH58x EP_DMA_5_7 @0x54"); + TU_VERIFY_STATIC(offsetof(USBOTG_FS_TypeDef, EP_CTRL_5_7) == 0x64, "CH58x EP_CTRL_5_7 @0x64"); + + #define CH32_USBFS_EP_CTRL_COMBINED 1 + // CH58x's hardware AUTO_TOG does not stay in sync (notably across clear-stall and multi-packet + // bulk transfers), causing data-toggle mismatch and bus resets. Drive the toggle manually in + // the ISR instead. CH32V103/V20x/V307 keep AUTO_TOG (this macro is undefined for them). + #define CH32_USBFS_EP_MANUAL_TOG 1 + // CH58x EP4 has no DMA register of its own: it overlays EP0's DMA region as + // EP0[0:63] + EP4_OUT[64:127] + EP4_IN[128:191], so EP0 needs a 192-byte buffer. + #define CH32_USBFS_EP4_SHARES_EP0 1 + #define USBHD_IRQn USB_IRQn + #ifndef NVIC_EnableIRQ + #define NVIC_EnableIRQ(n) PFIC_EnableIRQ(n) + #define NVIC_DisableIRQ(n) PFIC_DisableIRQ(n) + #endif #endif #ifdef __GNUC__ @@ -134,9 +233,14 @@ #define USBFS_INT_FG_TOG_OK (1 << 6) #define USBFS_INT_FG_IS_NAK (1 << 7) +// MIS_ST: the SUSPEND interrupt fires on both suspend and resume; this bit (R8_USB_MIS_ST) is 1 +// while the bus is suspended and 0 once it has resumed, so it tells the two apart. +#define USBFS_MIS_ST_SUSPEND (1 << 2) + // INT_ST #define USBFS_INT_ST_MASK_UIS_ENDP(x) (((x) >> 0) & 0x0F) #define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03) +#define USBFS_INT_ST_TOG_OK (1 << 6) // received packet's data toggle matched expectation // UDEV_CTRL #define USBFS_UDEV_CTRL_PORT_EN (1 << 0) @@ -166,6 +270,17 @@ #define USBFS_EP_R_RES_NAK (2 << 0) #define USBFS_EP_R_RES_STALL (3 << 0) +#ifdef CH32_USBFS_EP_CTRL_COMBINED +// Combined per-endpoint control register (older IP, e.g. CH32V103): IN response in +// bits [1:0], OUT response in bits [3:2], shared auto-toggle, separate IN/OUT toggle. +#define USBFS_EPC_T_RES_MASK 0x03 +#define USBFS_EPC_R_RES_MASK 0x0C +#define USBFS_EPC_R_RES_SHIFT 2 +#define USBFS_EPC_AUTO_TOG 0x10 +#define USBFS_EPC_T_TOG 0x40 +#define USBFS_EPC_R_TOG 0x80 +#endif + // token PID #define PID_OUT 0 #define PID_SOF 1 diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index 5cd25e33e..ece9cde07 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -29,84 +29,237 @@ #if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS -#include "device/dcd.h" -#include "ch32_usbfs_reg.h" + #include "device/dcd.h" + #include "ch32_usbfs_reg.h" -/* private defines */ -#define EP_MAX (8) + /* private defines */ + #define EP_MAX (8) -#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep]) -#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep]) -#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) -#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) + // Struct-based EP register access (uniform layout). CH58X has a different register map and + // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h. + #if CFG_TUSB_MCU == OPT_MCU_CH583 + // CH58X EP registers split into a low block (EP0-4) and a high block (EP5-7). Walk from each + // block's first slot by the 4-byte slot stride (pointer arithmetic off slot 0, so the unused + // ternary branch's index can't trip -Warray-bounds). EP4 has no DMA register of its own (it + // shares EP0's, slot 0) and is never written (see ep_shares_ep0_dma()). + #define EP_TX_LEN(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].T_LEN + (ep) * 4u \ + : &USBOTG_FS->EP_CTRL_5_7[0].T_LEN + ((ep) - 5u) * 4u)) + #define EP_CTRL(ep) (*((ep) <= 4u ? &USBOTG_FS->EP_CTRL_0_4[0].CTRL + (ep) * 4u \ + : &USBOTG_FS->EP_CTRL_5_7[0].CTRL + ((ep) - 5u) * 4u)) + #define EP_DMA(ep) (*((ep) <= 3u ? &USBOTG_FS->EP_DMA_0_3[0].DMA + (ep) * 2u \ + : (ep) == 4u ? &USBOTG_FS->EP_DMA_0_3[0].DMA \ + : &USBOTG_FS->EP_DMA_5_7[0].DMA + ((ep) - 5u) * 2u)) + #else + #define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep]) + #define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep]) + #define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) + #define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) + #endif + +// Endpoint control register access. The newer USBFS IP (CH32V20x/V307/X035) has separate +// TX_CTRL and RX_CTRL bytes per endpoint; the older IP (CH32V103) has a single combined +// UEPn_CTRL register. These helpers hide the difference so the rest of the driver is shared. +// Values use the newer-IP encoding (USBFS_EP_T_*/USBFS_EP_R_*); the combined path remaps them. +#ifdef CH32_USBFS_EP_CTRL_COMBINED + #ifndef EP_CTRL // parts with a custom register map (CH58X) define EP_CTRL directly in reg.h + #define EP_CTRL(ep) EP_TX_CTRL(ep) // UEPn_TX_CTRL field aliases the combined UEPn_CTRL register + #endif + + static inline uint8_t ep_tx_to_comb(uint8_t v) { + uint8_t c = v & USBFS_EP_T_RES_MASK; // IN response: bits [1:0] in both encodings + if (v & USBFS_EP_T_TOG) { c |= USBFS_EPC_T_TOG; } + if (v & USBFS_EP_T_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; } + return c; + } + static inline uint8_t ep_rx_to_comb(uint8_t v) { + uint8_t c = (uint8_t) ((v & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT); // OUT response -> bits [3:2] + if (v & USBFS_EP_R_TOG) { c |= USBFS_EPC_R_TOG; } + if (v & USBFS_EP_R_AUTO_TOG) { c |= USBFS_EPC_AUTO_TOG; } + return c; + } + // Set IN side (response/toggle/auto-tog), preserving the OUT response + OUT toggle. + static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_R_RES_MASK | USBFS_EPC_R_TOG)) | ep_tx_to_comb(v)); + } + // Set OUT side, preserving the IN response + IN toggle. + static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & (USBFS_EPC_T_RES_MASK | USBFS_EPC_T_TOG)) | ep_rx_to_comb(v)); + } + static inline void ep_tx_set_response(uint8_t ep, uint8_t res) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_T_RES_MASK) | (res & USBFS_EP_T_RES_MASK)); + } + static inline void ep_rx_set_response(uint8_t ep, uint8_t res) { + EP_CTRL(ep) = (uint8_t) ((EP_CTRL(ep) & ~USBFS_EPC_R_RES_MASK) | ((res & USBFS_EP_R_RES_MASK) << USBFS_EPC_R_RES_SHIFT)); + } + #define EP0_SETUP_RX_TOG USBFS_EP_R_TOG // combined IP: data/status stage after SETUP is DATA1 +#else + static inline void ep_tx_ctrl_set(uint8_t ep, uint8_t v) { EP_TX_CTRL(ep) = v; } + static inline void ep_rx_ctrl_set(uint8_t ep, uint8_t v) { EP_RX_CTRL(ep) = v; } + static inline void ep_tx_set_response(uint8_t ep, uint8_t res) { + EP_TX_CTRL(ep) = (uint8_t) ((EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | res); + } + static inline void ep_rx_set_response(uint8_t ep, uint8_t res) { + EP_RX_CTRL(ep) = (uint8_t) ((EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | res); + } + #define EP0_SETUP_RX_TOG 0 +#endif + +// Hardware auto data-toggle flag. Parts whose AUTO_TOG is reliable OR it into the EP setup so the +// controller flips DATA0/DATA1 itself; CH58x (CH32_USBFS_EP_MANUAL_TOG) leaves it clear and the +// ISR flips the toggle bit after each packet instead. +#ifdef CH32_USBFS_EP_MANUAL_TOG + #define EP_T_AUTO_TOG 0 + #define EP_R_AUTO_TOG 0 +#else + #define EP_T_AUTO_TOG USBFS_EP_T_AUTO_TOG + #define EP_R_AUTO_TOG USBFS_EP_R_AUTO_TOG +#endif /* private data */ struct usb_xfer { - bool valid; - uint8_t* buffer; - size_t len; - size_t processed_len; - size_t max_size; + bool valid; + uint8_t *buffer; + size_t len; + size_t processed_len; + size_t max_size; }; static struct { - bool ep0_tog; - bool isochronous[EP_MAX]; + bool ep0_tog; + bool isochronous[EP_MAX]; struct usb_xfer xfer[EP_MAX][2]; +#ifdef CH32_USBFS_EP4_SHARES_EP0 + // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4 + // share one contiguous 192-byte DMA region (EP4 has no DMA register of its own): + // EP0 [0:63] (half-duplex OUT+IN) + EP4 OUT [64:127] + EP4 IN [128:191]. Every other endpoint + // (incl. EP3, which is bulk-only here โ CH58X has no isochronous support) gets a plain 128-byte + // OUT+IN buffer, so no oversized EP3 buffer is needed. + TU_ATTR_ALIGNED(4) uint8_t ep0_ep4_buffer[3 * 64]; + TU_ATTR_ALIGNED(4) uint8_t ep1_buffer[2][64]; + TU_ATTR_ALIGNED(4) uint8_t ep2_buffer[2][64]; + TU_ATTR_ALIGNED(4) uint8_t ep3_buffer[2][64]; + TU_ATTR_ALIGNED(4) uint8_t ep5_buffer[2][64]; + TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64]; + TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64]; +#else TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64]; + // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B). TU_ATTR_ALIGNED(4) struct { // OUT transfers >64 bytes will overwrite queued IN data! uint8_t out[64]; uint8_t in[1023]; uint8_t pad; } ep3_buffer; +#endif } data; +// DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and +// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN +// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use +// their own named buffer (see the struct above). +#ifdef CH32_USBFS_EP4_SHARES_EP0 +// OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer). +static inline uint8_t* ch58x_ep_buffer(uint8_t ep) { + switch (ep) { + case 1: return data.ep1_buffer[0]; + case 2: return data.ep2_buffer[0]; + case 3: return data.ep3_buffer[0]; + case 5: return data.ep5_buffer[0]; + case 6: return data.ep6_buffer[0]; + default: return data.ep7_buffer[0]; // ep == 7 + } +} +#endif + +static inline uint32_t ep_dma_addr(uint8_t ep) { +#ifdef CH32_USBFS_EP4_SHARES_EP0 + if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA + return (uint32_t) ch58x_ep_buffer(ep); +#else + if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; } + return (uint32_t) &data.buffer[ep][0]; +#endif +} + +static inline uint8_t* ep_out_buf(uint8_t ep) { +#ifdef CH32_USBFS_EP4_SHARES_EP0 + if (ep == 0) { return &data.ep0_ep4_buffer[0]; } + if (ep == 4) { return &data.ep0_ep4_buffer[64]; } + return ch58x_ep_buffer(ep); +#else + if (ep == 3) { return data.ep3_buffer.out; } + return data.buffer[ep][TUSB_DIR_OUT]; +#endif +} + +static inline uint8_t* ep_in_buf(uint8_t ep) { +#ifdef CH32_USBFS_EP4_SHARES_EP0 + if (ep == 0) { return &data.ep0_ep4_buffer[0]; } // EP0 half-duplex: IN reuses OUT chunk + if (ep == 4) { return &data.ep0_ep4_buffer[128]; } + return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer +#else + if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk + if (ep == 3) { return data.ep3_buffer.in; } + return data.buffer[ep][TUSB_DIR_IN]; +#endif +} + +// EP4 on CH58X has no DMA register (shares EP0's); skip its EP_DMA() write. +static inline bool ep_shares_ep0_dma(uint8_t ep) { +#ifdef CH32_USBFS_EP4_SHARES_EP0 + return ep == 4; +#else + (void) ep; + return false; +#endif +} + /* private helpers */ static void update_in(uint8_t rhport, uint8_t ep, bool force) { - struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN]; + struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_IN]; if (xfer->valid) { if (force || xfer->len) { size_t len = TU_MIN(xfer->max_size, xfer->len); - if (ep == 0) { - memcpy(data.buffer[ep][TUSB_DIR_OUT], xfer->buffer, len); // ep0 uses same chunk - } else if (ep == 3) { - memcpy(data.ep3_buffer.in, xfer->buffer, len); - } else { - memcpy(data.buffer[ep][TUSB_DIR_IN], xfer->buffer, len); - } +#if CFG_TUSB_MCU == OPT_MCU_CH583 + // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023) + // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap + // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's. + len = TU_MIN(len, 64u); +#endif + memcpy(ep_in_buf(ep), xfer->buffer, len); xfer->buffer += len; xfer->len -= len; xfer->processed_len += len; EP_TX_LEN(ep) = len; if (ep == 0) { - EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0); - data.ep0_tog = !data.ep0_tog; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0)); + data.ep0_tog = !data.ep0_tog; } else if (data.isochronous[ep]) { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET; + ep_tx_set_response(ep, USBFS_EP_T_RES_NYET); } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_ACK; + ep_tx_set_response(ep, USBFS_EP_T_RES_ACK); } } else { xfer->valid = false; - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK; - dcd_event_xfer_complete( - rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, - XFER_RESULT_SUCCESS, true); + if (ep == 0) { + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0)); + } else if (!data.isochronous[ep]) { + ep_tx_set_response(ep, USBFS_EP_T_RES_NAK); + } + dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true); } } } static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { - struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT]; + struct usb_xfer *xfer = &data.xfer[ep][TUSB_DIR_OUT]; if (xfer->valid) { size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len)); - if (ep == 3) { - memcpy(xfer->buffer, data.ep3_buffer.out, len); - } else { - memcpy(xfer->buffer, data.buffer[ep][TUSB_DIR_OUT], len); - } +#if CFG_TUSB_MCU == OPT_MCU_CH583 + len = TU_MIN(len, 64u); // cap to the 64-byte EP buffer (see update_in) +#endif + memcpy(xfer->buffer, ep_out_buf(ep), len); xfer->buffer += len; xfer->len -= len; xfer->processed_len += len; @@ -117,41 +270,54 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { } if (ep == 0) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_rx_set_response(0, USBFS_EP_R_RES_NAK); + } else { + uint8_t rx_res = + data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK); + ep_rx_set_response(ep, rx_res); } } } +static void reset_ep_ctrls(void) { + for (uint8_t ep = 1; ep < EP_MAX; ep++) { + if (!ep_shares_ep0_dma(ep)) { EP_DMA(ep) = ep_dma_addr(ep); } + EP_TX_LEN(ep) = 0; + ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NYET); + ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET); + } +} + /* public functions */ -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rh_init; +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; // init registers USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN; USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN; - USBOTG_FS->DEV_ADDR = 0x00; + USBOTG_FS->DEV_ADDR = 0x00; USBOTG_FS->INT_FG = 0xFF; USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND; - // setup endpoint 0 - EP_DMA(0) = (uint32_t) &data.buffer[0][0]; - EP_TX_LEN(0) = 0; - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + // setup endpoint 0 (also backs EP4's buffer on CH58X via the shared DMA region) + EP_DMA(0) = ep_dma_addr(0); + EP_TX_LEN(0) = 0; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK); + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); // enable other endpoints but NAK everything USBOTG_FS->UEP4_1_MOD = 0xCC; USBOTG_FS->UEP2_3_MOD = 0xCC; +#if CFG_TUSB_MCU == OPT_MCU_CH583 + // CH58X: a single mode register enables EP5/6/7 RX+TX (different bit layout than CH32). + USBOTG_FS->UEP567_MOD = RB_UEP5_RX_EN | RB_UEP5_TX_EN | RB_UEP6_RX_EN | RB_UEP6_TX_EN | + RB_UEP7_RX_EN | RB_UEP7_TX_EN; +#else USBOTG_FS->UEP5_6_MOD = 0xCC; - USBOTG_FS->UEP7_MOD = 0x0C; + USBOTG_FS->UEP7_MOD = 0x0C; +#endif - for (uint8_t ep = 1; ep < EP_MAX; ep++) { - EP_DMA(ep) = (uint32_t) &data.buffer[ep][0]; - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK; - } - EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0]; + reset_ep_ctrls(); dcd_connect(rhport); @@ -159,196 +325,251 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } void dcd_int_handler(uint8_t rhport) { - (void) rhport; + (void)rhport; uint8_t status = USBOTG_FS->INT_FG; if (status & USBFS_INT_FG_TRANSFER) { - uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST); - uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST); + uint8_t int_st = USBOTG_FS->INT_ST; + uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(int_st); + uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(int_st); + uint16_t rx_len = USBOTG_FS->RX_LEN; switch (token) { case PID_OUT: { - uint16_t rx_len = USBOTG_FS->RX_LEN; + // Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit + // after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle + // does not reject these on its own, so the check is needed on every variant. EP0 keeps its + // own toggle via the SETUP/status flow and is exempt. + if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; } +#ifdef CH32_USBFS_EP_MANUAL_TOG + // CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet + // (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets). + EP_CTRL(ep) ^= USBFS_EPC_R_TOG; +#endif update_out(rhport, ep, rx_len); break; } case PID_IN: +#ifdef CH32_USBFS_EP_MANUAL_TOG + // Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own). + if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; } +#endif update_in(rhport, ep, false); break; case PID_SETUP: // setup clears stall - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK); + data.ep0_tog = true; + // A new SETUP supersedes any control transfer still in flight; drop its stale EP0 state so a + // spurious EP0 IN/OUT can't run update_in()/update_out() against the previous request. + data.xfer[0][TUSB_DIR_OUT].valid = false; + data.xfer[0][TUSB_DIR_IN].valid = false; + + uint8_t *ep0_out = ep_out_buf(0); + const tusb_control_request_t *setup = (const tusb_control_request_t *)ep0_out; + // EP0_SETUP_RX_TOG arms the data/status stage at DATA1 on the combined-control IP + ep_rx_ctrl_set(0, ((setup->wLength == 0) ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK) | EP0_SETUP_RX_TOG); - data.ep0_tog = true; - dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true); + dcd_event_setup_received(rhport, ep0_out, true); break; } USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER; } else if (status & USBFS_INT_FG_BUS_RST) { - data.ep0_tog = true; + data.ep0_tog = true; data.xfer[0][TUSB_DIR_OUT].max_size = 64; - data.xfer[0][TUSB_DIR_IN].max_size = 64; + data.xfer[0][TUSB_DIR_IN].max_size = 64; - //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); - dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); + // dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, + // true); + dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, + true); USBOTG_FS->DEV_ADDR = 0x00; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); + + reset_ep_ctrls(); USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST; } else if (status & USBFS_INT_FG_SUSPEND) { +#if CFG_TUSB_MCU == OPT_MCU_CH583 + // CH58x raises this single interrupt for both suspend and resume; MIS_ST's suspend bit tells + // them apart (set while suspended, clear once resumed) so tud_resume_cb() actually fires. + dcd_event_t event = {.rhport = rhport, + .event_id = (USBOTG_FS->MIS_ST & USBFS_MIS_ST_SUSPEND) ? DCD_EVENT_SUSPEND : DCD_EVENT_RESUME}; +#else dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND}; +#endif dcd_event_handler(&event, true); USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND; } } void dcd_int_enable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_EnableIRQ(USBHD_IRQn); } void dcd_int_disable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_DisableIRQ(USBHD_IRQn); } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - (void) dev_addr; + (void)dev_addr; dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response } void dcd_remote_wakeup(uint8_t rhport) { - (void) rhport; + (void)rhport; // TODO optional } void dcd_connect(uint8_t rhport) { - (void) rhport; + (void)rhport; USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN; } void dcd_disconnect(uint8_t rhport) { - (void) rhport; + (void)rhport; USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN; } void dcd_sof_enable(uint8_t rhport, bool en) { - (void) rhport; - (void) en; + (void)rhport; + (void)en; // TODO implement later } -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { - (void) rhport; +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS) { - USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue; + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { +#if CFG_TUSB_MCU == OPT_MCU_CH583 + // On CH58x R8_USB_DEV_AD bit 7 is a user general-purpose flag; only bits [6:0] are the address. + USBOTG_FS->DEV_ADDR = (uint8_t)((USBOTG_FS->DEV_ADDR & 0x80u) | (request->wValue & 0x7Fu)); +#else + USBOTG_FS->DEV_ADDR = (uint8_t)request->wValue; +#endif } - EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; } -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { - (void) rhport; - uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); TU_ASSERT(ep < EP_MAX); - data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS; data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep); if (ep != 0) { + // Opening clears the toggle to DATA0 (ep_*_ctrl_set writes the toggle bit clear since v has no + // R/T_TOG); with manual toggle EP_*_AUTO_TOG is 0 so the ISR owns subsequent toggling. if (dir == TUSB_DIR_OUT) { - if (data.isochronous[ep]) { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET; - } else { - EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK; - } + ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK); } else { - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; + ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK); } } return true; } void dcd_edpt_close_all(uint8_t rhport) { - (void) rhport; + (void)rhport; // TODO optional } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void) rhport; - (void) ep_addr; + (void)rhport; + (void)ep_addr; (void)largest_packet_size; +#if CFG_TUSB_MCU == OPT_MCU_CH583 + // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use + // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/ + // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright. return false; +#else + uint8_t ep = tu_edpt_number(ep_addr); + uint8_t dir = tu_edpt_dir(ep_addr); + + data.isochronous[ep] = true; + data.xfer[ep][dir].max_size = largest_packet_size; + return true; +#endif } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; (void)desc_ep; - return false; +#if CFG_TUSB_MCU == OPT_MCU_CH583 + return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc) +#else + return true; +#endif } -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); - struct usb_xfer* xfer = &data.xfer[ep][dir]; + struct usb_xfer *xfer = &data.xfer[ep][dir]; + // Keep the IRQ masked across the whole arming sequence: update_in()/ep_rx_set_response() do a + // read-modify-write of the (combined) EP control register, which the ISR also RMWs to flip the + // manual data toggle; re-enabling before they run lets a transfer IRQ clobber that toggle. dcd_int_disable(rhport); - xfer->valid = true; - xfer->buffer = buffer; - xfer->len = total_bytes; + xfer->valid = true; + xfer->buffer = buffer; + xfer->len = total_bytes; xfer->processed_len = 0; - dcd_int_enable(rhport); if (dir == TUSB_DIR_IN) { update_in(rhport, ep, true); + } else { + uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK; + ep_rx_set_response(ep, rx_res); } + dcd_int_enable(rhport); return true; } void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_STALL); } else { - EP_TX_LEN(0) = 0; - EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL; + EP_TX_LEN(0) = 0; + ep_tx_ctrl_set(0, USBFS_EP_T_RES_STALL); } } else { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | USBFS_EP_R_RES_STALL; + ep_rx_set_response(ep, USBFS_EP_R_RES_STALL); } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | USBFS_EP_T_RES_STALL; + ep_tx_set_response(ep, USBFS_EP_T_RES_STALL); } } } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - uint8_t ep = tu_edpt_number(ep_addr); + (void)rhport; + uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); if (ep == 0) { if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); } } else { + // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK; + ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK); } else { - EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK; + ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK); } } } diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c index 11734de37..ea3b052ad 100644 --- a/src/portable/wch/dcd_ch32_usbhs.c +++ b/src/portable/wch/dcd_ch32_usbhs.c @@ -24,7 +24,6 @@ * * This file is part of the TinyUSB stack. */ - #include "tusb_option.h" #if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \ @@ -37,138 +36,182 @@ #define EP_MAX 16 typedef struct { - uint8_t* buffer; + uint8_t *buffer; uint16_t total_len; uint16_t queued_len; uint16_t max_size; - bool is_last_packet; - bool is_iso; + bool is_iso; + bool valid; } xfer_ctl_t; -typedef enum { - EP_RESPONSE_ACK, - EP_RESPONSE_NAK, -} ep_response_list_t; - -#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] + #define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] static xfer_ctl_t xfer_status[EP_MAX][2]; -#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2) -#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4) -#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4) -#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2) + #define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2) + #define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4) + #define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4) + #define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2) -#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1)) -#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1)) + #define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1)) + #define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1)) /* Endpoint Buffer */ TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE]; +static bool ep0_tog; +static bool ep_data_tog[EP_MAX][2]; -static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) { +static void set_ep_toggle(uint8_t ep_num, tusb_dir_t ep_dir, bool data1) { if (ep_dir == TUSB_DIR_IN) { - uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK; - if (ep_num == 0) { - if (response_type == EP_RESPONSE_ACK) { - if (EP_TX_LEN(ep_num) == 0) { - EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1; - } else { - EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1; - } - } - } - if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) { - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG; - } else { - EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response; - } + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_TOG_MASK)) | + (data1 ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); } else { - uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK; - if (ep_num == 0) { - if (response_type == EP_RESPONSE_ACK) { - if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) { - EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1; - } - } else { - EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1; - } - } - EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response; + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_TOG_MASK)) | + (data1 ? USBHS_EP_R_TOG_1 : USBHS_EP_R_TOG_0); } } -static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) { - if (ep_dir == TUSB_DIR_IN) { - uint16_t remaining = xfer->total_len - xfer->queued_len; - uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size); +static void queue_in_packet(uint8_t ep_num, xfer_ctl_t* xfer) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t tx_len = TU_MIN(remaining, xfer->max_size); + + if (ep_num == 0) { + memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], tx_len); + } else { + EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + } + + EP_TX_LEN(ep_num) = tx_len; + xfer->queued_len += tx_len; + + if (ep_num == 0) { + EP_TX_CTRL(0) = USBHS_EP_T_RES_ACK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); + ep0_tog = !ep0_tog; + } else if (xfer->is_iso) { + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NYET; + } else { + set_ep_toggle(ep_num, TUSB_DIR_IN, ep_data_tog[ep_num][TUSB_DIR_IN]); + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_ACK; + } +} + +static void queue_out_packet(uint8_t ep_num, xfer_ctl_t* xfer) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t rx_len = TU_MIN(remaining, xfer->max_size); + + if (ep_num > 0) { + EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + EP_RX_MAX_LEN(ep_num) = rx_len; + } + + if (ep_num == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK; + } else if (xfer->is_iso) { + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NYET; + } else { + set_ep_toggle(ep_num, TUSB_DIR_OUT, ep_data_tog[ep_num][TUSB_DIR_OUT]); + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_ACK; + } +} +static void update_in(uint8_t rhport, uint8_t ep_num, bool force) { + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN); + if (!xfer->valid) { + return; + } + + if (!force && ep_num != 0 && !xfer->is_iso) { + ep_data_tog[ep_num][TUSB_DIR_IN] = !ep_data_tog[ep_num][TUSB_DIR_IN]; + } + + if (force || (xfer->total_len > xfer->queued_len)) { + queue_in_packet(ep_num, xfer); + } else { + xfer->valid = false; if (ep_num == 0) { - memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size); + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | (ep0_tog ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0); } else { - EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | USBHS_EP_T_RES_NAK; } + dcd_event_xfer_complete(rhport, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } +} - EP_TX_LEN(ep_num) = next_tx_size; - xfer->queued_len += next_tx_size; - if (xfer->queued_len == xfer->total_len) { - xfer->is_last_packet = true; - } - if (xfer->is_iso == true) { - /* Enable EP to generate ISA_ACT interrupt */ - USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); - } - } else { /* TUSB_DIR_OUT */ - uint16_t left_to_receive = xfer->total_len - xfer->queued_len; - uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive); +static void update_out(uint8_t rhport, uint8_t ep_num, uint16_t rx_len) { + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); + if (!xfer->valid) { + return; + } + + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t len = TU_MIN(rx_len, TU_MIN(remaining, xfer->max_size)); + + if (ep_num == 0) { + memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, len); + } - if (max_possible_rx_size == left_to_receive) { - xfer->is_last_packet = true; + xfer->queued_len += len; + + if (ep_num != 0 && !xfer->is_iso) { + ep_data_tog[ep_num][TUSB_DIR_OUT] = !ep_data_tog[ep_num][TUSB_DIR_OUT]; + } + + if ((xfer->queued_len == xfer->total_len) || (len < xfer->max_size)) { + xfer->valid = false; + if (ep_num == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_RES_MASK)) | USBHS_EP_R_RES_NAK; } + dcd_event_xfer_complete(rhport, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } - if (ep_num > 0) { - EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; - EP_RX_MAX_LEN(ep_num) = max_possible_rx_size; + if (ep_num != 0) { + if (xfer->valid) { + queue_out_packet(ep_num, xfer); + } else { + uint8_t rx_res = xfer->is_iso ? USBHS_EP_R_RES_NYET : USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | rx_res; } } - ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK); } -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rhport; + (void)rh_init; memset(&xfer_status, 0, sizeof(xfer_status)); + memset(ep_data_tog, 0, sizeof(ep_data_tog)); + ep0_tog = true; USBHSD->HOST_CTRL = 0x00; USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM; USBHSD->CONTROL = 0; -#if TUD_OPT_HIGH_SPEED + #if TUD_OPT_HIGH_SPEED USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED; -#else - #error OPT_MODE_FULL_SPEED not currently supported on CH32 + #else + #error OPT_MODE_FULL_SPEED not currently supported on CH32 USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED; -#endif + #endif USBHSD->INT_EN = 0; - USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN; + USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN; USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN; - USBHSD->ENDP_TYPE = 0x00; - USBHSD->BUF_MODE = 0x00; + USBHSD->ENDP_TYPE = 0x00; + USBHSD->BUF_MODE = 0x00; for (int ep = 0; ep < EP_MAX; ep++) { - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep) = 0; } - USBHSD->UEP0_DMA = (uint32_t) ep0_buffer; - USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE; + USBHSD->UEP0_DMA = (uint32_t)ep0_buffer; + USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; - xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; USBHSD->DEV_AD = 0; USBHSD->CONTROL |= USBHS_DEV_PU_EN; @@ -177,22 +220,24 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { } void dcd_int_enable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_EnableIRQ(USBHS_IRQn); } void dcd_int_disable(uint8_t rhport) { - (void) rhport; + (void)rhport; NVIC_DisableIRQ(USBHS_IRQn); } void dcd_edpt_close_all(uint8_t rhport) { - (void) rhport; + (void)rhport; + + memset(ep_data_tog, 0, sizeof(ep_data_tog)); for (size_t ep = 1; ep < EP_MAX; ep++) { - EP_TX_LEN(ep) = 0; - EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; - EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_RX_CTRL(ep) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep) = 0; } @@ -201,18 +246,18 @@ void dcd_edpt_close_all(uint8_t rhport) { } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - (void) dev_addr; + (void)dev_addr; // Response with zlp status dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); } void dcd_remote_wakeup(uint8_t rhport) { - (void) rhport; + (void)rhport; } void dcd_sof_enable(uint8_t rhport, bool en) { - (void) rhport; + (void)rhport; if (en) { USBHSD->INT_EN |= USBHS_SOF_ACT_EN; } else { @@ -220,24 +265,19 @@ void dcd_sof_enable(uint8_t rhport, bool en) { } } -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { - (void) rhport; - +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS) { - USBHSD->DEV_AD = (uint8_t) request->wValue; + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { + USBHSD->DEV_AD = (uint8_t)request->wValue; } - - EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; - EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; } -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { - (void) rhport; +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_edpt) { + (void)rhport; - uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); - tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + const uint8_t ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); + const tusb_dir_t dir = tu_edpt_dir(desc_edpt->bEndpointAddress); TU_ASSERT(ep_num < EP_MAX); @@ -245,13 +285,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { return true; } - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); - xfer->max_size = tu_edpt_packet_size(desc_edpt); + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->max_size = tu_edpt_packet_size(desc_edpt); + ep_data_tog[ep_num][dir] = false; xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); if (dir == TUSB_DIR_OUT) { USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num); - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; if (xfer->is_iso == true) { USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num); } @@ -259,31 +300,31 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { } else { if (xfer->is_iso == true) { USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num); - } else { - /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */ - USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); } - EP_TX_LEN(ep_num) = 0; - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); + EP_TX_LEN(ep_num) = 0; + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; } return true; } void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; EP_RX_MAX_LEN(ep_num) = 0; + ep_data_tog[ep_num][TUSB_DIR_OUT] = false; USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num); USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num); - } else { // TUSB_DIR_IN - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; - EP_TX_LEN(ep_num) = 0; + } else { // TUSB_DIR_IN + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_TX_LEN(ep_num) = 0; + ep_data_tog[ep_num][TUSB_DIR_IN] = false; USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num); USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); } @@ -305,128 +346,120 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) #endif void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL; } else { - EP_TX_LEN(0) = 0; + EP_TX_LEN(ep_num) = 0; EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL; } } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; + (void)rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; + ep_data_tog[ep_num][TUSB_DIR_OUT] = false; } else { - EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + ep_data_tog[ep_num][TUSB_DIR_IN] = false; } } -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { - (void) is_isr; - (void) rhport; - uint8_t const ep_num = tu_edpt_number(ep_addr); - tusb_dir_t const dir = tu_edpt_dir(ep_addr); +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->valid = true; - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; - xfer->is_last_packet = false; + if (ep_num == 0 && dir == TUSB_DIR_OUT) { + if (total_bytes == 0) { + EP_RX_CTRL(0) = (EP_RX_CTRL(0) & ~(USBHS_EP_R_TOG_MASK)) | USBHS_EP_R_TOG_1; + } else { + EP_RX_CTRL(0) ^= USBHS_EP_R_TOG_1; + } + } - xfer_data_packet(ep_num, dir, xfer); + if (dir == TUSB_DIR_IN) { + update_in(rhport, ep_num, true); + } else { + queue_out_packet(ep_num, xfer); + } return true; } void dcd_int_handler(uint8_t rhport) { - (void) rhport; + (void)rhport; - uint8_t int_flag = USBHSD->INT_FG; + uint8_t int_flag = USBHSD->INT_FG; uint8_t int_status = USBHSD->INT_ST; - if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) { - uint8_t const token = int_status & MASK_UIS_TOKEN; + if (int_flag & USBHS_TRANSFER_FLAG) { + const uint8_t token = int_status & MASK_UIS_TOKEN; + const uint8_t ep_num = int_status & MASK_UIS_ENDP; + const uint16_t len = USBHSD->RX_LEN; if (token == USBHS_TOKEN_PID_SOF) { uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK; dcd_event_sof(rhport, frame_count, true); - }else { - uint8_t const ep_num = int_status & MASK_UIS_ENDP; - tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT; - uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir); - xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir); - - if (token == USBHS_TOKEN_PID_OUT) { - uint16_t rx_len = USBHSD->RX_LEN; - - if (ep_num == 0) { - memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len); - } - - xfer->queued_len += rx_len; - if (rx_len < xfer->max_size) { - xfer->is_last_packet = true; - } - } else if (token == USBHS_TOKEN_PID_IN) { - if (xfer->is_iso && xfer->is_last_packet) { - /* Disable EP to avoid ISO_ACT interrupt generation */ - USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); - } else { - // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet - } - } - - if (xfer->is_last_packet == true) { - ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK); - dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true); - } else { - /* prepare next part of packet to xref */ - xfer_data_packet(ep_num, ep_dir, xfer); - } + } else if (token == USBHS_TOKEN_PID_OUT) { + update_out(rhport, ep_num, len); + } else if (token == USBHS_TOKEN_PID_IN) { + update_in(rhport, ep_num, false); } - - USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */ + USBHSD->INT_FG = (int_flag & USBHS_TRANSFER_FLAG); /* Clear flag */ } else if (int_flag & USBHS_SETUP_FLAG) { - ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK); - ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK); + tusb_control_request_t const* setup = + (tusb_control_request_t const*) ep0_buffer; + ep0_tog = true; + EP_RX_CTRL(0) = (setup->wLength == 0) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK; + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK; + dcd_event_setup_received(0, ep0_buffer, true); USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */ } else if (int_flag & USBHS_BUS_RST_FLAG) { // TODO CH32 does not detect actual speed at this time (should be known at end of reset) // This interrupt probably triggered at start of bus reset -// tusb_speed_t actual_speed; -// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){ -// case USBHS_SPEED_TYPE_HIGH: -// actual_speed = TUSB_SPEED_HIGH; -// break; -// case USBHS_SPEED_TYPE_FULL: -// actual_speed = TUSB_SPEED_FULL; -// break; -// case USBHS_SPEED_TYPE_LOW: -// actual_speed = TUSB_SPEED_LOW; -// break; -// default: -// TU_ASSERT(0,); -// break; -// } -// dcd_event_bus_reset(0, actual_speed, true); + // tusb_speed_t actual_speed; + // switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){ + // case USBHS_SPEED_TYPE_HIGH: + // actual_speed = TUSB_SPEED_HIGH; + // break; + // case USBHS_SPEED_TYPE_FULL: + // actual_speed = TUSB_SPEED_FULL; + // break; + // case USBHS_SPEED_TYPE_LOW: + // actual_speed = TUSB_SPEED_LOW; + // break; + // default: + // TU_ASSERT(0,); + // break; + // } + // dcd_event_bus_reset(0, actual_speed, true); dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true); USBHSD->DEV_AD = 0; - EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0; - EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + memset(ep_data_tog, 0, sizeof(ep_data_tog)); + ep0_tog = true; + EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0; + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */ } else if (int_flag & USBHS_SUSPEND_FLAG) { @@ -434,6 +467,9 @@ void dcd_int_handler(uint8_t rhport) { dcd_event_handler(&event, true); USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */ + } else { + // Unhandled interrupt + USBHSD->INT_FG = int_flag; /* Clear all flags */ } } #endif |
