/* * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama * SPDX-FileCopyrightText: Copyright (c) 2024, Brent Kowal (Analog Devices, Inc) * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ #include "tusb_option.h" #if CFG_TUD_ENABLED && defined(TUP_USBIP_MUSB) #define MUSB_DEBUG 2 #define MUSB_REGS(rhport) ((musb_regs_t*) MUSB_BASES[rhport]) #include "musb_type.h" #include "device/dcd.h" // Following symbols must be defined by port header // - musb_dcd_int_enable/disable/clear/get_enable // - musb_dcd_int_handler_enter/exit #if defined(TUP_USBIP_MUSB_TI) #include "musb_ti.h" #elif defined(TUP_USBIP_MUSB_ADI) #include "musb_max32.h" #elif defined(TUP_USBIP_MUSB_PY32) #include "musb_py32.h" #else #error "Unsupported MCU" #endif /*------------------------------------------------------------------ * MACRO TYPEDEF CONSTANT ENUM DECLARATION *------------------------------------------------------------------*/ typedef union { volatile uint8_t u8; volatile uint16_t u16; volatile uint32_t u32; } hw_fifo_t; 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 */ uint16_t mps; /* maximum packet size */ 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; // 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]; } TU_ATTR_ALWAYS_INLINE static inline uint16_t musb_mps_to_maxp(uint16_t mps) { #if defined(TUP_USBIP_MUSB_PY32) return (uint8_t) ((mps + 7u) / 8u); #else return mps; #endif } //-------------------------------------------------------------------- // HW FIFO Helper // Note: Index register is already set by caller //-------------------------------------------------------------------- #if MUSB_CFG_DYNAMIC_FIFO // musb is configured to use dynamic FIFO sizing. // FF Size is encodded: 1 << (fifo_size[3:0] + 3) = 8 << fifo_size[3:0] // FF Address is 8*ff_addr[12:0] // First 64 bytes are reserved for EP0 static uint32_t alloced_fifo_bytes; // ffsize is log2(mps) - 3 (round up) TU_ATTR_ALWAYS_INLINE static inline uint8_t hwfifo_byte2size(uint16_t nbytes) { uint8_t ffsize = 28 - tu_min8(28, __builtin_clz(nbytes)); if ((8u << ffsize) < nbytes) { ++ffsize; } return ffsize; } TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigned epnum, unsigned is_rx) { (void) epnum; musb->fifo_size[is_rx] = 0; musb->fifo_addr[is_rx] = 0; } TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps, bool double_packet) { uint8_t ffsize = hwfifo_byte2size(mps); mps = 8 << ffsize; // round up to the next power of 2 if (double_packet) { ffsize |= MUSB_FIFOSZ_DOUBLE_PACKET; mps <<= 1; } TU_ASSERT(alloced_fifo_bytes + mps <= MUSB_CFG_DYNAMIC_FIFO_SIZE); 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; } #else TU_ATTR_ALWAYS_INLINE static inline void hwfifo_reset(musb_regs_t* musb, unsigned epnum, unsigned is_rx) { (void) musb; (void) epnum; (void) is_rx; // nothing to do for static FIFO } TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsigned epnum, unsigned is_rx, unsigned mps, bool double_packet) { (void) mps; #if defined(TUP_USBIP_MUSB_PY32) (void) musb; (void) is_rx; (void) double_packet; // Puya FIFO sizes: EP0 = 64 B, EP1 = 512 B, EP2..4 = 128 B, EP5 = 64 B, shared between IN and OUT. static const uint16_t py32_fifo_size[] = { 64, 512, 128, 128, 128, 64 }; return epnum < TU_ARRAY_SIZE(py32_fifo_size) && mps <= py32_fifo_size[epnum]; #elif 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; } #endif // Flush FIFO and clear data toggle TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigned epnum, unsigned is_rx, bool clear_dtog) { (void) epnum; const uint8_t csrl_dtog = clear_dtog ? MUSB_CSRL_CLEAR_DATA_TOGGLE(is_rx) : 0; musb_ep_maxp_csr_t* maxp_csr = &musb->indexed_csr.maxp_csr[is_rx]; // may need to flush twice for double packet for (unsigned i=0; i<2; i++) { if (maxp_csr->csrl & MUSB_CSRL_PACKET_READY(is_rx)) { maxp_csr->csrl = MUSB_CSRL_FLUSH_FIFO(is_rx) | csrl_dtog; } } } // 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 = pipe->mps; 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; } // 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 } pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN); // No active transfer: a halt/abort disarmed the pipe (armed=false) but may leave remaining>0. // Do not keep loading the aborted transfer — that would re-fill the just-flushed FIFO and the // next (re-armed) transfer's data would stack on top (host sees an oversized packet -> babble). if (!pipe->armed) { return; } 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; pipe->armed = false; dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, TUSB_DIR_IN), xferred_len, XFER_RESULT_SUCCESS, true); } } // 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 = pipe->mps; 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_read(hwfifo, pipe->buf, xact_len, NULL); pipe->buf += xact_len; } pipe->remaining -= xact_len; } ep_csr->rx_csrl = 0; /* Clear RXRDY - release this FIFO slot */ return (xact_len < mps); } 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); if (ep_csr->rx_csrl & MUSB_RXCSRL1_STALLED) { ep_csr->rx_csrl &= ~(MUSB_RXCSRL1_STALLED | MUSB_RXCSRL1_OVER); return; // sent STALL, do nothing } // Fail gracefully. Spurious interrupt. if (!(ep_csr->rx_csrl & MUSB_RXCSRL1_RXRDY)) { return; } 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; } 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; pipe->armed = false; dcd_event_xfer_complete(rhport, epnum, xferred_len, XFER_RESULT_SUCCESS, is_isr); } } 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; musb_regs_t *musb_regs = MUSB_REGS(rhport); musb_ep_csr_t *ep_csr = get_ep_csr(musb_regs, epnum); if (dir_in) { pipe_write(musb_regs, pipe, epnum); } else { // 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, 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); pipe0_state_t* pipe0 = &_dcd.pipe0; const unsigned dir_in = tu_edpt_dir(ep_addr); 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; } 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; } // 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; // 21.1.5: SentStall and SetupEnd must be checked before anything else. if (csrl & MUSB_CSRL0_STALLED) { ep_csr->csr0l = 0; pipe0->state = PIPE0_STATE_IDLE; pipe0->deferred_setup_valid = false; pipe0->rxrdy_consumed = false; return; } if (csrl & MUSB_CSRL0_SETEND) { // 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; 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 */ } } // Receive Data (Setup or OUT) if (csrl & MUSB_CSRL0_RXRDY) { if (pipe0->rxrdy_consumed) { return; // stale latched IRQ: this RXRDY's packet was already drained } 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; } 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; } // 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; default: break; } return; } 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; } /* 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_isr(uint8_t rhport) { musb_regs_t* musb = MUSB_REGS(rhport); #if MUSB_CFG_DYNAMIC_FIFO alloced_fifo_bytes = CFG_TUD_ENDPOINT0_SIZE; #endif 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; /* Clear FIFO settings */ for (unsigned i = 1; i < TUP_DCD_ENDPOINT_MAX; ++i) { musb->index = i; hwfifo_reset(musb, i, 0); hwfifo_reset(musb, i, 1); } #if defined(TUP_USBIP_MUSB_PY32) dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true); #else dcd_event_bus_reset(rhport, (musb->power & MUSB_POWER_HSMODE) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, true); #endif } /*------------------------------------------------------------------ * Device API *------------------------------------------------------------------*/ #if CFG_TUSB_DEBUG >= MUSB_DEBUG static void print_musb_info(musb_regs_t* musb_regs) { #if defined(TUP_USBIP_MUSB_PY32) (void) musb_regs; // musb discovery fields not present TU_LOG1("musb py32 fixed full-speed configuration\r\n"); #else // print version, epinfo, raminfo, config_data0, fifo_size TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor); TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num); TU_LOG1("RAM Info: %u DMA Channel, %u RAM address width\r\n", musb_regs->raminfo_bit.dma_channel, musb_regs->raminfo_bit.ram_bits); musb_regs->index = 0; TU_LOG1("config_data0 = 0x%x\r\n", musb_regs->indexed_csr.config_data0); #if MUSB_CFG_DYNAMIC_FIFO TU_LOG1("Dynamic FIFO configuration\r\n"); #else for (uint8_t i=1; i <= musb_regs->epinfo_bit.tx_ep_num; i++) { musb_regs->index = i; TU_LOG1("FIFO %u Size: TX %u RX %u\r\n", i, musb_regs->indexed_csr.fifo_size_bit.tx, musb_regs->indexed_csr.fifo_size_bit.rx); } #endif #endif } #endif bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { (void) rh_init; musb_regs_t* musb_regs = MUSB_REGS(rhport); #if CFG_TUSB_DEBUG >= MUSB_DEBUG print_musb_info(musb_regs); #endif musb_regs->intr_usben |= MUSB_IE_SUSPND; musb_dcd_int_clear(rhport); musb_dcd_phy_init(rhport); dcd_connect(rhport); return true; } void dcd_int_enable(uint8_t rhport) { musb_dcd_int_enable(rhport); } void dcd_int_disable(uint8_t rhport) { musb_dcd_int_disable(rhport); } // 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) { 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; 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 void dcd_remote_wakeup(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_regs->power |= MUSB_POWER_RESUME; unsigned cnt = SystemCoreClock / 1000; while (cnt--) __NOP(); musb_regs->power &= ~MUSB_POWER_RESUME; } #if defined(TUP_USBIP_MUSB_PY32) void dcd_connect(uint8_t rhport) { (void) rhport; } void dcd_disconnect(uint8_t rhport) { (void) rhport; } #else // Connect by enabling internal pull-up resistor on D+/D- void dcd_connect(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_regs->power |= TUD_OPT_HIGH_SPEED ? MUSB_POWER_HSENAB : 0; musb_regs->power |= MUSB_POWER_SOFTCONN; } // Disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_regs->power &= ~MUSB_POWER_SOFTCONN; } #endif void dcd_sof_enable(uint8_t rhport, bool en) { (void) rhport; (void) en; // TODO implement later } //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ // 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 tusb_dir_t epdir = tu_edpt_dir(ep_addr); const unsigned mps = tu_edpt_packet_size(ep_desc); pipe_state_t *pipe = pipe_get(epn, epdir); pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; pipe->mps = (uint16_t) mps; 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 - epdir); musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx]; maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps); maxp_csr->csrh = 0; #if MUSB_CFG_SHARED_FIFO 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, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK)); musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn); return true; } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { const unsigned epn = tu_edpt_number(ep_addr); const unsigned dir_in = tu_edpt_dir(ep_addr); 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; pipe_state_t *pipe = pipe_get(epn, dir_in); pipe->mps = largest_packet_size; ep_csr->maxp_csr[is_rx].csrh = 0; TU_ASSERT(hwfifo_config(musb, epn, is_rx, largest_packet_size, true)); return true; } 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 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 = pipe_get(epn, dir_in); pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; pipe->mps = (uint16_t) mps; 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; musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx]; maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps); maxp_csr->csrh |= MUSB_CSRH_ISO; #if MUSB_CFG_SHARED_FIFO if (dir_in) { maxp_csr->csrh |= MUSB_CSRH_TX_MODE; } #endif hwfifo_flush(musb, epn, is_rx, true); #if MUSB_CFG_DYNAMIC_FIFO // fifo space is already allocated, keep the address and just change packet size musb->fifo_size[is_rx] = hwfifo_byte2size(mps) | MUSB_FIFOSZ_DOUBLE_PACKET; #endif musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn); if (ie) musb_dcd_int_enable(rhport); return true; } void dcd_edpt_close_all(uint8_t rhport) { musb_regs_t* musb = MUSB_REGS(rhport); unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); musb->intr_txen = 1; /* Enable only EP0 */ musb->intr_rxen = 0; for (unsigned i = 1; i < TUP_DCD_ENDPOINT_MAX; ++i) { musb_ep_csr_t* ep_csr = get_ep_csr(musb, i); for (unsigned d = 0; d < 2; d++) { musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[d]; hwfifo_flush(musb, i, d, true); hwfifo_reset(musb, i, d); maxp_csr->maxp = musb_mps_to_maxp(0); maxp_csr->csrh = 0; } } #if MUSB_CFG_DYNAMIC_FIFO alloced_fifo_bytes = CFG_TUD_ENDPOINT0_SIZE; #endif if (ie) musb_dcd_int_enable(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)rhport; bool ret; unsigned const epnum = tu_edpt_number(ep_addr); unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); if (epnum) { ret = edpt_n_xfer(rhport, ep_addr, buffer, total_bytes, false, is_isr); } else { (void) is_isr; ret = edpt0_xfer(rhport, ep_addr, buffer, total_bytes, is_isr); } if (ie) { musb_dcd_int_enable(rhport); } return ret; } // Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack // - 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)rhport; bool ret; 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); ret = edpt_n_xfer(rhport, ep_addr, ff, total_bytes, true, is_isr); if (ie) musb_dcd_int_enable(rhport); return ret; } // Stall endpoint void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); unsigned const epn = tu_edpt_number(ep_addr); musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); if (0 == epn) { 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 tusb_dir_t ep_dir = tu_edpt_dir(ep_addr); const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u); // A halt aborts the transfer: flush staged FIFO packet(s) before stalling, else leftover TX data // concatenates with the next transfer after un-halt -> host sees an oversized packet (babble). // FLUSH must precede SEND_STALL, which clears the TXRDY that hwfifo_flush() gates on. hwfifo_flush(musb_regs, epn, is_rx, false); 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); } // clear stall, data toggle is also reset to DATA0 void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { (void)rhport; unsigned const ie = musb_dcd_get_int_enable(rhport); musb_dcd_int_disable(rhport); unsigned const epn = tu_edpt_number(ep_addr); musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, epn); const uint8_t is_rx = 1 - tu_edpt_dir(ep_addr); ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_CLEAR_DATA_TOGGLE(is_rx); if (ie) musb_dcd_int_enable(rhport); } /*------------------------------------------------------------------- * ISR *-------------------------------------------------------------------*/ void dcd_int_handler(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); const uint8_t saved_index = musb_regs->index; // save endpoint index //Part specific ISR setup/entry musb_dcd_int_handler_enter(rhport); uint_fast8_t intr_usb = musb_regs->intr_usb; // a read will clear this interrupt status uint_fast8_t intr_tx = musb_regs->intr_tx; // a read will clear this interrupt status uint_fast8_t intr_rx = musb_regs->intr_rx; // a read will clear this interrupt status // TU_LOG1("D%2x T%2x R%2x\r\n", is, txis, rxis); intr_usb &= musb_regs->intr_usben; /* Clear disabled interrupts */ if (intr_usb & MUSB_IS_DISCON) { } if (intr_usb & MUSB_IS_SOF) { dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); } if (intr_usb & MUSB_IS_RESET) { process_bus_reset_isr(rhport); } if (intr_usb & MUSB_IS_RESUME) { dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); } if (intr_usb & MUSB_IS_SUSPEND) { dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); } intr_tx &= musb_regs->intr_txen; /* Clear disabled interrupts */ while (intr_tx) { 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 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 } #endif