kopia lustrzana https://github.com/micropython/micropython-lib
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 #874 Signed-off-by: Angus Gratton <angus@redyak.com.au>pull/896/head
rodzic
27e4d73bc2
commit
1d3c722b7d
|
@ -1,2 +1,2 @@
|
||||||
metadata(version="0.1.0")
|
metadata(version="0.1.1")
|
||||||
package("usb")
|
package("usb")
|
||||||
|
|
|
@ -8,6 +8,14 @@ from micropython import const
|
||||||
import machine
|
import machine
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
try:
|
||||||
|
from _thread import get_ident
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
def get_ident():
|
||||||
|
return 0 # Placeholder, for no threading support
|
||||||
|
|
||||||
|
|
||||||
_EP_IN_FLAG = const(1 << 7)
|
_EP_IN_FLAG = const(1 << 7)
|
||||||
|
|
||||||
# USB descriptor types
|
# USB descriptor types
|
||||||
|
@ -76,6 +84,8 @@ class _Device:
|
||||||
self._itfs = {} # Mapping from interface number to interface object, set by init()
|
self._itfs = {} # Mapping from interface number to interface object, set by init()
|
||||||
self._eps = {} # Mapping from endpoint address to interface object, set by _open_cb()
|
self._eps = {} # Mapping from endpoint address to interface object, set by _open_cb()
|
||||||
self._ep_cbs = {} # Mapping from endpoint address to Optional[xfer callback]
|
self._ep_cbs = {} # Mapping from endpoint address to Optional[xfer callback]
|
||||||
|
self._cb_thread = None # Thread currently running endpoint callback
|
||||||
|
self._cb_ep = None # Endpoint number currently running callback
|
||||||
self._usbd = machine.USBDevice() # low-level API
|
self._usbd = machine.USBDevice() # low-level API
|
||||||
|
|
||||||
def init(self, *itfs, **kwargs):
|
def init(self, *itfs, **kwargs):
|
||||||
|
@ -298,7 +308,7 @@ class _Device:
|
||||||
# that function for documentation about the possible parameter values.
|
# that function for documentation about the possible parameter values.
|
||||||
if ep_addr not in self._eps:
|
if ep_addr not in self._eps:
|
||||||
raise ValueError("ep_addr")
|
raise ValueError("ep_addr")
|
||||||
if self._ep_cbs[ep_addr]:
|
if self._xfer_pending(ep_addr):
|
||||||
raise RuntimeError("xfer_pending")
|
raise RuntimeError("xfer_pending")
|
||||||
|
|
||||||
# USBDevice callback may be called immediately, before Python execution
|
# USBDevice callback may be called immediately, before Python execution
|
||||||
|
@ -308,12 +318,25 @@ class _Device:
|
||||||
self._ep_cbs[ep_addr] = done_cb or True
|
self._ep_cbs[ep_addr] = done_cb or True
|
||||||
return self._usbd.submit_xfer(ep_addr, data)
|
return self._usbd.submit_xfer(ep_addr, data)
|
||||||
|
|
||||||
|
def _xfer_pending(self, ep_addr):
|
||||||
|
# Singleton function to return True if transfer is pending on this endpoint.
|
||||||
|
#
|
||||||
|
# Generally, drivers should call Interface.xfer_pending() instead. See that
|
||||||
|
# function for more documentation.
|
||||||
|
return self._ep_cbs[ep_addr] or (self._cb_ep == ep_addr and self._cb_thread != get_ident())
|
||||||
|
|
||||||
def _xfer_cb(self, ep_addr, result, xferred_bytes):
|
def _xfer_cb(self, ep_addr, result, xferred_bytes):
|
||||||
# Singleton callback from TinyUSB custom class driver when a transfer completes.
|
# Singleton callback from TinyUSB custom class driver when a transfer completes.
|
||||||
cb = self._ep_cbs.get(ep_addr, None)
|
cb = self._ep_cbs.get(ep_addr, None)
|
||||||
|
self._cb_thread = get_ident()
|
||||||
|
self._cb_ep = ep_addr # Track while callback is running
|
||||||
self._ep_cbs[ep_addr] = None
|
self._ep_cbs[ep_addr] = None
|
||||||
|
try:
|
||||||
|
# For a pending xfer, 'cb' should either a callback function or True (if no callback)
|
||||||
if callable(cb):
|
if callable(cb):
|
||||||
cb(ep_addr, result, xferred_bytes)
|
cb(ep_addr, result, xferred_bytes)
|
||||||
|
finally:
|
||||||
|
self._cb_ep = None
|
||||||
|
|
||||||
def _control_xfer_cb(self, stage, request):
|
def _control_xfer_cb(self, stage, request):
|
||||||
# Singleton callback from TinyUSB custom class driver when a control
|
# Singleton callback from TinyUSB custom class driver when a control
|
||||||
|
@ -528,7 +551,12 @@ class Interface:
|
||||||
# Return True if a transfer is already pending on ep_addr.
|
# Return True if a transfer is already pending on ep_addr.
|
||||||
#
|
#
|
||||||
# Only one transfer can be submitted at a time.
|
# Only one transfer can be submitted at a time.
|
||||||
return _dev and bool(_dev._ep_cbs[ep_addr])
|
#
|
||||||
|
# The transfer is marked pending while a completion callback is running
|
||||||
|
# for that endpoint, unless this function is called from the callback
|
||||||
|
# itself. This makes it simple to submit a new transfer from the
|
||||||
|
# completion callback.
|
||||||
|
return _dev and _dev._xfer_pending(ep_addr)
|
||||||
|
|
||||||
def submit_xfer(self, ep_addr, data, done_cb=None):
|
def submit_xfer(self, ep_addr, data, done_cb=None):
|
||||||
# Submit a USB transfer (of any type except control)
|
# Submit a USB transfer (of any type except control)
|
||||||
|
|
Ładowanie…
Reference in New Issue