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
8685static 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
122121static 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
141135static 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
182147static 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
195167static 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)
248228void 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