88import machine
99import struct
1010
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+
1119_EP_IN_FLAG = const (1 << 7 )
1220
1321# USB descriptor types
@@ -76,6 +84,8 @@ def __init__(self):
7684 self ._itfs = {} # Mapping from interface number to interface object, set by init()
7785 self ._eps = {} # Mapping from endpoint address to interface object, set by _open_cb()
7886 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
7989 self ._usbd = machine .USBDevice () # low-level API
8090
8191 def init (self , * itfs , ** kwargs ):
@@ -176,7 +186,9 @@ def maybe_set(value, idx):
176186 itf .desc_cfg (desc , itf_num , ep_num , strs )
177187
178188 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+ )
180192 itf_num += 1
181193
182194 ep_num += itf .num_eps ()
@@ -298,7 +310,7 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
298310 # that function for documentation about the possible parameter values.
299311 if ep_addr not in self ._eps :
300312 raise ValueError ("ep_addr" )
301- if self ._ep_cbs [ ep_addr ] :
313+ if self ._xfer_pending ( ep_addr ) :
302314 raise RuntimeError ("xfer_pending" )
303315
304316 # USBDevice callback may be called immediately, before Python execution
@@ -308,12 +320,26 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
308320 self ._ep_cbs [ep_addr ] = done_cb or True
309321 return self ._usbd .submit_xfer (ep_addr , data )
310322
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+
311332 def _xfer_cb (self , ep_addr , result , xferred_bytes ):
312333 # Singleton callback from TinyUSB custom class driver when a transfer completes.
313334 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
314337 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
317343
318344 def _control_xfer_cb (self , stage , request ):
319345 # Singleton callback from TinyUSB custom class driver when a control
@@ -528,7 +554,12 @@ def xfer_pending(self, ep_addr):
528554 # Return True if a transfer is already pending on ep_addr.
529555 #
530556 # 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 )
532563
533564 def submit_xfer (self , ep_addr , data , done_cb = None ):
534565 # Submit a USB transfer (of any type except control)
0 commit comments