Skip to content

Commit 585f37a

Browse files
authored
Merge pull request hathach#1489 from kasjer/kasjer/fix-nrf5x-dma-access
nrf5x: Fix DMA access
2 parents 8a28e7c + 8b37aa1 commit 585f37a

File tree

1 file changed

+37
-56
lines changed

1 file changed

+37
-56
lines changed

src/portable/nordic/nrf5x/dcd_nrf5x.c

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X
3030

31+
#include <stdatomic.h>
3132
#include "nrf.h"
3233
#include "nrf_clock.h"
3334
#include "nrf_power.h"
@@ -72,16 +73,14 @@ typedef struct
7273
// nRF will auto accept OUT packet after DMA is done
7374
// indicate packet is already ACK
7475
volatile bool data_received;
76+
volatile bool started;
7577

7678
// Set to true when data was transferred from RAM to ISO IN output buffer.
7779
// New data can be put in ISO IN output buffer after SOF.
7880
bool iso_in_transfer_ready;
7981

8082
} xfer_td_t;
8183

82-
static osal_mutex_def_t dcd_mutex_def;
83-
static osal_mutex_t dcd_mutex;
84-
8584
// Data for managing dcd
8685
static struct
8786
{
@@ -90,7 +89,7 @@ static struct
9089
xfer_td_t xfer[EP_CBI_COUNT + 1][2];
9190

9291
// nRF can only carry one DMA at a time, this is used to guard the access to EasyDMA
93-
volatile bool dma_running;
92+
atomic_bool dma_running;
9493
}_dcd;
9594

9695
/*------------------------------------------------------------------*/
@@ -121,8 +120,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void)
121120
// helper to start DMA
122121
static void start_dma(volatile uint32_t* reg_startep)
123122
{
124-
_dcd.dma_running = true;
125-
126123
(*reg_startep) = 1;
127124
__ISB(); __DSB();
128125

@@ -131,58 +128,26 @@ static void start_dma(volatile uint32_t* reg_startep)
131128
// Therefore dma_pending is corrected right away
132129
if ( (reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT) )
133130
{
134-
_dcd.dma_running = false;
131+
atomic_flag_clear(&_dcd.dma_running);
135132
}
136133
}
137134

138-
// only 1 EasyDMA can be active at any time
139-
// TODO use Cortex M4 LDREX and STREX command (atomic) to have better mutex access to EasyDMA
140-
// since current implementation does not 100% guarded against race condition
141135
static void edpt_dma_start(volatile uint32_t* reg_startep)
142136
{
143-
// Called in critical section i.e within USB ISR, or USB/Global interrupt disabled
144-
if ( is_in_isr() || __get_PRIMASK() || !NVIC_GetEnableIRQ(USBD_IRQn) )
137+
if ( atomic_flag_test_and_set(&_dcd.dma_running) )
145138
{
146-
if (_dcd.dma_running)
147-
{
148-
//use usbd task to defer later
149-
usbd_defer_func((osal_task_func_t) edpt_dma_start, (void*) (uintptr_t) reg_startep, true);
150-
}else
151-
{
152-
start_dma(reg_startep);
153-
}
139+
usbd_defer_func((osal_task_func_t) edpt_dma_start, (void*) (uintptr_t) reg_startep, true);
154140
}else
155141
{
156-
// Called in non-critical thread-mode, should be 99% of the time.
157-
// Should be safe to blocking wait until previous DMA transfer complete
158-
uint8_t const rhport = 0;
159-
bool started = false;
160-
osal_mutex_lock(dcd_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
161-
while(!started)
162-
{
163-
// LDREX/STREX may be needed in form of std atomic (required C11) or
164-
// use osal mutex to guard against multiple core MCUs such as nRF53
165-
dcd_int_disable(rhport);
166-
167-
if ( !_dcd.dma_running )
168-
{
169-
start_dma(reg_startep);
170-
started = true;
171-
}
172-
173-
dcd_int_enable(rhport);
174-
175-
// osal_yield();
176-
}
177-
osal_mutex_unlock(dcd_mutex);
142+
start_dma(reg_startep);
178143
}
179144
}
180145

181146
// DMA is complete
182147
static void edpt_dma_end(void)
183148
{
184149
TU_ASSERT(_dcd.dma_running, );
185-
_dcd.dma_running = false;
150+
atomic_flag_clear(&_dcd.dma_running);
186151
}
187152

188153
// helper getting td
@@ -191,27 +156,42 @@ static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir)
191156
return &_dcd.xfer[epnum][dir];
192157
}
193158

