]> Repositories - Adafruit_Blinka-hackapet.git/blobdiff - src/usb_hid.py
solve issues with multiple hid devices on one gadget
[Adafruit_Blinka-hackapet.git] / src / usb_hid.py
index a893d837cee0f3ab93330404deff509ad0700f21..f7ecf97e4b8169887610e2243733557fc59f9c9d 100644 (file)
@@ -12,25 +12,24 @@ from typing import Sequence
 from pathlib import Path
 import os
 import atexit
 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(
             "%s module not present in your kernel. did you insmod it?" % module
         )
 
 for module in ["dwc2", "libcomposite"]:
     if Path("/proc/modules").read_text().find(module) == -1:
         raise Exception(
             "%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:
     """
 
 
 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__(
     """
 
     def __init__(
@@ -52,6 +51,10 @@ class Device:
         self._last_received_report = None
 
     def send_report(self, report: bytearray, report_id: int = None):
         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:
         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,7 +93,7 @@ class Device:
         device = (
             Path(
                 "%s/functions/hid.usb%s/dev"
         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()
             .strip()
             )
             .read_text()
             .strip()
@@ -255,7 +258,7 @@ Device.MOUSE = Device(
     ),
     usage_page=0x1,
     usage=0x02,
     ),
     usage_page=0x1,
     usage=0x02,
-    report_ids=[2],
+    report_ids=[0x02],
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
@@ -339,13 +342,15 @@ Device.BOOT_KEYBOARD = Device(
             0x29,
             0x05,  # usage maximum (kana)
             0x91,
             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,
             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,
             0x95,
             0x06,  # report count (6)
             0x75,
@@ -442,7 +447,7 @@ Device.BOOT_MOUSE = Device(
     ),
     usage_page=0x1,
     usage=0x02,
     ),
     usage_page=0x1,
     usage=0x02,
-    report_ids=[1],
+    report_ids=[0],
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
@@ -457,29 +462,30 @@ def disable() -> None:
     as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
     """
     try:
     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("")
     except FileNotFoundError:
         pass
     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()
 
         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()
 
         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()
         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()
         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:
         if function_dir.is_dir():
             function_dir.rmdir()
     try:
-        Path(gadget_root).rmdir()
-    except:
+        Path(this.gadget_root).rmdir()
+    except FileNotFoundError:
         pass
         pass
+    this.devices = []
 
 
 atexit.register(disable)
 
 
 atexit.register(disable)
@@ -523,17 +529,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.
 
     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.
     """
     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
     if len(requested_devices) == 0:
         disable()
         return
@@ -543,89 +545,89 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
     if boot_device == 2:
         requested_devices = [Device.BOOT_MOUSE]
 
     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/<gadget name>
-
-    e.g.::
-
-        $ mkdir $CONFIGFS_HOME/usb_gadget/g1
-
-        ...
-        ...
-        ...
-
-        $ cd $CONFIGFS_HOME/usb_gadget/g1
-
-    Each gadget needs to have its vendor id <VID> and product id <PID> specified::
-
-        $ echo <VID> > idVendor
-        $ echo <PID> > 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 <serial number> > strings/0x409/serialnumber
-        $ echo <manufacturer> > strings/0x409/manufacturer
-        $ echo <product> > 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(
+    """
+    1. Creating the gadgets
+    -----------------------
+    #
+    For each gadget to be created its corresponding directory must be created::
+    #
+        $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
+    #
+    e.g.::
+    #
+        $ mkdir $CONFIGFS_HOME/usb_gadget/g1
+    #
+        ...
+        ...
+        ...
+    #
+        $ cd $CONFIGFS_HOME/usb_gadget/g1
+    #
+    Each gadget needs to have its vendor id <VID> and product id <PID> specified::
+    #
+        $ echo <VID> > idVendor
+        $ echo <PID> > 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 <serial number> > strings/0x409/serialnumber
+        $ echo <manufacturer> > strings/0x409/manufacturer
+        $ echo <product> > 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)  # Version 1.0.0
+    Path("%s/bcdUSB" % this.gadget_root).write_text("%s" % 0x0200)  # USB 2.0
+    Path("%s/bDeviceClass" % this.gadget_root).write_text(
         "%s" % 0x00
     )  # multipurpose i guess?
         "%s" % 0x00
     )  # 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(
+    Path("%s/bDeviceProtocol" % this.gadget_root).write_text("%s" % 0x00)
+    Path("%s/bDeviceSubClass" % this.gadget_root).write_text("%s" % 0x00)
+    Path("%s/bMaxPacketSize0" % this.gadget_root).write_text("%s" % 0x08)
+    Path("%s/idProduct" % this.gadget_root).write_text(
         "%s" % 0x0104
     )  # Multifunction Composite Gadget
         "%s" % 0x0104
     )  # 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/<name>.<number>
-
-    where <name> can be any string which is legal in a filesystem and the
-    <number> 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 <configuration> > 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)  # Linux Foundation
+    """
+    2. Creating the configurations
+    ------------------------------
+    #
+    Each gadget will consist of a number of configurations, their corresponding
+    directories must be created:
+    #
+    $ mkdir configs/<name>.<number>
+    #
+    where <name> can be any string which is legal in a filesystem and the
+    <number> 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 <configuration> > 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(
         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(
@@ -633,35 +635,31 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
         )
         Path("%s/MaxPower" % config_root).write_text("150")
         Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
         )
         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/<name>.<instance name>
-
-        where <name> 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
+        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/<name>.<instance name>
+        #
+        # where <name> 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):
         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:
             try:
                 Path("%s/" % function_root).mkdir(parents=True)
             except FileExistsError:
@@ -672,39 +670,39 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
             )
             Path("%s/subclass" % function_root).write_text("%s" % 1)
             Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
             )
             Path("%s/subclass" % function_root).write_text("%s" % 1)
             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/<name>.<instance name> configs/<name>.<number>
-
-            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 name> > UDC
-
-    where <udc name> 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/<name>.<instance name> configs/<name>.<number>
+            #
+            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:
+                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 name> > UDC
+    #
+    # where <udc name> is one of those found in /sys/class/udc/*
+    # e.g.::
+    #
+    # $ echo s3c-hsotg > UDC  """
     udc = next(Path("/sys/class/udc/").glob("*"))
     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)