Skip to content

Commit 1a38d65

Browse files
committed
fix nrf easy dma race condition
1 parent c49197a commit 1a38d65

File tree

2 files changed

+70
-66
lines changed

2 files changed

+70
-66
lines changed

src/class/cdc/cdc_device.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef struct
8080
//--------------------------------------------------------------------+
8181
CFG_TUSB_MEM_SECTION static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC];
8282

83-
static void _prep_out_transaction (cdcd_interface_t* p_cdc)
83+
static bool _prep_out_transaction (cdcd_interface_t* p_cdc)
8484
{
8585
uint8_t const rhport = TUD_OPT_RHPORT;
8686
uint16_t available = tu_fifo_remaining(&p_cdc->rx_ff);
@@ -89,21 +89,23 @@ static void _prep_out_transaction (cdcd_interface_t* p_cdc)
8989
// TODO Actually we can still carry out the transfer, keeping count of received bytes
9090
// and slowly move it to the FIFO when read().
9191
// This pre-check reduces endpoint claiming
92-
TU_VERIFY(available >= sizeof(p_cdc->epout_buf), );
92+
TU_VERIFY(available >= sizeof(p_cdc->epout_buf));
9393

9494
// claim endpoint
95-
TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out), );
95+
TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out));
9696

9797
// fifo can be changed before endpoint is claimed
9898
available = tu_fifo_remaining(&p_cdc->rx_ff);
9999

100100
if ( available >= sizeof(p_cdc->epout_buf) )
101101
{
102-
usbd_edpt_xfer(rhport, p_cdc->ep_out, p_cdc->epout_buf, sizeof(p_cdc->epout_buf));
102+
return usbd_edpt_xfer(rhport, p_cdc->ep_out, p_cdc->epout_buf, sizeof(p_cdc->epout_buf));
103103
}else
104104
{
105105
// Release endpoint since we don't make any transfer
106106
usbd_edpt_release(rhport, p_cdc->ep_out);
107+
108+
return false;
107109
}
108110
}
109111

src/portable/nordic/nrf5x/dcd_nrf5x.c

Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ typedef struct
6363
uint8_t* buffer;
6464
uint16_t total_len;
6565
volatile uint16_t actual_len;
66-
uint16_t mps; // max packet size
66+
uint16_t mps; // max packet size
6767

6868
// nRF will auto accept OUT packet after DMA is done
6969
// indicate packet is already ACK
@@ -84,8 +84,6 @@ static struct
8484

8585
// Number of pending DMA that is started but not handled yet by dcd_int_handler().
8686
// Since nRF can only carry one DMA can run at a time, this value is normally be either 0 or 1.
87-
// However, in critical section with interrupt disabled, the DMA can be finished and added up
88-
// until handled by dcd_int_handler() when exiting critical section.
8987
volatile uint8_t dma_pending;
9088
}_dcd;
9189

@@ -115,67 +113,68 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void)
115113
}
116114

