8
8
import machine
9
9
import struct
10
10
11
+ try :
12
+ from _thread import get_ident
13
+ except ImportError :
14
+
15
+ def get_ident ():
16
+ return 0 # Placeholder, for no threading support
17
+
18
+
11
19
_EP_IN_FLAG = const (1 << 7 )
12
20
13
21
# USB descriptor types
@@ -76,6 +84,8 @@ def __init__(self):
76
84
self ._itfs = {} # Mapping from interface number to interface object, set by init()
77
85
self ._eps = {} # Mapping from endpoint address to interface object, set by _open_cb()
78
86
self ._ep_cbs = {} # Mapping from endpoint address to Optional[xfer callback]
87
+ self ._cb_thread = None # Thread currently running endpoint callback
88
+ self ._cb_ep = None # Endpoint number currently running callback
79
89
self ._usbd = machine .USBDevice () # low-level API
80
90
81
91
def init (self , * itfs , ** kwargs ):
@@ -176,7 +186,9 @@ def maybe_set(value, idx):
176
186
itf .desc_cfg (desc , itf_num , ep_num , strs )
177
187
178
188
for _ in range (itf .num_itfs ()):
179
- self ._itfs [itf_num ] = itf # Mapping from interface numbers to interfaces
189
+ self ._itfs [itf_num ] = (
190
+ itf # Mapping from interface numbers to interfaces
191
+ )
180
192
itf_num += 1
181
193
182
194
ep_num += itf .num_eps ()
@@ -298,7 +310,7 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
298
310
# that function for documentation about the possible parameter values.
299
311
if ep_addr not in self ._eps :
300
312
raise ValueError ("ep_addr" )
301
- if self ._ep_cbs [ ep_addr ] :
313
+ if self ._xfer_pending ( ep_addr ) :
302
314
raise RuntimeError ("xfer_pending" )
303
315
304
316
# USBDevice callback may be called immediately, before Python execution
@@ -308,12 +320,26 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
308
320
self ._ep_cbs [ep_addr ] = done_cb or True
309
321
return self ._usbd .submit_xfer (ep_addr , data )
310
322
323
+ def _xfer_pending (self , ep_addr ):
324
+ # Singleton function to return True if transfer is pending on this endpoint.
325
+ #
326
+ # Generally, drivers should call Interface.xfer_pending() instead. See that
327
+ # function for more documentation.
328
+ return bool (self ._ep_cbs [ep_addr ]) or (
329
+ self ._cb_ep == ep_addr and self ._cb_thread != get_ident ()
330
+ )
331
+
311
332
def _xfer_cb (self , ep_addr , result , xferred_bytes ):
312
333
# Singleton callback from TinyUSB custom class driver when a transfer completes.
313
334
cb = self ._ep_cbs .get (ep_addr , None )
335
+ self ._cb_thread = get_ident ()
336
+ self ._cb_ep = ep_addr # Track while callback is running
314
337
self ._ep_cbs [ep_addr ] = None
315
- if callable (cb ):
316
- cb (ep_addr , result , xferred_bytes )
338
+ try :
339
+ if callable (cb ):
340
+ cb (ep_addr , result , xferred_bytes )
341
+ finally :
342
+ self ._cb_ep = None
317
343
318
344
def _control_xfer_cb (self , stage , request ):
319
345
# Singleton callback from TinyUSB custom class driver when a control
@@ -528,7 +554,12 @@ def xfer_pending(self, ep_addr):
528
554
# Return True if a transfer is already pending on ep_addr.
529
555
#
530
556
# Only one transfer can be submitted at a time.
531
- return _dev and bool (_dev ._ep_cbs [ep_addr ])
557
+ #
558
+ # The transfer is marked pending while a completion callback is running
559
+ # for that endpoint, unless this function is called from the callback
560
+ # itself. This makes it simple to submit a new transfer from the
561
+ # completion callback.
562
+ return _dev and _dev ._xfer_pending (ep_addr )
532
563
533
564
def submit_xfer (self , ep_addr , data , done_cb = None ):
534
565
# Submit a USB transfer (of any type except control)
0 commit comments