Skip to content

Commit 1d3c722

Browse files
committed
usb: Fix race if transfers are submitted by a thread.
The USB pending transfer flag was cleared before calling the completion callback, to allow the callback code to call submit_xfer() again. Unfortunately this isn't safe in a multi-threaded environment, as another thread may see the endpoint is available before the callback is done executing and submit a new transfer. Rather than adding extra locking, specifically treat the transfer as still pending if checked from another thread while the callback is executing. Closes micropython#874 Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 27e4d73 commit 1d3c722

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
metadata(version="0.1.0")
1+
metadata(version="0.1.1")
22
package("usb")

micropython/usb/usb-device/usb/device/core.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
import machine
99
import 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):
@@ -298,7 +308,7 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
298308
# that function for documentation about the possible parameter values.
299309
if ep_addr not in self._eps:
300310
raise ValueError("ep_addr")
301-
if self._ep_cbs[ep_addr]:
311+
if self._xfer_pending(ep_addr):
302312
raise RuntimeError("xfer_pending")
303313

304314
# USBDevice callback may be called immediately, before Python execution
@@ -308,12 +318,25 @@ def _submit_xfer(self, ep_addr, data, done_cb=None):
308318
self._ep_cbs[ep_addr] = done_cb or True
309319
return self._usbd.submit_xfer(ep_addr, data)
310320

321+
def _xfer_pending(self, ep_addr):
322+
# Singleton function to return True if transfer is pending on this endpoint.
323+
#
324+
# Generally, drivers should call Interface.xfer_pending() instead. See that
325+
# function for more documentation.
326+
return self._ep_cbs[ep_addr] or (self._cb_ep == ep_addr and self._cb_thread != get_ident())
327+
311328
def _xfer_cb(self, ep_addr, result, xferred_bytes):
312329
# Singleton callback from TinyUSB custom class driver when a transfer completes.
313330
cb = self._ep_cbs.get(ep_addr, None)
331+
self._cb_thread = get_ident()
332+
self._cb_ep = ep_addr # Track while callback is running
314333
self._ep_cbs[ep_addr] = None
315-
if callable(cb):
316-
cb(ep_addr, result, xferred_bytes)
334+
try:
335+
# For a pending xfer, 'cb' should either a callback function or True (if no callback)
336+
if callable(cb):
337+
cb(ep_addr, result, xferred_bytes)
338+
finally:
339+
self._cb_ep = None
317340

318341
def _control_xfer_cb(self, stage, request):
319342
# Singleton callback from TinyUSB custom class driver when a control
@@ -528,7 +551,12 @@ def xfer_pending(self, ep_addr):
528551
# Return True if a transfer is already pending on ep_addr.
529552
#
530553
# Only one transfer can be submitted at a time.
531-
return _dev and bool(_dev._ep_cbs[ep_addr])
554+
#
555+
# The transfer is marked pending while a completion callback is running
556+
# for that endpoint, unless this function is called from the callback
557+
# itself. This makes it simple to submit a new transfer from the
558+
# completion callback.
559+
return _dev and _dev._xfer_pending(ep_addr)
532560

533561
def submit_xfer(self, ep_addr, data, done_cb=None):
534562
# Submit a USB transfer (of any type except control)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy