Skip to content

Commit

Permalink
pyusb: Add MicroPython implementation of PyUSB library.
Browse files Browse the repository at this point in the history
Signed-off-by: Damien George <[email protected]>
  • Loading branch information
dpgeorge committed May 27, 2024
1 parent 2c30a4e commit 50ed36f
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 0 deletions.
18 changes: 18 additions & 0 deletions unix-ffi/pyusb/examples/lsusb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Simple example to list attached USB devices.

import usb.core

for device in usb.core.find(find_all=True):
print("ID {:04x}:{:04x}".format(device.idVendor, device.idProduct))
for cfg in device:
print(
" config numitf={} value={} attr={} power={}".format(
cfg.bNumInterfaces, cfg.bConfigurationValue, cfg.bmAttributes, cfg.bMaxPower
)
)
for itf in cfg:
print(
" interface class={} subclass={}".format(
itf.bInterfaceClass, itf.bInterfaceSubClass
)
)
3 changes: 3 additions & 0 deletions unix-ffi/pyusb/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
metadata(version="0.1.0", pypi="pyusb")

package("usb")
2 changes: 2 additions & 0 deletions unix-ffi/pyusb/usb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# MicroPython USB host library, compatible with PyUSB.
# MIT license; Copyright (c) 2021-2024 Damien P. George
10 changes: 10 additions & 0 deletions unix-ffi/pyusb/usb/control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# MicroPython USB host library, compatible with PyUSB.
# MIT license; Copyright (c) 2021-2024 Damien P. George


def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex=0):
wValue = desc_index | desc_type << 8
d = dev.ctrl_transfer(0x80, 0x06, wValue, wIndex, desc_size)
if len(d) < 2:
raise Exception("invalid descriptor")
return d
239 changes: 239 additions & 0 deletions unix-ffi/pyusb/usb/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# MicroPython USB host library, compatible with PyUSB.
# MIT license; Copyright (c) 2021-2024 Damien P. George

import sys
import ffi
import uctypes

if sys.maxsize >> 32:
UINTPTR_SIZE = 8
UINTPTR = uctypes.UINT64
else:
UINTPTR_SIZE = 4
UINTPTR = uctypes.UINT32


def _align_word(x):
return (x + UINTPTR_SIZE - 1) & ~(UINTPTR_SIZE - 1)


ptr_descriptor = (0 | uctypes.ARRAY, 1 | UINTPTR)

libusb_device_descriptor = {
"bLength": 0 | uctypes.UINT8,
"bDescriptorType": 1 | uctypes.UINT8,
"bcdUSB": 2 | uctypes.UINT16,
"bDeviceClass": 4 | uctypes.UINT8,
"bDeviceSubClass": 5 | uctypes.UINT8,
"bDeviceProtocol": 6 | uctypes.UINT8,
"bMaxPacketSize0": 7 | uctypes.UINT8,
"idVendor": 8 | uctypes.UINT16,
"idProduct": 10 | uctypes.UINT16,
"bcdDevice": 12 | uctypes.UINT16,
"iManufacturer": 14 | uctypes.UINT8,
"iProduct": 15 | uctypes.UINT8,
"iSerialNumber": 16 | uctypes.UINT8,
"bNumConfigurations": 17 | uctypes.UINT8,
}

libusb_config_descriptor = {
"bLength": 0 | uctypes.UINT8,
"bDescriptorType": 1 | uctypes.UINT8,
"wTotalLength": 2 | uctypes.UINT16,
"bNumInterfaces": 4 | uctypes.UINT8,
"bConfigurationValue": 5 | uctypes.UINT8,
"iConfiguration": 6 | uctypes.UINT8,
"bmAttributes": 7 | uctypes.UINT8,
"MaxPower": 8 | uctypes.UINT8,
"interface": _align_word(9) | UINTPTR, # array of libusb_interface
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
}

libusb_interface = {
"altsetting": 0 | UINTPTR, # array of libusb_interface_descriptor
"num_altsetting": UINTPTR_SIZE | uctypes.INT,
}

libusb_interface_descriptor = {
"bLength": 0 | uctypes.UINT8,
"bDescriptorType": 1 | uctypes.UINT8,
"bInterfaceNumber": 2 | uctypes.UINT8,
"bAlternateSetting": 3 | uctypes.UINT8,
"bNumEndpoints": 4 | uctypes.UINT8,
"bInterfaceClass": 5 | uctypes.UINT8,
"bInterfaceSubClass": 6 | uctypes.UINT8,
"bInterfaceProtocol": 7 | uctypes.UINT8,
"iInterface": 8 | uctypes.UINT8,
"endpoint": _align_word(9) | UINTPTR,
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
}

libusb = ffi.open("libusb-1.0.so")
libusb_init = libusb.func("i", "libusb_init", "p")
libusb_exit = libusb.func("v", "libusb_exit", "p")
libusb_get_device_list = libusb.func("i", "libusb_get_device_list", "pp") # return is ssize_t
libusb_free_device_list = libusb.func("v", "libusb_free_device_list", "pi")
libusb_get_device_descriptor = libusb.func("i", "libusb_get_device_descriptor", "pp")
libusb_get_config_descriptor = libusb.func("i", "libusb_get_config_descriptor", "pBp")
libusb_free_config_descriptor = libusb.func("v", "libusb_free_config_descriptor", "p")
libusb_open = libusb.func("i", "libusb_open", "pp")
libusb_set_configuration = libusb.func("i", "libusb_set_configuration", "pi")
libusb_claim_interface = libusb.func("i", "libusb_claim_interface", "pi")
libusb_control_transfer = libusb.func("i", "libusb_control_transfer", "pBBHHpHI")


