28
28
29
29
#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X
30
30
31
+ #include <stdatomic.h>
31
32
#include "nrf.h"
32
33
#include "nrf_clock.h"
33
34
#include "nrf_power.h"
@@ -72,16 +73,14 @@ typedef struct
72
73
// nRF will auto accept OUT packet after DMA is done
73
74
// indicate packet is already ACK
74
75
volatile bool data_received ;
76
+ volatile bool started ;
75
77
76
78
// Set to true when data was transferred from RAM to ISO IN output buffer.
77
79
// New data can be put in ISO IN output buffer after SOF.
78
80
bool iso_in_transfer_ready ;
79
81
80
82
} xfer_td_t ;
81
83
82
- static osal_mutex_def_t dcd_mutex_def ;
83
- static osal_mutex_t dcd_mutex ;
84
-
85
84
// Data for managing dcd
86
85
static struct
87
86
{
@@ -90,7 +89,7 @@ static struct
90
89
xfer_td_t xfer [EP_CBI_COUNT + 1 ][2 ];
91
90
92
91
// 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 ;
94
93
}_dcd ;
95
94
96
95
/*------------------------------------------------------------------*/
@@ -121,8 +120,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void)
121
120
// helper to start DMA
122
121
static void start_dma (volatile uint32_t * reg_startep )
123
122
{
124
- _dcd .dma_running = true;
125
-
126
123
(* reg_startep ) = 1 ;
127
124
__ISB (); __DSB ();
128
125
@@ -131,58 +128,26 @@ static void start_dma(volatile uint32_t* reg_startep)
131
128
// Therefore dma_pending is corrected right away
132
129
if ( (reg_startep == & NRF_USBD -> TASKS_EP0STATUS ) || (reg_startep == & NRF_USBD -> TASKS_EP0RCVOUT ) )
133
130
{
134
- _dcd .dma_running = false ;
131
+ atomic_flag_clear ( & _dcd .dma_running ) ;
135
132
}
136
133
}
137
134
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
141
135
static void edpt_dma_start (volatile uint32_t * reg_startep )
142
136
{
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 ) )
145
138
{
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);
154
140
}else
155
141
{
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 );
178
143
}
179
144
}
180
145
181
146
// DMA is complete
182
147
static void edpt_dma_end (void )
183
148
{
184
149
TU_ASSERT (_dcd .dma_running , );
185
- _dcd .dma_running = false ;
150
+ atomic_flag_clear ( & _dcd .dma_running ) ;
186
151
}
187
152
188
153
// helper getting td
@@ -191,27 +156,42 @@ static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir)
191
156
return & _dcd .xfer [epnum ][dir ];
192
157
}
193
158
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
+
194
166
// Start DMA to move data from Endpoint -> RAM
195
167
static void xact_out_dma (uint8_t epnum )
196
168
{
197
169
xfer_td_t * xfer = get_td (epnum , TUSB_DIR_OUT );
198
170
uint32_t xact_len ;
199
171
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
+ }
200
179
if (epnum == EP_ISO_NUM )
201
180
{
202
181
xact_len = NRF_USBD -> SIZE .ISOOUT ;
203
182
// If ZERO bit is set, ignore ISOOUT length
204
183
if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk )
205
184
{
206
185
xact_len = 0 ;
186
+ atomic_flag_clear (& _dcd .dma_running );
207
187
}
208
188
else
209
189
{
210
190
// Trigger DMA move data from Endpoint -> SRAM
211
191
NRF_USBD -> ISOOUT .PTR = (uint32_t ) xfer -> buffer ;
212
192
NRF_USBD -> ISOOUT .MAXCNT = xact_len ;
213
193
214
- edpt_dma_start (& NRF_USBD -> TASKS_STARTISOOUT );
194
+ start_dma (& NRF_USBD -> TASKS_STARTISOOUT );
215
195
}
216
196
}
217
197
else
@@ -223,7 +203,7 @@ static void xact_out_dma(uint8_t epnum)
223
203
NRF_USBD -> EPOUT [epnum ].PTR = (uint32_t ) xfer -> buffer ;
224
204
NRF_USBD -> EPOUT [epnum ].MAXCNT = xact_len ;
225
205
226
- edpt_dma_start (& NRF_USBD -> TASKS_STARTEPOUT [epnum ]);
206
+ start_dma (& NRF_USBD -> TASKS_STARTEPOUT [epnum ]);
227
207
}
228
208
}
229
209
@@ -248,7 +228,6 @@ static void xact_in_dma(uint8_t epnum)
248
228
void dcd_init (uint8_t rhport )
249
229
{
250
230
TU_LOG1 ("dcd init\r\n" );
251
- dcd_mutex = osal_mutex_create (& dcd_mutex_def );
252
231
(void ) rhport ;
253
232
}
254
233
@@ -471,17 +450,10 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t
471
450
472
451
xfer_td_t * xfer = get_td (epnum , dir );
473
452
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 );
478
454
xfer -> buffer = buffer ;
479
455
xfer -> total_len = total_bytes ;
480
456
xfer -> actual_len = 0 ;
481
- if (!is_in_isr ()) {
482
- dcd_int_enable (rhport );
483
- osal_mutex_unlock (dcd_mutex );
484
- }
485
457
486
458
// Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage
487
459
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
496
468
}
497
469
else if ( dir == TUSB_DIR_OUT )
498
470
{
471
+ xfer -> started = true;
499
472
if ( epnum == 0 )
500
473
{
501
474
// Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA
502
475
edpt_dma_start (& NRF_USBD -> TASKS_EP0RCVOUT );
503
476
}else
504
477
{
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 )
506
485
{
507
486
// Data is already received previously
508
487
// start DMA to copy to SRAM
@@ -804,7 +783,9 @@ void dcd_int_handler(uint8_t rhport)
804
783
}
805
784
}else
806
785
{
786
+ TU_ASSERT (xfer -> started ,);
807
787
xfer -> total_len = xfer -> actual_len ;
788
+ xfer -> started = false;
808
789
809
790
// CBI OUT complete
810
791
dcd_event_xfer_complete (0 , epnum , xfer -> actual_len , XFER_RESULT_SUCCESS , true);
@@ -857,7 +838,7 @@ void dcd_int_handler(uint8_t rhport)
857
838
{
858
839
xfer_td_t * xfer = get_td (epnum , TUSB_DIR_OUT );
859
840
860
- if (xfer -> actual_len < xfer -> total_len )
841
+ if ( xfer -> started && xfer -> actual_len < xfer -> total_len )
861
842
{
862
843
xact_out_dma (epnum );
863
844
}else
0 commit comments