Skip to content

Commit 50ed36f

Browse files
committed
pyusb: Add MicroPython implementation of PyUSB library.
Signed-off-by: Damien George <[email protected]>
1 parent 2c30a4e commit 50ed36f

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed

Diff for: unix-ffi/pyusb/examples/lsusb.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Simple example to list attached USB devices.
2+
3+
import usb.core
4+
5+
for device in usb.core.find(find_all=True):
6+
print("ID {:04x}:{:04x}".format(device.idVendor, device.idProduct))
7+
for cfg in device:
8+
print(
9+
" config numitf={} value={} attr={} power={}".format(
10+
cfg.bNumInterfaces, cfg.bConfigurationValue, cfg.bmAttributes, cfg.bMaxPower
11+
)
12+
)
13+
for itf in cfg:
14+
print(
15+
" interface class={} subclass={}".format(
16+
itf.bInterfaceClass, itf.bInterfaceSubClass
17+
)
18+
)

Diff for: unix-ffi/pyusb/manifest.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
metadata(version="0.1.0", pypi="pyusb")
2+
3+
package("usb")

Diff for: unix-ffi/pyusb/usb/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George

Diff for: unix-ffi/pyusb/usb/control.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
5+
def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex=0):
6+
wValue = desc_index | desc_type << 8
7+
d = dev.ctrl_transfer(0x80, 0x06, wValue, wIndex, desc_size)
8+
if len(d) < 2:
9+
raise Exception("invalid descriptor")
10+
return d

