Skip to content

Commit c4cd6c8

Browse files
committed
dcd/musb: defer EP0 SETUP during DATA_IN/STATUS race
Handle cases where a new SETUP arrives before the previous control transfer fully completes by buffering the SETUP and replaying it after status completion. Split EP0 DATA state into DATA_IN/DATA_OUT and finalize pending status-out completion before processing deferred SETUP. Signed-off-by: HiFiPhile <admin@hifiphile.com>
1 parent e1cb1b5 commit c4cd6c8

3 files changed

Lines changed: 80 additions & 26 deletions

File tree

src/portable/mentor/musb/dcd_musb.c

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ typedef struct {
8282

8383
enum {
8484
PIPE0_STATE_IDLE = 0, // no active control transfer
85-
PIPE0_STATE_DATA, // DATA stage (IN or OUT — direction implied by CSR/dir)
85+
PIPE0_STATE_DATA_IN, // DATA IN stage
86+
PIPE0_STATE_DATA_OUT, // DATA OUT stage
8687
PIPE0_STATE_STATUS_IN, // STATUS IN — device sends IN-ZLP; awaits send-ACK IRQ
8788
PIPE0_STATE_STATUS_OUT, // post-DATAEND, neither edpt0_xfer(STATUS OUT) nor confirmation IRQ has happened yet
8889
PIPE0_STATE_STATUS_OUT_PENDING, // one of {edpt0_xfer(STATUS OUT), confirmation IRQ} has happened; the other fires xfer_complete
@@ -95,12 +96,41 @@ typedef struct {
9596
uint16_t remain_wlength; // bytes remaining in the control transfer's DATA stage
9697
uint8_t state;
9798
uint8_t pending_addr; // new USB address latched by dcd_set_address; applied when STATUS IN completes
99+
tusb_control_request_t deferred_setup;
100+
bool deferred_setup_valid;
98101
} pipe0;
99102
pipe_state_t pipe[MUSB_PIPE_COUNT];
100103
} dcd_data_t;
101104

102105
static dcd_data_t _dcd;
103106

107+
static void pipe0_start_setup(uint8_t rhport, musb_ep_csr_t* ep_csr,
108+
tusb_control_request_t const* req, bool is_isr) {
109+
_dcd.pipe0.remain_wlength = req->wLength;
110+
111+
if (req->wLength == 0) {
112+
_dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
113+
} else {
114+
if (req->bmRequestType & TUSB_DIR_IN_MASK) {
115+
_dcd.pipe0.state = PIPE0_STATE_DATA_IN;
116+
ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
117+
} else {
118+
_dcd.pipe0.state = PIPE0_STATE_DATA_OUT;
119+
}
120+
}
121+
122+
dcd_event_setup_received(rhport, (const uint8_t *) req, is_isr);
123+
}
124+
125+
static void pipe0_process_deferred_setup(uint8_t rhport, musb_ep_csr_t* ep_csr, bool is_isr) {
126+
if (!_dcd.pipe0.deferred_setup_valid) {
127+
return;
128+
}
129+
130+
_dcd.pipe0.deferred_setup_valid = false;
131+
pipe0_start_setup(rhport, ep_csr, &_dcd.pipe0.deferred_setup, is_isr);
132+
}
133+
104134
// EP0 must not call this — it has its own scalars in dcd_data_t.
105135
TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_dir_t epdir) {
106136
size_t idx = epnum - 1u;
@@ -323,7 +353,7 @@ static void process_epout(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epnum,
323353

324354
static bool edpt_n_xfer(uint8_t rhport, uint8_t ep_addr, void *buffer, uint16_t total_bytes, bool use_fifo, bool is_isr) {
325355
const uint8_t epnum = tu_edpt_number(ep_addr);
326-
const unsigned dir_in = tu_edpt_dir(ep_addr);
356+
const tusb_dir_t dir_in = tu_edpt_dir(ep_addr);
327357

328358
pipe_state_t *pipe = pipe_get(epnum, dir_in);
329359
if (use_fifo) {
@@ -361,7 +391,8 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_
361391
const unsigned dir_in = tu_edpt_dir(ep_addr);
362392

363393
switch (_dcd.pipe0.state) {
364-
case PIPE0_STATE_DATA: {
394+
case PIPE0_STATE_DATA_IN:
395+
case PIPE0_STATE_DATA_OUT: {
365396
_dcd.pipe0.xact_len = total_bytes;
366397
if (dir_in) {
367398
// DATA IN: load FIFO, set TXRDY. Add DATAEND on the last chunk
@@ -396,6 +427,7 @@ static bool edpt0_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_
396427
// Second event — IRQ already arrived, fire complete now.
397428
_dcd.pipe0.state = PIPE0_STATE_IDLE;
398429
dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, is_isr);
430+
pipe0_process_deferred_setup(rhport, ep_csr, is_isr);
399431
break;
400432

401433
default: break;
@@ -410,9 +442,14 @@ static void process_ep0(uint8_t rhport) {
410442
musb_ep_csr_t* ep_csr = get_ep_csr(musb_regs, 0);
411443
uint_fast8_t csrl = ep_csr->csr0l;
412444

445+
if (csrl & MUSB_CSRL0_DATAEND) {
446+
return;
447+
}
448+
413449
if (csrl & MUSB_CSRL0_STALLED) {
414450
ep_csr->csr0l = 0;
415451
_dcd.pipe0.state = PIPE0_STATE_IDLE;
452+
_dcd.pipe0.deferred_setup_valid = false;
416453
return;
417454
}
418455

@@ -421,6 +458,7 @@ static void process_ep0(uint8_t rhport) {
421458
// do nothing, it is probably another setup packet, usbd will reset its state.
422459
ep_csr->csr0l = MUSB_CSRL0_SETENDC;
423460
_dcd.pipe0.state = PIPE0_STATE_IDLE;
461+
_dcd.pipe0.deferred_setup_valid = false;
424462
if (!(csrl & MUSB_CSRL0_RXRDY)) {
425463
return; /* no SETUP waiting behind it */
426464
}
@@ -430,30 +468,19 @@ static void process_ep0(uint8_t rhport) {
430468
if (csrl & MUSB_CSRL0_RXRDY) {
431469
const uint16_t count0 = ep_csr->count0;
432470
switch (_dcd.pipe0.state) {
433-
case PIPE0_STATE_IDLE:
471+
case PIPE0_STATE_IDLE: {
434472
TU_ASSERT(sizeof(tusb_control_request_t) == count0, );
435473
union {
436474
tusb_control_request_t req;
437475
uint32_t u32[2];
438476
} setup_packet;
439477
setup_packet.u32[0] = musb_regs->fifo[0];
440478
setup_packet.u32[1] = musb_regs->fifo[0];
441-
442-
_dcd.pipe0.remain_wlength = setup_packet.req.wLength;
443-
444-
if (setup_packet.req.wLength == 0) {
445-
_dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
446-
} else {
447-
_dcd.pipe0.state = PIPE0_STATE_DATA;
448-
// If OUT (rx) direction, let edpt0_xfer() clear RXRDY when it's ready to receive data.
449-
if (setup_packet.req.bmRequestType & TUSB_DIR_IN_MASK) {
450-
ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
451-
}
452-
}
453-
dcd_event_setup_received(rhport, (const uint8_t *)&setup_packet.req, true);
479+
pipe0_start_setup(rhport, ep_csr, &setup_packet.req, true);
454480
break;
481+
}
455482

456-
case PIPE0_STATE_DATA: {
483+
case PIPE0_STATE_DATA_OUT: {
457484
// EP0 OUT is single-packet (TU_ASSERT total_bytes <= EP0_SIZE in edpt0_xfer)
458485
// so the whole packet drains in one shot.
459486
if (count0) {
@@ -463,31 +490,54 @@ static void process_ep0(uint8_t rhport) {
463490
if (_dcd.pipe0.remain_wlength == 0) {
464491
// last packet: change state and leave RXRDY for edpt0_xfer(STATUS IN) to ack
465492
_dcd.pipe0.state = PIPE0_STATE_STATUS_IN;
466-
} else {
467-
ep_csr->csr0l = MUSB_CSRL0_RXRDYC;
468493
}
469494
dcd_event_xfer_complete(rhport, TU_EP0_OUT, count0, XFER_RESULT_SUCCESS, true);
470495
break;
471496
}
472497

473-
default: break;
498+
// New SETUP packet arrived while old control transfer is not finished yet. This could happen in following scenarios:
499+
// - Status IN/OUT finished, IRQ and new setup packet IRQ arrive at the same time.
500+
// - Data IN finished and status OUT is received, both IRQs and new setup packet IRQ arrive at the same time.
501+
// could happen when CPU load is high, save the new setup packet for later processing after current status stage complete.
502+
case PIPE0_STATE_STATUS_OUT:
503+
case PIPE0_STATE_STATUS_OUT_PENDING:
504+
case PIPE0_STATE_STATUS_IN:
505+
case PIPE0_STATE_DATA_IN: {
506+
TU_ASSERT(sizeof(tusb_control_request_t) == count0, );
507+
union {
508+
tusb_control_request_t req;
509+
uint32_t u32[2];
510+
} setup_packet;
511+
setup_packet.u32[0] = musb_regs->fifo[0];
512+
setup_packet.u32[1] = musb_regs->fifo[0];
513+
514+
_dcd.pipe0.deferred_setup = setup_packet.req;
515+
_dcd.pipe0.deferred_setup_valid = true;
516+
goto process_status;
517+
}
474518
}
475519

476520
return;
477521
}
478522

523+
process_status:
479524
/* When CSRL0 is zero, it means that either
480525
* - completion of sending any length packet TxPktRdy clear
481526
* - or status stage is complete (ZLP) after DataEnd is set */
482527
switch (_dcd.pipe0.state) {
483-
case PIPE0_STATE_DATA:
528+
case PIPE0_STATE_DATA_IN:
484529
// csrl == 0 in DATA state = TXRDY just cleared, i.e. a DATA IN packet was successfully sent. If the just-sent
485530
// packet was the last (DATAEND was set when ep0_remain_datalen hit zero), transition
486531
// to STATUS_OUT to await the host's STATUS-OUT ZLP confirmation IRQ.
487532
if (_dcd.pipe0.remain_wlength == 0) {
488533
_dcd.pipe0.state = PIPE0_STATE_STATUS_OUT;
534+
// If a new SETUP was deferred then STATUS OUT IRQ is missed, manually transition to STATUS_OUT_PENDING to allow ep0_xfer(STATUS OUT) to fire complete immediately.
535+
if (_dcd.pipe0.deferred_setup_valid) {
536+
_dcd.pipe0.state = PIPE0_STATE_STATUS_OUT_PENDING;
537+
}
489538
}
490539
dcd_event_xfer_complete(rhport, TU_EP0_IN, _dcd.pipe0.xact_len, XFER_RESULT_SUCCESS, true);
540+
491541
break;
492542

493543
case PIPE0_STATE_STATUS_OUT:
@@ -499,6 +549,7 @@ static void process_ep0(uint8_t rhport) {
499549
// Second event — edpt0_xfer(STATUS OUT) already called, fire complete now.
500550
_dcd.pipe0.state = PIPE0_STATE_IDLE;
501551
dcd_event_xfer_complete(rhport, TU_EP0_OUT, 0, XFER_RESULT_SUCCESS, true);
552+
pipe0_process_deferred_setup(rhport, ep_csr, true);
502553
break;
503554

504555
case PIPE0_STATE_STATUS_IN:
@@ -508,6 +559,7 @@ static void process_ep0(uint8_t rhport) {
508559
}
509560
_dcd.pipe0.state = PIPE0_STATE_IDLE;
510561
dcd_event_xfer_complete(rhport, TU_EP0_IN, 0, XFER_RESULT_SUCCESS, true);
562+
pipe0_process_deferred_setup(rhport, ep_csr, true);
511563
break;
512564

513565
default: break;
@@ -527,6 +579,7 @@ static void process_bus_reset(uint8_t rhport) {
527579
_dcd.pipe0.buf = NULL;
528580
_dcd.pipe0.xact_len = 0;
529581
_dcd.pipe0.remain_wlength = 0;
582+
_dcd.pipe0.deferred_setup_valid = false;
530583

531584
musb->intr_txen = 1; /* Enable only EP0 */
532585
musb->intr_rxen = 0;
@@ -646,7 +699,7 @@ void dcd_sof_enable(uint8_t rhport, bool en)
646699
bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) {
647700
const unsigned ep_addr = ep_desc->bEndpointAddress;
648701
const unsigned epn = tu_edpt_number(ep_addr);
649-
const unsigned epdir = tu_edpt_dir(ep_addr);
702+
const tusb_dir_t epdir = tu_edpt_dir(ep_addr);
650703
const unsigned mps = tu_edpt_packet_size(ep_desc);
651704

652705
pipe_state_t *pipe = pipe_get(epn, epdir);
@@ -689,7 +742,7 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet
689742
bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) {
690743
const unsigned ep_addr = ep_desc->bEndpointAddress;
691744
const unsigned epn = tu_edpt_number(ep_addr);
692-
const unsigned dir_in = tu_edpt_dir(ep_addr);
745+
const tusb_dir_t dir_in = tu_edpt_dir(ep_addr);
693746
const unsigned mps = tu_edpt_packet_size(ep_desc);
694747

695748
unsigned const ie = musb_dcd_get_int_enable(rhport);
@@ -804,6 +857,7 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) {
804857
if (ep_addr == TU_EP0_OUT) { /* Ignore EP0 OUT */
805858
_dcd.pipe0.state = PIPE0_STATE_IDLE;
806859
_dcd.pipe0.buf = NULL;
860+
_dcd.pipe0.deferred_setup_valid = false;
807861
ep_csr->csr0l = MUSB_CSRL0_STALL;
808862
}
809863
} else {

src/portable/mentor/musb/musb_max32.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ extern "C" {
4747
#define MUSB_CFG_SHARED_FIFO 1 // shared FIFO for TX and RX endpoints
4848
#define MUSB_CFG_DYNAMIC_FIFO 0 // dynamic EP FIFO sizing
4949

50-
const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS };
50+
static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS };
5151

5252
#if CFG_TUD_ENABLED
5353
#define USBHS_M31_CLOCK_RECOVERY

src/portable/mentor/musb/musb_ti.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
#define MUSB_CFG_DYNAMIC_FIFO 1
5050
#define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096
5151

52-
const uintptr_t MUSB_BASES[] = { USB0_BASE };
52+
static const uintptr_t MUSB_BASES[] = { USB0_BASE };
5353

5454
// Header supports both device and host modes. Only include what's necessary
5555
#if CFG_TUD_ENABLED

0 commit comments

Comments
 (0)