117115
// helper to start DMA
116+
static void start_dma(volatile uint32_t* reg_startep)
117+
{
118+
_dcd.dma_pending = true;
119+
120+
(*reg_startep) = 1;
121+
__ISB(); __DSB();
122+
123+
// TASKS_EP0STATUS, TASKS_EP0RCVOUT seem to need EasyDMA to be available
124+
// However these don't trigger any DMA transfer and got ENDED event subsequently
125+
// Therefore dma_pending is corrected right away
126+
if ( (reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT) )
127+
{
128+
_dcd.dma_pending = false;
129+
}
130+
}
131+
132+
// only 1 EasyDMA can be active at any time
118133
// TODO use Cortex M4 LDREX and STREX command (atomic) to have better mutex access to EasyDMA
119134
// since current implementation does not 100% guarded against race condition
120135
static void edpt_dma_start(volatile uint32_t* reg_startep)
121136
{
122-
// Only one dma can be active
123-
if ( _dcd.dma_pending )
137+
// Called in critical section i.e within USB ISR, or USB/Global interrupt disabled
138+
if ( is_in_isr() || __get_PRIMASK() || !NVIC_GetEnableIRQ(USBD_IRQn) )
124139
{
125-
if (is_in_isr())
140+
if (_dcd.dma_pending)
126141
{
127-
// Called within ISR, use usbd task to defer later
142+
//use usbd task to defer later
128143
usbd_defer_func((osal_task_func_t) edpt_dma_start, (void*) (uintptr_t) reg_startep, true);
129-
return;
144+
}else
145+
{
146+
start_dma(reg_startep);
130147
}
131-
else
148+
}else
149+
{
150+
// Called in non-critical thread-mode, should be 99% of the time.
151+
// Should be safe to blocking wait until previous DMA transfer complete
152+
uint8_t const rhport = 0;
153+
bool started = false;
154+
while(!started)
132155
{
133-
if ( __get_PRIMASK() || !NVIC_GetEnableIRQ(USBD_IRQn) )
134-
{
135-
// Called in critical section with interrupt disabled. We have to manually check
136-
// for the DMA complete by comparing current pending DMA with number of ENDED Events
137-
uint32_t ended = 0;
156+
// LDREX/STREX may be needed in form of std atomic (required C11) or
157+
// use osal mutex to guard against multiple core MCUs such as nRF53
158+
dcd_int_disable(rhport);
138159

139-
while ( _dcd.dma_pending > ((uint8_t) ended) )
140-
{
141-
ended = NRF_USBD->EVENTS_ENDISOIN + NRF_USBD->EVENTS_ENDISOOUT;
142-
143-
for (uint8_t i=0; i<EP_CBI_COUNT; i++)
144-
{
145-
ended += NRF_USBD->EVENTS_ENDEPIN[i] + NRF_USBD->EVENTS_ENDEPOUT[i];
146-
}
147-
}
148-
}else
160+
if ( !_dcd.dma_pending )
149161
{
150-
// Called in non-critical thread-mode, should be 99% of the time.
151-
// Should be safe to blocking wait until previous DMA transfer complete
152-
while ( _dcd.dma_pending ) { }
162+
start_dma(reg_startep);
163+
started = true;
153164
}
154-
}
155-
}
156165

157-
_dcd.dma_pending++;
166+
dcd_int_enable(rhport);
158167

159-
(*reg_startep) = 1;
160-
__ISB(); __DSB();
168+
// osal_yield();
169+
}
170+
}
161171
}
162172

163173
// DMA is complete
164174
static void edpt_dma_end(void)
165175
{
166176
TU_ASSERT(_dcd.dma_pending, );
167-
_dcd.dma_pending = 0;
168-
}
169-
170-
// helper to set TASKS_EP0STATUS / TASKS_EP0RCVOUT since they also need EasyDMA
171-
// However TASKS_EP0STATUS doesn't trigger any DMA transfer and got ENDED event subsequently
172-
// Therefore dma_running state will be corrected right away
173-
void start_ep0_task(volatile uint32_t* reg_task)
174-
{
175-
edpt_dma_start(reg_task);
176-
177-
// correct the dma_running++ in dma start
178-
if (_dcd.dma_pending) _dcd.dma_pending--;
177+
_dcd.dma_pending = false;
179178
}
180179

181180
// helper getting td
@@ -194,7 +193,10 @@ static void xact_out_dma(uint8_t epnum)
194193
{
195194
xact_len = NRF_USBD->SIZE.ISOOUT;
196195
// If ZERO bit is set, ignore ISOOUT length
197-
if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk) xact_len = 0;
196+
if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk)
197+
{
198+
xact_len = 0;
199+
}
198200
else
199201
{
200202
// Trigger DMA move data from Endpoint -> SRAM
@@ -216,8 +218,8 @@ static void xact_out_dma(uint8_t epnum)
216218
edpt_dma_start(&NRF_USBD->TASKS_STARTEPOUT[epnum]);
217219
}
218220

219-
xfer->buffer += xact_len;
220-
xfer->actual_len += xact_len;
221+
// xfer->buffer += xact_len;
222+
// xfer->actual_len += xact_len;
221223
}
222224

223225
// Prepare for a CBI transaction IN, call at the start
@@ -232,7 +234,7 @@ static void xact_in_dma(uint8_t epnum)
232234
NRF_USBD->EPIN[epnum].PTR = (uint32_t) xfer->buffer;
233235
NRF_USBD->EPIN[epnum].MAXCNT = xact_len;
234236

235-
xfer->buffer += xact_len;
237+
//xfer->buffer += xact_len;
236238

