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 ):
@@ -100,6 +110,7 @@ def config( # noqa: PLR0913
100
110
device_protocol = 0 ,
101
111
config_str = None ,
102
112
max_power_ma = None ,
113
+ remote_wakeup = False ,
103
114
):
104
115
# Configure the USB device with a set of interfaces, and optionally reconfiguring the
105
116
# device and configuration descriptor fields
@@ -189,7 +200,7 @@ def maybe_set(value, idx):
189
200
bmAttributes = (
190
201
(1 << 7 ) # Reserved
191
202
| (0 if max_power_ma else (1 << 6 )) # Self-Powered
192
- # Remote Wakeup not currently supported
203
+ | (( 1 << 5 ) if remote_wakeup else 0 )
193
204
)
194
205
195
206
# Configuration string is optional but supported
@@ -242,8 +253,8 @@ def active(self, *optional_value):
242
253
return self ._usbd .active (* optional_value )
243
254
244
255
def _open_itf_cb (self , desc ):
245
- # Singleton callback from TinyUSB custom class driver , when USB host does
246
- # Set Configuration. Called once per interface or IAD.
256
+ # Callback from TinyUSB lower layer , when USB host does Set
257
+ # Configuration. Called once per interface or IAD.
247
258
248
259
# Note that even if the configuration descriptor contains an IAD, 'desc'
249
260
# starts from the first interface descriptor in the IAD and not the IAD
@@ -281,7 +292,7 @@ def _open_itf_cb(self, desc):
281
292
itf .on_open ()
282
293
283
294
def _reset_cb (self ):
284
- # Callback when the USB device is reset by the host
295
+ # TinyUSB lower layer callback when the USB device is reset by the host
285
296
286
297
# Allow interfaces to respond to the reset
287
298
for itf in self ._itfs .values ():
@@ -292,13 +303,13 @@ def _reset_cb(self):
292
303
self ._ep_cbs = {}
293
304
294
305
def _submit_xfer (self , ep_addr , data , done_cb = None ):
295
- # Singleton function to submit a USB transfer (of any type except control).
306
+ # Submit a USB transfer (of any type except control) to TinyUSB lower layer .
296
307
#
297
308
# Generally, drivers should call Interface.submit_xfer() instead. See
298
309
# that function for documentation about the possible parameter values.
299
310
if ep_addr not in self ._eps :
300
311
raise ValueError ("ep_addr" )
301
- if self ._ep_cbs [ ep_addr ] :
312
+ if self ._xfer_pending ( ep_addr ) :
302
313
raise RuntimeError ("xfer_pending" )
303
314
304
315
# USBDevice callback may be called immediately, before Python execution
@@ -308,15 +319,32 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
308
319
self ._ep_cbs [ep_addr ] = done_cb or True
309
320
return self ._usbd .submit_xfer (ep_addr , data )
310
321
322
+ def _xfer_pending (self , ep_addr ):
323
+ # Returns True if a transfer is pending on this endpoint.
324
+ #
325
+ # Generally, drivers should call Interface.xfer_pending() instead. See that
326
+ # function for more documentation.
327
+ return self ._ep_cbs [ep_addr ] or (self ._cb_ep == ep_addr and self ._cb_thread != get_ident ())
328
+
311
329
def _xfer_cb (self , ep_addr , result , xferred_bytes ):
312
- # Singleton callback from TinyUSB custom class driver when a transfer completes.
330
+ # Callback from TinyUSB lower layer when a transfer completes.
313
331
cb = self ._ep_cbs .get (ep_addr , None )
332
+ self ._cb_thread = get_ident ()
333
+ self ._cb_ep = ep_addr # Track while callback is running
314
334
self ._ep_cbs [ep_addr ] = None
315
- if callable (cb ):
316
- cb (ep_addr , result , xferred_bytes )
335
+
336
+ # In most cases, 'cb' is a callback function for the transfer. Can also be:
337
+ # - True (for a transfer with no callback)
338
+ # - None (TinyUSB callback arrived for invalid endpoint, or no transfer.
339
+ # Generally unlikely, but may happen in transient states.)
340
+ try :
341
+ if callable (cb ):
342
+ cb (ep_addr , result , xferred_bytes )
343
+ finally :
344
+ self ._cb_ep = None
317
345
318
346
def _control_xfer_cb (self , stage , request ):
319
- # Singleton callback from TinyUSB custom class driver when a control
347
+ # Callback from TinyUSB lower layer when a control
320
348
# transfer is in progress.
321
349
#
322
350
# stage determines appropriate responses (possible values
@@ -528,7 +556,12 @@ def xfer_pending(self, ep_addr):
528
556
# Return True if a transfer is already pending on ep_addr.
529
557
#
530
558
# Only one transfer can be submitted at a time.
531
- return _dev and bool (_dev ._ep_cbs [ep_addr ])
559
+ #
560
+ # The transfer is marked pending while a completion callback is running
561
+ # for that endpoint, unless this function is called from the callback
562
+ # itself. This makes it simple to submit a new transfer from the
563
+ # completion callback.
564
+ return _dev and _dev ._xfer_pending (ep_addr )
532
565
533
566
def submit_xfer (self , ep_addr , data , done_cb = None ):
534
567
# Submit a USB transfer (of any type except control)
0 commit comments