summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/portable/mentor/musb/dcd_musb.c167
1 files changed, 93 insertions, 74 deletions
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index 7b46580cc..c646380d5 100644
--- a/src/portable/mentor/musb/dcd_musb.c
+++ b/src/portable/mentor/musb/dcd_musb.c
@@ -50,8 +50,6 @@
* MACRO TYPEDEF CONSTANT ENUM DECLARATION
*------------------------------------------------------------------*/
-#define REQUEST_TYPE_INVALID (0xFFu)
-
typedef union {
volatile uint8_t u8;
volatile uint16_t u16;
@@ -82,27 +80,25 @@ typedef struct {
#define MUSB_PIPE_COUNT (2u * TUP_DCD_ENDPOINT_MAX - 1u)
#endif
+// EP0 control-transfer state (§21.1.4). The IRQ handler derives direction
+// and phase from this state instead of the cached SETUP packet.
enum {
- EP0_STATE_IDLE = 0,
- EP0_STATE_TX,
- EP0_STATE_RX,
- EP0_STATE_STATUS
+ EP0_STATE_IDLE = 0, // no active control transfer
+ EP0_STATE_SETUP_RECEIVED, // SETUP received, awaiting DATA or STATUS call from usbd
+ EP0_STATE_TX, // DATA IN armed (TXRDY set), awaiting send-ACK IRQ
+ EP0_STATE_RX, // DATA OUT armed (RXRDY cleared), awaiting host-packet IRQ
+ EP0_STATE_STATUS, // STATUS IN-ZLP armed (DATAEND set), awaiting confirmation IRQ
};
typedef struct {
- union {
- tusb_control_request_t setup_packet;
- uint32_t setup_buffer[2];
- };
uint8_t ep0_state;
+ uint8_t pending_addr; // new USB address latched by dcd_set_address, applied when STATUS IN completes
pipe_state_t pipe[MUSB_PIPE_COUNT];
} dcd_data_t;
// EP0 control-transfer state is held by usbd_control.c (request, total_xferred,
-// data_len). dcd just keeps the last SETUP packet's bmRequestType so it knows
-// the original direction when handling DATA/STATUS phase calls. After the
-// transfer's STATUS stage completes (or a new SETUP/SETEND aborts it), the
-// bmRequestType is reset to REQUEST_TYPE_INVALID.
+// data_len). dcd tracks phase in _dcd.ep0_state. The SETUP packet is drained
+// into a local in process_ep0 and dispatched upstream — never cached here.
static dcd_data_t _dcd;
@@ -375,140 +371,161 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
pipe_state_t* pipe0 = pipe_get(0, TUSB_DIR_OUT);
const unsigned dir_in = tu_edpt_dir(ep_addr);
- const unsigned req = _dcd.setup_packet.bmRequestType;
if (total_bytes == 0) {
// STATUS phase
- if (req == REQUEST_TYPE_INVALID) {
- // No active request — likely a stale STATUS call (e.g. new SETUP arrived
- // after the previous DATA stage but before usbd reached this point).
- // Suppress the complete event to avoid confusing the upper stack.
+ if (_dcd.ep0_state == EP0_STATE_IDLE) {
+ // Stale STATUS call (e.g. new SETUP arrived between DATA and STATUS).
TU_LOG1("Drop stale CONTROL_STAGE_ACK\r\n");
return true;
}
if (dir_in) {
- // STATUS IN of an OUT request: send ZLP IN with DATAEND so HW completes
- // the control transfer.
+ // STATUS IN (Write/zero-data req): send ZLP IN with DATAEND. The
+ // xfer_complete event fires from process_ep0 on the confirmation IRQ.
pipe0->buf = NULL;
pipe0->length = 0;
pipe0->remaining = 0;
+ _dcd.ep0_state = EP0_STATE_STATUS;
ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
} else {
- // STATUS OUT of an IN request: HW already auto-handled it via DATAEND on
- // the last DATA IN packet. Just fire the complete event.
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
+ // STATUS OUT (Read req): HW already auto-handled via DATAEND on the last
+ // DATA IN packet. Fire complete inline; the actual OUT-ZLP IRQ that
+ // follows is silently absorbed in process_ep0.
+ _dcd.ep0_state = EP0_STATE_IDLE;
dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, is_isr);
}
return true;
}
- // DATA phase. Direction must match the original request.
- TU_ASSERT(req != REQUEST_TYPE_INVALID && tu_edpt_dir(req) == dir_in);
+ // DATA phase — valid from SETUP_RECEIVED (first chunk / Write) or TX
+ // (subsequent Read chunk). Direction+length drives the next state.
+ TU_ASSERT(_dcd.ep0_state == EP0_STATE_SETUP_RECEIVED || _dcd.ep0_state == EP0_STATE_TX);
volatile void *fifo_ptr = &musb_regs->fifo[0];
if (dir_in) {
- // DATA IN: load FIFO, set TXRDY. Set DATAEND when this is a short packet
- // (USB short-packet rule => end of data stage). For multiple-of-EP0-size
- // data, usbd will follow with another DATA chunk or a STATUS request, and
- // the latter sends ZLP+DATAEND to terminate.
+ // DATA IN: load FIFO, set TXRDY. Add DATAEND for a short packet (ends
+ // the data stage per USB short-packet rule).
tu_hwfifo_write(fifo_ptr, buffer, total_bytes, NULL);
pipe0->buf = buffer + total_bytes;
pipe0->length = total_bytes;
pipe0->remaining = 0;
+ _dcd.ep0_state = EP0_STATE_TX;
ep_csr->csr0l = (total_bytes < CFG_TUD_ENDPOINT0_SIZE)
? (MUSB_CSRL0_TXRDY | MUSB_CSRL0_DATAEND)
: MUSB_CSRL0_TXRDY;
} else {
- // DATA OUT: arm to receive into buffer; ack to release the EP0 RX FIFO.
+ // DATA OUT: arm, ack RXRDY so host can send DATA OUT.
pipe0->buf = buffer;
pipe0->length = total_bytes;
pipe0->remaining = total_bytes;
+ _dcd.ep0_state = EP0_STATE_RX;
ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
}
return true;
}
+// 21.1.5: endpoint 0 service routine as peripheral. Drives the IDLE /
+// SETUP_RECEIVED / TX / RX / STATUS machine; direction on each IRQ is
+// implied by the state.
static void process_ep0(uint8_t rhport) {
musb_regs_t* musb_regs = MUSB_REGS(rhport);
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
pipe_state_t* pipe0 = pipe_get(0, TUSB_DIR_OUT);
uint_fast8_t csrl = ep_csr->csr0l;
- // 21.1.5: endpoint 0 service routine as peripheral
if (csrl & MUSB_CSRL0_STALLED) {
- /* Returned STALL packet to HOST. */
- ep_csr->csr0l = 0; /* Clear STALL */
+ ep_csr->csr0l = 0;
+ _dcd.ep0_state = EP0_STATE_IDLE;
return;
}
- unsigned req = _dcd.setup_packet.bmRequestType;
if (csrl & MUSB_CSRL0_SETEND) {
- // Host aborted the current control transfer (sent a new SETUP or premature STATUS in the middle of DATA stage
+ // Host aborted the current control transfer (new SETUP or premature STATUS).
ep_csr->csr0l = MUSB_CSRL0_SETENDC;
- if (req != REQUEST_TYPE_INVALID && pipe0->buf) {
- /* DATA stage was aborted by receiving STATUS or SETUP packet. */
+ if (_dcd.ep0_state == EP0_STATE_TX || _dcd.ep0_state == EP0_STATE_RX) {
+ const uint8_t dir_ep_addr = (_dcd.ep0_state == EP0_STATE_TX) ? TUSB_DIR_IN_MASK : 0;
pipe0->buf = NULL;
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
dcd_event_xfer_complete(rhport,
- req & TUSB_DIR_IN_MASK,
+ dir_ep_addr,
pipe0->length - pipe0->remaining,
XFER_RESULT_SUCCESS, true);
}
- req = REQUEST_TYPE_INVALID;
- if (!(csrl & MUSB_CSRL0_RXRDY)) return; /* Received SETUP packet */
+ _dcd.ep0_state = EP0_STATE_IDLE;
+ if (!(csrl & MUSB_CSRL0_RXRDY)) return; /* no SETUP waiting behind it */
}
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,);
- _dcd.setup_buffer[0] = musb_regs->fifo[0];
- _dcd.setup_buffer[1] = musb_regs->fifo[0];
- if (_dcd.setup_packet.wLength > 0 && tu_edpt_dir(_dcd.setup_packet.bmRequestType)) {
+ const uint16_t count0 = ep_csr->count0;
+
+ if (_dcd.ep0_state == EP0_STATE_IDLE) {
+ // SETUP token (count0 == 8). A count0 == 0 here would be a stray
+ // STATUS-OUT ZLP that bypassed the absorbing path below; silently ack.
+ if (count0 == 0) {
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
+ return;
+ }
+ TU_ASSERT(sizeof(tusb_control_request_t) == count0,);
+ union {
+ tusb_control_request_t req;
+ uint32_t u32[2];
+ } setup;
+ setup.u32[0] = musb_regs->fifo[0];
+ setup.u32[1] = musb_regs->fifo[0];
+ _dcd.ep0_state = EP0_STATE_SETUP_RECEIVED;
+ // Ack RXRDY now for Read requests so host can start sending IN tokens.
+ // Write / zero-data leave it set — HW NAKs OUT tokens until edpt0_xfer
+ // (OUT or STATUS IN) clears it.
+ if (setup.req.wLength > 0 && tu_edpt_dir(setup.req.bmRequestType)) {
ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
}
- dcd_event_setup_received(rhport, (const uint8_t*)(uintptr_t)&_dcd.setup_packet, true);
+ dcd_event_setup_received(rhport, (const uint8_t*)&setup.req, true);
return;
}
- if (pipe0->buf) {
- /* DATA OUT: pipe0 must be armed by the prior edpt0_xfer(OUT). The host
- * cannot send DATA OUT until that call clears the SETUP-stage RXRDY, so
- * armed is guaranteed true here. */
- const uint16_t count0 = ep_csr->count0;
+ if (_dcd.ep0_state == EP0_STATE_RX) {
+ /* DATA OUT: drain armed buffer, complete, return to SETUP_RECEIVED for STATUS call. */
const uint16_t len = tu_min16(tu_min16(pipe0->remaining, 64), count0);
if (len) {
tu_hwfifo_read(&musb_regs->fifo[0], pipe0->buf, len, NULL);
pipe0->remaining -= len;
}
pipe0->buf = NULL;
+ _dcd.ep0_state = EP0_STATE_SETUP_RECEIVED;
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
dcd_event_xfer_complete(rhport,
tu_edpt_addr(0, TUSB_DIR_OUT),
pipe0->length - pipe0->remaining,
XFER_RESULT_SUCCESS, true);
+ return;
+ }
+
+ // State SETUP_RECEIVED or TX with count0 == 0: stray STATUS-OUT ZLP for
+ // a Read request whose inline complete already dropped state to IDLE.
+ if (count0 == 0) {
+ ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
}
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 */
- 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;
+ /* CSR0L == 0: TXRDY cleared (data sent) or STATUS confirmation. */
+ if (_dcd.ep0_state == EP0_STATE_STATUS) {
+ // STATUS IN confirmed by host's ACK of our IN-ZLP.
+ if (_dcd.pending_addr) {
+ musb_regs->faddr = _dcd.pending_addr;
+ _dcd.pending_addr = 0;
}
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
+ _dcd.ep0_state = EP0_STATE_IDLE;
dcd_event_xfer_complete(rhport,
tu_edpt_addr(0, TUSB_DIR_IN),
- pipe0->length - pipe0->remaining,
- XFER_RESULT_SUCCESS, true);
+ 0, XFER_RESULT_SUCCESS, true);
return;
}
- if (pipe0->buf) {
- /* DATA IN */
+
+ if (_dcd.ep0_state == EP0_STATE_TX) {
+ /* DATA IN packet sent. For short packets DATAEND was set; the STATUS-OUT
+ * ZLP IRQ that follows lands in the count0==0 branch above. Return to
+ * SETUP_RECEIVED so usbd can post the next chunk or the STATUS call. */
pipe0->buf = NULL;
+ _dcd.ep0_state = EP0_STATE_SETUP_RECEIVED;
dcd_event_xfer_complete(rhport,
tu_edpt_addr(0, TUSB_DIR_IN),
pipe0->length - pipe0->remaining,
@@ -525,8 +542,7 @@ static void process_bus_reset(uint8_t rhport) {
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.ep0_state = EP0_STATE_IDLE;
/* When EP0 pipe buf has not NULL, DATA stage works in progress. */
pipe_state_t* pipe0 = pipe_get(0, TUSB_DIR_OUT);
pipe0->buf = NULL;
@@ -591,18 +607,21 @@ 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 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);
pipe_state_t* pipe0 = pipe_get(0, TUSB_DIR_OUT);
+ _dcd.pending_addr = dev_addr;
pipe0->buf = NULL;
pipe0->length = 0;
pipe0->remaining = 0;
- /* Clear RX FIFO to return ACK. */
+ _dcd.ep0_state = EP0_STATE_STATUS;
+ /* Send STATUS IN ZLP with DATAEND; host ACK fires the confirmation IRQ. */
ep_csr->csr0l = MUSB_CSRL0_RXRDYC | MUSB_CSRL0_DATAEND;
}
@@ -803,7 +822,7 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
if (0 == epn) {
if (!ep_addr) { /* Ignore EP80 */
- _dcd.setup_packet.bmRequestType = REQUEST_TYPE_INVALID;
+ _dcd.ep0_state = EP0_STATE_IDLE;
pipe_state_t* pipe0 = pipe_get(0, TUSB_DIR_OUT);
pipe0->buf = NULL;
ep_csr->csr0l = MUSB_CSRL0_STALL;