237239
edpt_dma_start(&NRF_USBD->TASKS_STARTEPIN[epnum]);
238240
}
@@ -242,6 +244,7 @@ static void xact_in_dma(uint8_t epnum)
242244
//--------------------------------------------------------------------+
243245
void dcd_init (uint8_t rhport)
244246
{
247+
TU_LOG1("dcd init\r\n");
245248
(void) rhport;
246249
}
247250

@@ -466,7 +469,7 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
466469
if ( control_status )
467470
{
468471
// Status Phase also requires EasyDMA has to be available as well !!!!
469-
start_ep0_task(&NRF_USBD->TASKS_EP0STATUS);
472+
edpt_dma_start(&NRF_USBD->TASKS_EP0STATUS);
470473

471474
// The nRF doesn't interrupt on status transmit so we queue up a success response.
472475
dcd_event_xfer_complete(0, ep_addr, 0, XFER_RESULT_SUCCESS, is_in_isr());
@@ -476,7 +479,7 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
476479
if ( epnum == 0 )
477480
{
478481
// Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA
479-
start_ep0_task(&NRF_USBD->TASKS_EP0RCVOUT);
482+
edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT);
480483
}else
481484
{
482485
if ( xfer->data_received )
@@ -522,7 +525,6 @@ void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr)
522525
// There maybe data in endpoint fifo already, we need to pull it out
523526
if ( (dir == TUSB_DIR_OUT) && xfer->data_received )
524527
{
525-
TU_LOG_LOCATION();
526528
xfer->data_received = false;
527529
xact_out_dma(epnum);
528530
}
@@ -717,7 +719,8 @@ void dcd_int_handler(uint8_t rhport)
717719

718720
if ( int_status & EDPT_END_ALL_MASK )
719721
{
720-
// DMA complete move data from SRAM -> Endpoint
722+
// DMA complete move data from SRAM <-> Endpoint
723+
// Must before endpoint transfer handling
721724
edpt_dma_end();
722725
}
723726

@@ -732,7 +735,7 @@ void dcd_int_handler(uint8_t rhport)
732735
* - Host -> Endpoint
733736
* EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPOUT[i]
734737
* to start DMA. For Bulk/Interrupt, this step can occur automatically (without sw),
735-
* which means data may or may not be ready (data_received flag).
738+
* which means data may or may not be ready (out_received flag).
736739
* - Endpoint -> RAM
737740
* ENDEPOUT[i] interrupted, transaction complete, sw prepare next transaction
738741
*
@@ -764,20 +767,16 @@ void dcd_int_handler(uint8_t rhport)
764767
xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT);
765768
uint8_t const xact_len = NRF_USBD->EPOUT[epnum].AMOUNT;
766769

770+
xfer->buffer += xact_len;
771+
xfer->actual_len += xact_len;
772+
767773
// Transfer complete if transaction len < Max Packet Size or total len is transferred
768774
if ( (epnum != EP_ISO_NUM) && (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len) )
769775
{
770776
if ( epnum == 0 )
771777
{
772778
// Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA
773-
if ( _dcd.dma_pending )
774-
{
775-
// use usbd task to defer later
776-
usbd_defer_func((osal_task_func_t) start_ep0_task, (void*) (uintptr_t) &NRF_USBD->TASKS_EP0RCVOUT, true);
777-
}else
778-
{
779-
start_ep0_task(&NRF_USBD->TASKS_EP0RCVOUT);
780-
}
779+
edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT);
781780
}else
782781
{
783782
// nRF auto accept next Bulk/Interrupt OUT packet
@@ -814,8 +813,10 @@ void dcd_int_handler(uint8_t rhport)
814813
if ( tu_bit_test(data_status, epnum) || (epnum == 0 && is_control_in) )
815814
{
816815
xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN);
816+
uint8_t const xact_len = NRF_USBD->EPIN[epnum].AMOUNT; // MAXCNT
817817

818-
xfer->actual_len += NRF_USBD->EPIN[epnum].MAXCNT;
818+
xfer->buffer += xact_len;
819+
xfer->actual_len += xact_len;
819820

820821
if ( xfer->actual_len < xfer->total_len )
821822
{
@@ -842,6 +843,7 @@ void dcd_int_handler(uint8_t rhport)
842843
}else
843844
{
844845
// Data overflow !!! Nah, nRF will auto accept next Bulk/Interrupt OUT packet
846+
// If USBD is already queued this
845847
// Mark this endpoint with data received
846848
xfer->data_received = true;
847849
}

0 commit comments

Comments
 (0)