Skip to content

Commit 7ab6240

Browse files
committed
util/agents/usb_hid_relay: fix concurrent access
Since c9fc5bf, it was no longer possible to use the same USB relay device from multiple labgrid processes, as the USB device was kept open and claimed. To fix this, we use a context manager which first claims the USB interface (with retry while busy) and releases it after the transaction. With this fix, multiple processes can toggle outputs in a busy loop without causing 'USBError(16, 'Resource busy')' failures. Fixes: c9fc5bf ("labgrid/util/agents/usb_hid_relay: keep the USB device open") Signed-off-by: Jan Luebbe <[email protected]>
1 parent 2ddc8b1 commit 7ab6240

File tree

1 file changed

+36
-10
lines changed

1 file changed

+36
-10
lines changed

labgrid/util/agents/usb_hid_relay.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
- Turn digital output on and off
1212
"""
1313

14+
import errno
15+
from contextlib import contextmanager
16+
from time import monotonic, sleep
17+
1418
import usb.core
1519
import usb.util
1620

@@ -26,18 +30,35 @@ def __init__(self, **args):
2630
raise ValueError("Device not found")
2731

2832
if self._dev.idVendor == 0x16C0:
29-
self.set_output = self.set_output_dcttech
30-
self.get_output = self.get_output_dcttech
33+
self._set_output = self._set_output_dcttech
34+
self._get_output = self._get_output_dcttech
3135
elif self._dev.idVendor == 0x5131:
32-
self.set_output = self.set_output_lcus
33-
self.get_output = self.get_output_lcus
36+
self._set_output = self._set_output_lcus
37+
self._get_output = self._get_output_lcus
3438
else:
3539
raise ValueError(f"Unknown vendor/protocol for VID {self._dev.idVendor:x}")
3640

3741
if self._dev.is_kernel_driver_active(0):
3842
self._dev.detach_kernel_driver(0)
3943

40-
def set_output_dcttech(self, number, status):
44+
@contextmanager
45+
def _claimed(self):
46+
timeout = monotonic() + 1.0
47+
while True:
48+
try:
49+
usb.util.claim_interface(self._dev, 0)
50+
break
51+
except usb.core.USBError as e:
52+
if monotonic() > timeout:
53+
raise e
54+
if e.errno == errno.EBUSY:
55+
sleep(0.01)
56+
else:
57+
raise e
58+
yield
59+
usb.util.release_interface(self._dev, 0)
60+
61+
def _set_output_dcttech(self, number, status):
4162
assert 1 <= number <= 8
4263
req = [0xFF if status else 0xFD, number]
4364
self._dev.ctrl_transfer(
@@ -48,7 +69,7 @@ def set_output_dcttech(self, number, status):
4869
req, # payload
4970
)
5071

51-
def get_output_dcttech(self, number):
72+
def _get_output_dcttech(self, number):
5273
assert 1 <= number <= 8
5374
resp = self._dev.ctrl_transfer(
5475
usb.util.CTRL_TYPE_CLASS | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.ENDPOINT_IN,
@@ -59,7 +80,7 @@ def get_output_dcttech(self, number):
5980
)
6081
return bool(resp[7] & (1 << (number - 1)))
6182

62-
def set_output_lcus(self, number, status):
83+
def _set_output_lcus(self, number, status):
6384
assert 1 <= number <= 8
6485
ep_in = self._dev[0][(0, 0)][0]
6586
ep_out = self._dev[0][(0, 0)][1]
@@ -68,13 +89,18 @@ def set_output_lcus(self, number, status):
6889
ep_out.write(req)
6990
ep_in.read(64)
7091

71-
def get_output_lcus(self, number):
92+
def _get_output_lcus(self, number):
7293
assert 1 <= number <= 8
7394
# we have no information on how to read the current value
7495
return False
7596

76-
def __del__(self):
77-
usb.util.release_interface(self._dev, 0)
97+
def set_output(self, number, status):
98+
with self._claimed():
99+
self._set_output(number, status)
100+
101+
def get_output(self, number):
102+
with self._claimed():
103+
self._get_output(number)
78104

79105

80106
_relays = {}

0 commit comments

Comments
 (0)