def _new(sdesc):
buf = bytearray(uctypes.sizeof(sdesc))
s = uctypes.struct(uctypes.addressof(buf), sdesc)
return s


class Interface:
def __init__(self, descr):
# Public attributes.
self.bInterfaceClass = descr.bInterfaceClass
self.bInterfaceSubClass = descr.bInterfaceSubClass
self.iInterface = descr.iInterface
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)


class Configuration:
def __init__(self, dev, cfg_idx):
cfgs = _new(ptr_descriptor)
if libusb_get_config_descriptor(dev._dev, cfg_idx, cfgs) != 0:
libusb_exit(0)
raise Exception
descr = uctypes.struct(cfgs[0], libusb_config_descriptor)

# Extract all needed info because descr is going to be free'd at the end.
self._itfs = []
itf_array = uctypes.struct(
descr.interface, (0 | uctypes.ARRAY, descr.bNumInterfaces, libusb_interface)
)
for i in range(descr.bNumInterfaces):
itf = itf_array[i]
alt_array = uctypes.struct(
itf.altsetting,
(0 | uctypes.ARRAY, itf.num_altsetting, libusb_interface_descriptor),
)
for j in range(itf.num_altsetting):
alt = alt_array[j]
self._itfs.append(Interface(alt))

# Public attributes.
self.bNumInterfaces = descr.bNumInterfaces
self.bConfigurationValue = descr.bConfigurationValue
self.bmAttributes = descr.bmAttributes
self.bMaxPower = descr.MaxPower
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)

# Free descr memory in the driver.
libusb_free_config_descriptor(cfgs[0])

def __iter__(self):
return iter(self._itfs)


class Device:
_TIMEOUT_DEFAULT = 1000

def __init__(self, dev, descr):
self._dev = dev
self._num_cfg = descr.bNumConfigurations
self._handle = None
self._claim_itf = set()

# Public attributes.
self.idVendor = descr.idVendor
self.idProduct = descr.idProduct

def __iter__(self):
for i in range(self._num_cfg):
yield Configuration(self, i)

def __getitem__(self, i):
return Configuration(self, i)

def _open(self):
if self._handle is None:
# Open the USB device.
handle = _new(ptr_descriptor)
if libusb_open(self._dev, handle) != 0:
libusb_exit(0)
raise Exception
self._handle = handle[0]

def _claim_interface(self, i):
if libusb_claim_interface(self._handle, i) != 0:
libusb_exit(0)
raise Exception

def set_configuration(self):
# Select default configuration.
self._open()
cfg = Configuration(self, 0).bConfigurationValue
ret = libusb_set_configuration(self._handle, cfg)
if ret != 0:
libusb_exit(0)
raise Exception

def ctrl_transfer(
self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None
):
if data_or_wLength is None:
l = 0
data = bytes()
elif isinstance(data_or_wLength, int):
l = data_or_wLength
data = bytearray(l)
else:
l = len(data_or_wLength)
data = data_or_wLength
self._open()
if wIndex & 0xFF not in self._claim_itf:
self._claim_interface(wIndex & 0xFF)
self._claim_itf.add(wIndex & 0xFF)
if timeout is None:
timeout = self._TIMEOUT_DEFAULT
ret = libusb_control_transfer(
self._handle, bmRequestType, bRequest, wValue, wIndex, data, l, timeout * 1000
)
if ret < 0:
libusb_exit(0)
raise Exception
if isinstance(data_or_wLength, int):
return data
else:
return ret


def find(*, find_all=False, custom_match=None, idVendor=None, idProduct=None):
if libusb_init(0) < 0:
raise Exception

devs = _new(ptr_descriptor)
count = libusb_get_device_list(0, devs)
if count < 0:
libusb_exit(0)
raise Exception

dev_array = uctypes.struct(devs[0], (0 | uctypes.ARRAY, count | UINTPTR))
descr = _new(libusb_device_descriptor)
devices = None
for i in range(count):
libusb_get_device_descriptor(dev_array[i], descr)
if idVendor and descr.idVendor != idVendor:
continue
if idProduct and descr.idProduct != idProduct:
continue
device = Device(dev_array[i], descr)
if custom_match and not custom_match(device):
continue
if not find_all:
return device
if not devices:
devices = []
devices.append(device)
return devices
16 changes: 16 additions & 0 deletions unix-ffi/pyusb/usb/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# MicroPython USB host library, compatible with PyUSB.
# MIT license; Copyright (c) 2021-2024 Damien P. George

import usb.control


def claim_interface(device, interface):
device._claim_interface(interface)


def get_string(device, index):
bs = usb.control.get_descriptor(device, 254, 3, index, 0)
s = ""
for i in range(2, bs[0] & 0xFE, 2):
s += chr(bs[i] | bs[i + 1] << 8)
return s

0 comments on commit 50ed36f

Please sign in to comment.