Diff for: unix-ffi/pyusb/usb/core.py

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
import sys
5+
import ffi
6+
import uctypes
7+
8+
if sys.maxsize >> 32:
9+
UINTPTR_SIZE = 8
10+
UINTPTR = uctypes.UINT64
11+
else:
12+
UINTPTR_SIZE = 4
13+
UINTPTR = uctypes.UINT32
14+
15+
16+
def _align_word(x):
17+
return (x + UINTPTR_SIZE - 1) & ~(UINTPTR_SIZE - 1)
18+
19+
20+
ptr_descriptor = (0 | uctypes.ARRAY, 1 | UINTPTR)
21+
22+
libusb_device_descriptor = {
23+
"bLength": 0 | uctypes.UINT8,
24+
"bDescriptorType": 1 | uctypes.UINT8,
25+
"bcdUSB": 2 | uctypes.UINT16,
26+
"bDeviceClass": 4 | uctypes.UINT8,
27+
"bDeviceSubClass": 5 | uctypes.UINT8,
28+
"bDeviceProtocol": 6 | uctypes.UINT8,
29+
"bMaxPacketSize0": 7 | uctypes.UINT8,
30+
"idVendor": 8 | uctypes.UINT16,
31+
"idProduct": 10 | uctypes.UINT16,
32+
"bcdDevice": 12 | uctypes.UINT16,
33+
"iManufacturer": 14 | uctypes.UINT8,
34+
"iProduct": 15 | uctypes.UINT8,
35+
"iSerialNumber": 16 | uctypes.UINT8,
36+
"bNumConfigurations": 17 | uctypes.UINT8,
37+
}
38+
39+
libusb_config_descriptor = {
40+
"bLength": 0 | uctypes.UINT8,
41+
"bDescriptorType": 1 | uctypes.UINT8,
42+
"wTotalLength": 2 | uctypes.UINT16,
43+
"bNumInterfaces": 4 | uctypes.UINT8,
44+
"bConfigurationValue": 5 | uctypes.UINT8,
45+
"iConfiguration": 6 | uctypes.UINT8,
46+
"bmAttributes": 7 | uctypes.UINT8,
47+
"MaxPower": 8 | uctypes.UINT8,
48+
"interface": _align_word(9) | UINTPTR, # array of libusb_interface
49+
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
50+
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
51+
}
52+
53+
libusb_interface = {
54+
"altsetting": 0 | UINTPTR, # array of libusb_interface_descriptor
55+
"num_altsetting": UINTPTR_SIZE | uctypes.INT,
56+
}
57+
58+
libusb_interface_descriptor = {
59+
"bLength": 0 | uctypes.UINT8,
60+
"bDescriptorType": 1 | uctypes.UINT8,
61+
"bInterfaceNumber": 2 | uctypes.UINT8,
62+
"bAlternateSetting": 3 | uctypes.UINT8,
63+
"bNumEndpoints": 4 | uctypes.UINT8,
64+
"bInterfaceClass": 5 | uctypes.UINT8,
65+
"bInterfaceSubClass": 6 | uctypes.UINT8,
66+
"bInterfaceProtocol": 7 | uctypes.UINT8,
67+
"iInterface": 8 | uctypes.UINT8,
68+
"endpoint": _align_word(9) | UINTPTR,
69+
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
70+
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
71+
}
72+
73+
libusb = ffi.open("libusb-1.0.so")
74+
libusb_init = libusb.func("i", "libusb_init", "p")
75+
libusb_exit = libusb.func("v", "libusb_exit", "p")
76+
libusb_get_device_list = libusb.func("i", "libusb_get_device_list", "pp") # return is ssize_t
77+
libusb_free_device_list = libusb.func("v", "libusb_free_device_list", "pi")
78+
libusb_get_device_descriptor = libusb.func("i", "libusb_get_device_descriptor", "pp")
79+
libusb_get_config_descriptor = libusb.func("i", "libusb_get_config_descriptor", "pBp")
80+
libusb_free_config_descriptor = libusb.func("v", "libusb_free_config_descriptor", "p")
81+
libusb_open = libusb.func("i", "libusb_open", "pp")
82+
libusb_set_configuration = libusb.func("i", "libusb_set_configuration", "pi")
83+
libusb_claim_interface = libusb.func("i", "libusb_claim_interface", "pi")
84+
libusb_control_transfer = libusb.func("i", "libusb_control_transfer", "pBBHHpHI")
85+
86+
87+
def _new(sdesc):
88+
buf = bytearray(uctypes.sizeof(sdesc))
89+
s = uctypes.struct(uctypes.addressof(buf), sdesc)
90+
return s
91+
92+
93+
class Interface:
94+
def __init__(self, descr):
95+
# Public attributes.
96+
self.bInterfaceClass = descr.bInterfaceClass
97+
self.bInterfaceSubClass = descr.bInterfaceSubClass
98+
self.iInterface = descr.iInterface
99+
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)
100+
101+
102+
class Configuration:
103+
def __init__(self, dev, cfg_idx):
104+
cfgs = _new(ptr_descriptor)
105+
if libusb_get_config_descriptor(dev._dev, cfg_idx, cfgs) != 0:
106+
libusb_exit(0)
107+
raise Exception
108+
descr = uctypes.struct(cfgs[0], libusb_config_descriptor)
109+
110+
# Extract all needed info because descr is going to be free'd at the end.
111+
self._itfs = []
112+
itf_array = uctypes.struct(
113+
descr.interface, (0 | uctypes.ARRAY, descr.bNumInterfaces, libusb_interface)
114+
)
115+
for i in range(descr.bNumInterfaces):
116+
itf = itf_array[i]
117+
alt_array = uctypes.struct(
118+
itf.altsetting,
119+
(0 | uctypes.ARRAY, itf.num_altsetting, libusb_interface_descriptor),
120+
)
121+
for j in range(itf.num_altsetting):
122+
alt = alt_array[j]
123+
self._itfs.append(Interface(alt))
124+
125+
# Public attributes.
126+
self.bNumInterfaces = descr.bNumInterfaces
127+
self.bConfigurationValue = descr.bConfigurationValue
128+
self.bmAttributes = descr.bmAttributes
129+
self.bMaxPower = descr.MaxPower
130+
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)
131+
132+
# Free descr memory in the driver.
133+
libusb_free_config_descriptor(cfgs[0])
134+
135+
def __iter__(self):
136+
return iter(self._itfs)
137+
138+
139+
class Device:
140+
_TIMEOUT_DEFAULT = 1000
141+
142+
def __init__(self, dev, descr):
143+
self._dev = dev
144+
self._num_cfg = descr.bNumConfigurations
145+
self._handle = None
146+
self._claim_itf = set()
147+
148+
# Public attributes.
149+
self.idVendor = descr.idVendor
150+
self.idProduct = descr.idProduct
151+
152+
def __iter__(self):
153+
for i in range(self._num_cfg):
154+
yield Configuration(self, i)
155+
156+
def __getitem__(self, i):
157+
return Configuration(self, i)
158+
159+
def _open(self):
160+
if self._handle is None:
161+
# Open the USB device.
162+
handle = _new(ptr_descriptor)
163+
if libusb_open(self._dev, handle) != 0:
164+
libusb_exit(0)
165+
raise Exception
166+
self._handle = handle[0]
167+
168+
def _claim_interface(self, i):
169+
if libusb_claim_interface(self._handle, i) != 0:
170+
libusb_exit(0)
171+
raise Exception
172+
173+
def set_configuration(self):
174+
# Select default configuration.
175+
self._open()
176+
cfg = Configuration(self, 0).bConfigurationValue
177+
ret = libusb_set_configuration(self._handle, cfg)
178+
if ret != 0:
179+
libusb_exit(0)
180+
raise Exception
181+
182+
def ctrl_transfer(
183+
self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None
184+
):
185+
if data_or_wLength is None:
186+
l = 0
187+
data = bytes()
188+
elif isinstance(data_or_wLength, int):
189+
l = data_or_wLength
190+
data = bytearray(l)
191+
else:
192+
l = len(data_or_wLength)
193+
data = data_or_wLength
194+
self._open()
195+
if wIndex & 0xFF not in self._claim_itf:
196+
self._claim_interface(wIndex & 0xFF)
197+
self._claim_itf.add(wIndex & 0xFF)
198+
if timeout is None:
199+
timeout = self._TIMEOUT_DEFAULT
200+
ret = libusb_control_transfer(
201+
self._handle, bmRequestType, bRequest, wValue, wIndex, data, l, timeout * 1000
202+
)
203+
if ret < 0:
204+
libusb_exit(0)
205+
raise Exception
206+
if isinstance(data_or_wLength, int):
207+
return data
208+
else:
209+
return ret
210+
211+
212+
def find(*, find_all=False, custom_match=None, idVendor=None, idProduct=None):
213+
if libusb_init(0) < 0:
214+
raise Exception
215+
216+
devs = _new(ptr_descriptor)
217+
count = libusb_get_device_list(0, devs)
218+
if count < 0:
219+
libusb_exit(0)
220+
raise Exception
221+
222+
dev_array = uctypes.struct(devs[0], (0 | uctypes.ARRAY, count | UINTPTR))
223+
descr = _new(libusb_device_descriptor)
224+
devices = None
225+
for i in range(count):
226+
libusb_get_device_descriptor(dev_array[i], descr)
227+
if idVendor and descr.idVendor != idVendor:
228+
continue
229+
if idProduct and descr.idProduct != idProduct:
230+
continue
231+
device = Device(dev_array[i], descr)
232+
if custom_match and not custom_match(device):
233+
continue
234+
if not find_all:
235+
return device
236+
if not devices:
237+
devices = []
238+
devices.append(device)
239+
return devices

Diff for: unix-ffi/pyusb/usb/util.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
import usb.control
5+
6+
7+
def claim_interface(device, interface):
8+
device._claim_interface(interface)
9+
10+
11+
def get_string(device, index):
12+
bs = usb.control.get_descriptor(device, 254, 3, index, 0)
13+
s = ""
14+
for i in range(2, bs[0] & 0xFE, 2):
15+
s += chr(bs[i] | bs[i + 1] << 8)
16+
return s

0 commit comments

Comments
 (0)