X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/blobdiff_plain/6e2fc2d7771cc089fb32fd3184c694e03ac21810..35e4d48c5f911e7c70a1de4888b27ef97a59da65:/src/usb_hid.py diff --git a/src/usb_hid.py b/src/usb_hid.py index a893d83..791647b 100644 --- a/src/usb_hid.py +++ b/src/usb_hid.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT """ `usb_hid` - support for usb hid devices via usb_gadget driver =========================================================== @@ -12,25 +15,24 @@ from typing import Sequence from pathlib import Path import os import atexit +import sys for module in ["dwc2", "libcomposite"]: - if Path("/proc/modules").read_text().find(module) == -1: - raise Exception( + if Path("/proc/modules").read_text(encoding="utf-8").find(module) == -1: + raise Exception( # pylint: disable=broad-exception-raised "%s module not present in your kernel. did you insmod it?" % module ) +this = sys.modules[__name__] -gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka" -_boot_device = 0 -devices = [] - - -def get_boot_device() -> int: - return _boot_device +this.gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka" +this.boot_device = 0 +this.devices = [] class Device: """ - HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c + HID Device specification: see + https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c """ def __init__( @@ -52,6 +54,10 @@ class Device: self._last_received_report = None def send_report(self, report: bytearray, report_id: int = None): + """Send an HID report. If the device descriptor specifies zero or one report id's, + you can supply `None` (the default) as the value of ``report_id``. + Otherwise you must specify which report id to use when sending the report. + """ report_id = report_id or self.report_ids[0] device_path = self.get_device_path(report_id) with open(device_path, "rb+") as fd: @@ -90,9 +96,9 @@ class Device: device = ( Path( "%s/functions/hid.usb%s/dev" - % (gadget_root, report_id or self.report_ids[0]) + % (this.gadget_root, report_id or self.report_ids[0]) ) - .read_text() + .read_text(encoding="utf-8") .strip() .split(":")[1] ) @@ -255,7 +261,7 @@ Device.MOUSE = Device( ), usage_page=0x1, usage=0x02, - report_ids=[2], + report_ids=[0x02], in_report_lengths=[4], out_report_lengths=[0], ) @@ -339,13 +345,15 @@ Device.BOOT_KEYBOARD = Device( 0x29, 0x05, # usage maximum (kana) 0x91, - 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x02, # output + # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile) 0x95, 0x01, # report count (1) 0x75, 0x05, # report size (5) 0x91, - 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x01, # output + # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile) 0x95, 0x06, # report count (6) 0x75, @@ -442,7 +450,7 @@ Device.BOOT_MOUSE = Device( ), usage_page=0x1, usage=0x02, - report_ids=[1], + report_ids=[0], in_report_lengths=[4], out_report_lengths=[0], ) @@ -457,29 +465,30 @@ def disable() -> None: as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`. """ try: - Path("%s/UDC" % gadget_root).write_text("") + Path("%s/UDC" % this.gadget_root).write_text("", encoding="utf-8") except FileNotFoundError: pass - for symlink in Path(gadget_root).glob("configs/**/hid.usb*"): + for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"): symlink.unlink() - for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"): + for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"): if strings_file.is_dir(): strings_file.rmdir() - for strings_file in Path(gadget_root).rglob("configs/*/strings/*"): + for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"): if strings_file.is_dir(): strings_file.rmdir() - for config_dir in Path(gadget_root).rglob("configs/*"): + for config_dir in Path(this.gadget_root).rglob("configs/*"): if config_dir.is_dir(): config_dir.rmdir() - for function_dir in Path(gadget_root).rglob("functions/*"): + for function_dir in Path(this.gadget_root).rglob("functions/*"): if function_dir.is_dir(): function_dir.rmdir() try: - Path(gadget_root).rmdir() - except: + Path(this.gadget_root).rmdir() + except FileNotFoundError: pass + this.devices = [] atexit.register(disable) @@ -523,17 +532,13 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: The HID boot device must usually be the first or only device presented by CircuitPython. The HID device will be USB interface number 0. - To make sure it is the first device, disable other USB devices, including CDC and MSC (CIRCUITPY). + To make sure it is the first device, disable other USB devices, including CDC and MSC + (CIRCUITPY). If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython will enter safe mode to report this error. """ - global _boot_device, devices - _boot_device = boot_device + this.boot_device = boot_device - """ Enable usb_hid - shim for https://docs.circuitpython.org/en/latest/shared-bindings/usb_hid/index.html#usb_hid.enable - - """ if len(requested_devices) == 0: disable() return @@ -543,168 +548,178 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: if boot_device == 2: requested_devices = [Device.BOOT_MOUSE] - """ - 1. Creating the gadgets - ----------------------- - - For each gadget to be created its corresponding directory must be created:: - - $ mkdir $CONFIGFS_HOME/usb_gadget/ - - e.g.:: - - $ mkdir $CONFIGFS_HOME/usb_gadget/g1 - - ... - ... - ... - - $ cd $CONFIGFS_HOME/usb_gadget/g1 - - Each gadget needs to have its vendor id and product id specified:: - - $ echo > idVendor - $ echo > idProduct - - A gadget also needs its serial number, manufacturer and product strings. - In order to have a place to store them, a strings subdirectory must be created - for each language, e.g.:: - - $ mkdir strings/0x409 - - Then the strings can be specified:: - - $ echo > strings/0x409/serialnumber - $ echo > strings/0x409/manufacturer - $ echo > strings/0x409/product - """ - Path("%s/functions" % gadget_root).mkdir(parents=True, exist_ok=True) - Path("%s/configs" % gadget_root).mkdir(parents=True, exist_ok=True) - Path("%s/bcdDevice" % gadget_root).write_text("%s" % 1) # Version 1.0.0 - Path("%s/bcdUSB" % gadget_root).write_text("%s" % 0x0200) # USB 2.0 - Path("%s/bDeviceClass" % gadget_root).write_text( - "%s" % 0x00 + # """ + # 1. Creating the gadgets + # ----------------------- + # + # For each gadget to be created its corresponding directory must be created:: + # + # $ mkdir $CONFIGFS_HOME/usb_gadget/ + # + # e.g.:: + # + # $ mkdir $CONFIGFS_HOME/usb_gadget/g1 + # + # ... + # ... + # ... + # + # $ cd $CONFIGFS_HOME/usb_gadget/g1 + # + # Each gadget needs to have its vendor id and product id specified:: + # + # $ echo > idVendor + # $ echo > idProduct + # + # A gadget also needs its serial number, manufacturer and product strings. + # In order to have a place to store them, a strings subdirectory must be created + # for each language, e.g.:: + # + # $ mkdir strings/0x409 + # + # Then the strings can be specified:: + # + # $ echo > strings/0x409/serialnumber + # $ echo > strings/0x409/manufacturer + # $ echo > strings/0x409/product + # """ + Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True) + Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True) + Path("%s/bcdDevice" % this.gadget_root).write_text( + "%s" % 1, encoding="utf-8" + ) # Version 1.0.0 + Path("%s/bcdUSB" % this.gadget_root).write_text( + "%s" % 0x0200, encoding="utf-8" + ) # USB 2.0 + Path("%s/bDeviceClass" % this.gadget_root).write_text( + "%s" % 0x00, encoding="utf-8" ) # multipurpose i guess? - Path("%s/bDeviceProtocol" % gadget_root).write_text("%s" % 0x00) - Path("%s/bDeviceSubClass" % gadget_root).write_text("%s" % 0x00) - Path("%s/bMaxPacketSize0" % gadget_root).write_text("%s" % 0x08) - Path("%s/idProduct" % gadget_root).write_text( - "%s" % 0x0104 + Path("%s/bDeviceProtocol" % this.gadget_root).write_text( + "%s" % 0x00, encoding="utf-8" + ) + Path("%s/bDeviceSubClass" % this.gadget_root).write_text( + "%s" % 0x00, encoding="utf-8" + ) + Path("%s/bMaxPacketSize0" % this.gadget_root).write_text( + "%s" % 0x08, encoding="utf-8" + ) + Path("%s/idProduct" % this.gadget_root).write_text( + "%s" % 0x0104, encoding="utf-8" ) # Multifunction Composite Gadget - Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation - """ - 2. Creating the configurations - ------------------------------ - - Each gadget will consist of a number of configurations, their corresponding - directories must be created: - - $ mkdir configs/. - - where can be any string which is legal in a filesystem and the - is the configuration's number, e.g.:: - - $ mkdir configs/c.1 - - ... - ... - ... - - Each configuration also needs its strings, so a subdirectory must be created - for each language, e.g.:: - - $ mkdir configs/c.1/strings/0x409 - - Then the configuration string can be specified:: - - $ echo > configs/c.1/strings/0x409/configuration - - Some attributes can also be set for a configuration, e.g.:: - - $ echo 120 > configs/c.1/MaxPower - """ - - for i, device in enumerate(requested_devices): - config_root = "%s/configs/device.%s" % (gadget_root, i + 1) + Path("%s/idVendor" % this.gadget_root).write_text( + "%s" % 0x1D6B, encoding="utf-8" + ) # Linux Foundation + # """ + # 2. Creating the configurations + # ------------------------------ + # + # Each gadget will consist of a number of configurations, their corresponding + # directories must be created: + # + # $ mkdir configs/. + # + # where can be any string which is legal in a filesystem and the + # is the configuration's number, e.g.:: + # + # $ mkdir configs/c.1 + # + # ... + # ... + # ... + # + # Each configuration also needs its strings, so a subdirectory must be created + # for each language, e.g.:: + # + # $ mkdir configs/c.1/strings/0x409 + # + # Then the configuration string can be specified:: + # + # $ echo > configs/c.1/strings/0x409/configuration + # + # Some attributes can also be set for a configuration, e.g.:: + # + # $ echo 120 > configs/c.1/MaxPower + # """ + + for device in requested_devices: + config_root = "%s/configs/device.1" % this.gadget_root Path("%s/" % config_root).mkdir(parents=True, exist_ok=True) Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True) Path("%s/strings/0x409/configuration" % config_root).write_text( - "my configuration" + "my configuration", encoding="utf-8" ) - Path("%s/MaxPower" % config_root).write_text("150") - Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080) - devices.append(device) - """ - 3. Creating the functions - ------------------------- - - The gadget will provide some functions, for each function its corresponding - directory must be created:: - - $ mkdir functions/. - - where corresponds to one of allowed function names and instance name - is an arbitrary string allowed in a filesystem, e.g.:: - - $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module() - - ... - ... - ... - - Each function provides its specific set of attributes, with either read-only - or read-write access. Where applicable they need to be written to as - appropriate. - Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. - - """ - - # create functions + Path("%s/MaxPower" % config_root).write_text("150", encoding="utf-8") + Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080, encoding="utf-8") + this.devices.append(device) + # """ + # 3. Creating the functions + # ------------------------- + # + # The gadget will provide some functions, for each function its corresponding + # directory must be created:: + # + # $ mkdir functions/. + # + # where corresponds to one of allowed function names and instance name + # is an arbitrary string allowed in a filesystem, e.g.:: + # + # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module() + # + # ... + # ... + # ... + # + # Each function provides its specific set of attributes, with either read-only + # or read-write access. Where applicable they need to be written to as + # appropriate. + # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. """ for report_index, report_id in enumerate(device.report_ids): - function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id) + function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id) try: Path("%s/" % function_root).mkdir(parents=True) except FileExistsError: continue - Path("%s/protocol" % function_root).write_text("%s" % report_id) + Path("%s/protocol" % function_root).write_text( + "%s" % report_id, encoding="utf-8" + ) Path("%s/report_length" % function_root).write_text( - "%s" % device.in_report_lengths[report_index] + "%s" % device.in_report_lengths[report_index], encoding="utf-8" ) - Path("%s/subclass" % function_root).write_text("%s" % 1) + Path("%s/subclass" % function_root).write_text("%s" % 1, encoding="utf-8") Path("%s/report_desc" % function_root).write_bytes(device.descriptor) - """ - 4. Associating the functions with their configurations - ------------------------------------------------------ - - At this moment a number of gadgets is created, each of which has a number of - configurations specified and a number of functions available. What remains - is specifying which function is available in which configuration (the same - function can be used in multiple configurations). This is achieved with - creating symbolic links:: - - $ ln -s functions/. configs/. - - e.g.:: - - $ ln -s functions/ncm.usb0 configs/c.1 - """ - - Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root) - """ - 5. Enabling the gadget - ---------------------- - Such a gadget must be finally enabled so that the USB host can enumerate it. - - In order to enable the gadget it must be bound to a UDC (USB Device - Controller):: - - $ echo > UDC - - where is one of those found in /sys/class/udc/* - e.g.:: - - $ echo s3c-hsotg > UDC - - """ + # """ + # 4. Associating the functions with their configurations + # ------------------------------------------------------ + # + # At this moment a number of gadgets is created, each of which has a number of + # configurations specified and a number of functions available. What remains + # is specifying which function is available in which configuration (the same + # function can be used in multiple configurations). This is achieved with + # creating symbolic links:: + # + # $ ln -s functions/. configs/. + # + # e.g.:: + # + # $ ln -s functions/ncm.usb0 configs/c.1 """ + try: + Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to( + function_root + ) + except FileNotFoundError: + pass + # """ 5. Enabling the gadget + # ---------------------- + # Such a gadget must be finally enabled so that the USB host can enumerate it. + # + # In order to enable the gadget it must be bound to a UDC (USB Device + # Controller):: + # + # $ echo > UDC + # + # where is one of those found in /sys/class/udc/* + # e.g.:: + # + # $ echo s3c-hsotg > UDC """ udc = next(Path("/sys/class/udc/").glob("*")) - Path("%s/UDC" % gadget_root).write_text("%s" % udc.name) + Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name, encoding="utf-8")