159+
static void xact_out_dma(uint8_t epnum);
160+
// Function wraps xact_out_dma which wants uint8_t while usbd_defer_func wants void (*)(void *)
161+
static void xact_out_dma_wrapper(void *epnum)
162+
{
163+
xact_out_dma((uint8_t)((uintptr_t)epnum));
164+
}
165+
194166
// Start DMA to move data from Endpoint -> RAM
195167
static void xact_out_dma(uint8_t epnum)
196168
{
197169
xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT);
198170
uint32_t xact_len;
199171

172+
// DMA can't be active during read of SIZE.EPOUT or SIZE.ISOOUT, so try to lock,
173+
// If already running deffer call regardless if it was called from ISR or task,
174+
if ( atomic_flag_test_and_set(&_dcd.dma_running) )
175+
{
176+
usbd_defer_func((osal_task_func_t)xact_out_dma_wrapper, (void *)(uint32_t)epnum, is_in_isr());
177+
return;
178+
}
200179
if (epnum == EP_ISO_NUM)
201180
{
202181
xact_len = NRF_USBD->SIZE.ISOOUT;
203182
// If ZERO bit is set, ignore ISOOUT length
204183
if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk)
205184
{
206185
xact_len = 0;
186+
atomic_flag_clear(&_dcd.dma_running);
207187
}
208188
else
209189
{
210190
// Trigger DMA move data from Endpoint -> SRAM
211191
NRF_USBD->ISOOUT.PTR = (uint32_t) xfer->buffer;
212192
NRF_USBD->ISOOUT.MAXCNT = xact_len;
213193

214-
edpt_dma_start(&NRF_USBD->TASKS_STARTISOOUT);
194+
start_dma(&NRF_USBD->TASKS_STARTISOOUT);
215195
}
216196
}
217197
else
@@ -223,7 +203,7 @@ static void xact_out_dma(uint8_t epnum)
223203
NRF_USBD->EPOUT[epnum].PTR = (uint32_t) xfer->buffer;
224204
NRF_USBD->EPOUT[epnum].MAXCNT = xact_len;
225205

226-
edpt_dma_start(&NRF_USBD->TASKS_STARTEPOUT[epnum]);
206+
start_dma(&NRF_USBD->TASKS_STARTEPOUT[epnum]);
227207
}
228208
}
229209

@@ -248,7 +228,6 @@ static void xact_in_dma(uint8_t epnum)
248228
void dcd_init (uint8_t rhport)
249229
{
250230
TU_LOG1("dcd init\r\n");
251-
dcd_mutex = osal_mutex_create(&dcd_mutex_def);
252231
(void) rhport;
253232
}
254233

@@ -471,17 +450,10 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
471450

472451
xfer_td_t* xfer = get_td(epnum, dir);
473452

474-
if (!is_in_isr()) {
475-
osal_mutex_lock(dcd_mutex, OSAL_TIMEOUT_WAIT_FOREVER);
476-
dcd_int_disable(rhport);
477-
}
453+
TU_ASSERT(!xfer->started);
478454
xfer->buffer = buffer;
479455
xfer->total_len = total_bytes;
480456
xfer->actual_len = 0;
481-
if (!is_in_isr()) {
482-
dcd_int_enable(rhport);
483-
osal_mutex_unlock(dcd_mutex);
484-
}
485457

486458
// Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage
487459
bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE));
@@ -496,13 +468,20 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
496468
}
497469
else if ( dir == TUSB_DIR_OUT )
498470
{
471+
xfer->started = true;
499472
if ( epnum == 0 )
500473
{
501474
// Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA
502475
edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT);
503476
}else
504477
{
505-
if ( xfer->data_received && xfer->total_len > xfer->actual_len)
478+
// started just set, it could start DMA transfer if interrupt was trigger after this line
479+
// code only needs to start transfer (from Endpoint to RAM) when data_received was set
480+
// before started was set. If started is NOT set but data_received is, it means that
481+
// current transfer was already finished and next data is already present in endpoint and
482+
// can be consumed by future transfer
483+
__ISB(); __DSB();
484+
if ( xfer->data_received && xfer->started )
506485
{
507486
// Data is already received previously
508487
// start DMA to copy to SRAM
@@ -804,7 +783,9 @@ void dcd_int_handler(uint8_t rhport)
804783
}
805784
}else
806785
{
786+
TU_ASSERT(xfer->started,);
807787
xfer->total_len = xfer->actual_len;
788+
xfer->started = false;
808789

809790
// CBI OUT complete
810791
dcd_event_xfer_complete(0, epnum, xfer->actual_len, XFER_RESULT_SUCCESS, true);
@@ -857,7 +838,7 @@ void dcd_int_handler(uint8_t rhport)
857838
{
858839
xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT);
859840

860-
if (xfer->actual_len < xfer->total_len)
841+
if ( xfer->started && xfer->actual_len < xfer->total_len )
861842
{
862843
xact_out_dma(epnum);
863844
}else

0 commit comments

Comments
 (0)