]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/usb_hid.py
solve issues with multiple hid devices on one gadget
[Adafruit_Blinka-hackapet.git] / src / usb_hid.py
1 """
2 `usb_hid` - support for usb hid devices via usb_gadget driver
3 ===========================================================
4 See `CircuitPython:usb_hid` in CircuitPython for more details.
5 For now using report ids in the descriptor
6
7 # regarding usb_gadget see https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
8 * Author(s): Björn Bösel
9 """
10
11 from typing import Sequence
12 from pathlib import Path
13 import os
14 import atexit
15 import sys
16
17 for module in ["dwc2", "libcomposite"]:
18     if Path("/proc/modules").read_text().find(module) == -1:
19         raise Exception(
20             "%s module not present in your kernel. did you insmod it?" % module
21         )
22 this = sys.modules[__name__]
23
24 this.gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
25 this.boot_device = 0
26 this.devices = []
27
28
29 class Device:
30     """
31     HID Device specification: see
32     https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
33     """
34
35     def __init__(
36         self,
37         *,
38         descriptor: bytes,
39         usage_page: int,
40         usage: int,
41         report_ids: Sequence[int],
42         in_report_lengths: Sequence[int],
43         out_report_lengths: Sequence[int],
44     ) -> None:
45         self.out_report_lengths = out_report_lengths
46         self.in_report_lengths = in_report_lengths
47         self.report_ids = report_ids
48         self.usage = usage
49         self.usage_page = usage_page
50         self.descriptor = descriptor
51         self._last_received_report = None
52
53     def send_report(self, report: bytearray, report_id: int = None):
54         """Send an HID report. If the device descriptor specifies zero or one report id's,
55         you can supply `None` (the default) as the value of ``report_id``.
56         Otherwise you must specify which report id to use when sending the report.
57         """
58         report_id = report_id or self.report_ids[0]
59         device_path = self.get_device_path(report_id)
60         with open(device_path, "rb+") as fd:
61             if report_id > 0:
62                 report = bytearray(report_id.to_bytes(1, "big")) + report
63             fd.write(report)
64
65     @property
66     def last_received_report(
67         self,
68     ) -> bytes:
69         """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
70         Same as `get_last_received_report()` with no argument.
71
72         Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
73         """
74         return self.get_last_received_report()
75
76     def get_last_received_report(self, report_id=None) -> bytes:
77         """Get the last received HID OUT or feature report for the given report ID.
78         The report ID may be omitted if there is no report ID, or only one report ID.
79         Return `None` if nothing received.
80         """
81         device_path = self.get_device_path(report_id or self.report_ids[0])
82         with open(device_path, "rb+") as fd:
83             os.set_blocking(fd.fileno(), False)
84             report = fd.read(self.out_report_lengths[0])
85             if report is not None:
86                 self._last_received_report = report
87         return self._last_received_report
88
89     def get_device_path(self, report_id):
90         """
91         translates the /dev/hidg device from the report id
92         """
93         device = (
94             Path(
95                 "%s/functions/hid.usb%s/dev"
96                 % (this.gadget_root, report_id or self.report_ids[0])
97             )
98             .read_text()
99             .strip()
100             .split(":")[1]
101         )
102         device_path = "/dev/hidg%s" % device
103         return device_path
104
105     KEYBOARD = None
106     MOUSE = None
107     CONSUMER_CONTROL = None
108
109
110 Device.KEYBOARD = Device(
111     descriptor=bytes(
112         (
113             0x05,
114             0x01,  # usage page (generic desktop ctrls)
115             0x09,
116             0x06,  # usage (keyboard)
117             0xA1,
118             0x01,  # collection (application)
119             0x85,
120             0x01,  # Report ID (1)
121             0x05,
122             0x07,  # usage page (kbrd/keypad)
123             0x19,
124             0xE0,  # usage minimum (0xe0)
125             0x29,
126             0xE7,  # usage maximum (0xe7)
127             0x15,
128             0x00,  # logical minimum (0)
129             0x25,
130             0x01,  # logical maximum (1)
131             0x75,
132             0x01,  # report size (1)
133             0x95,
134             0x08,  # report count (8)
135             0x81,
136             0x02,  # input (data,var,abs,no wrap,linear,preferred state,no null position)
137             0x95,
138             0x01,  # report count (1)
139             0x75,
140             0x08,  # report size (8)
141             0x81,
142             0x01,  # input (const,array,abs,no wrap,linear,preferred state,no null position)
143             0x95,
144             0x03,  # report count (3)
145             0x75,
146             0x01,  # report size (1)
147             0x05,
148             0x08,  # usage page (leds)
149             0x19,
150             0x01,  # usage minimum (num lock)
151             0x29,
152             0x05,  # usage maximum (kana)
153             0x91,
154             0x02,  # output
155             # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
156             0x95,
157             0x01,  # report count (1)
158             0x75,
159             0x05,  # report size (5)
160             0x91,
161             0x01,  # output
162             # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
163             0x95,
164             0x06,  # report count (6)
165             0x75,
166             0x08,  # report size (8)
167             0x15,
168             0x00,  # logical minimum (0)
169             0x26,
170             0xFF,
171             0x00,  # logical maximum (255)
172             0x05,
173             0x07,  # usage page (kbrd/keypad)
174             0x19,
175             0x00,  # usage minimum (0x00)
176             0x2A,
177             0xFF,
178             0x00,  # usage maximum (0xff)
179             0x81,
180             0x00,  # input (data,array,abs,no wrap,linear,preferred state,no null position)
181             0xC0,  # end collection
182         )
183     ),
184     usage_page=0x1,
185     usage=0x6,
186     report_ids=[0x1],
187     in_report_lengths=[8],
188     out_report_lengths=[1],
189 )
190 Device.MOUSE = Device(
191     descriptor=bytes(
192         (
193             0x05,
194             0x01,  # Usage Page (Generic Desktop Ctrls)
195             0x09,
196             0x02,  # Usage (Mouse)
197             0xA1,
198             0x01,  # Collection (Application)
199             0x85,
200             0x02,  # Report ID (2)
201             0x09,
202             0x01,  # Usage (Pointer)
203             0xA1,
204             0x00,  # Collection (Physical)
205             0x05,
206             0x09,  # Usage Page (Button)
207             0x19,
208             0x01,  # Usage Minimum (0x01)
209             0x29,
210             0x05,  # Usage Maximum (0x05)
211             0x15,
212             0x00,  # Logical Minimum (0)
213             0x25,
214             0x01,  # Logical Maximum (1)
215             0x95,
216             0x05,  # Report Count (5)
217             0x75,
218             0x01,  # Report Size (1)
219             0x81,
220             0x02,  # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
221             0x95,
222             0x01,  # Report Count (1)
223             0x75,
224             0x03,  # Report Size (3)
225             0x81,
226             0x01,  # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
227             0x05,
228             0x01,  # Usage Page (Generic Desktop Ctrls)
229             0x09,
230             0x30,  # Usage (X)
231             0x09,
232             0x31,  # Usage (Y)
233             0x15,
234             0x81,  # Logical Minimum (-127)
235             0x25,
236             0x7F,  # Logical Maximum (127)
237             0x75,
238             0x08,  # Report Size (8)
239             0x95,
240             0x02,  # Report Count (2)
241             0x81,
242             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
243             0x09,
244             0x38,  # Usage (Wheel)
245             0x15,
246             0x81,  # Logical Minimum (-127)
247             0x25,
248             0x7F,  # Logical Maximum (127)
249             0x75,
250             0x08,  # Report Size (8)
251             0x95,
252             0x01,  # Report Count (1)
253             0x81,
254             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
255             0xC0,  # End Collection
256             0xC0,  # End Collection
257         )
258     ),
259     usage_page=0x1,
260     usage=0x02,
261     report_ids=[0x02],
262     in_report_lengths=[4],
263     out_report_lengths=[0],
264 )
265
266 Device.CONSUMER_CONTROL = Device(
267     descriptor=bytes(
268         (
269             0x05,
270             0x0C,  # Usage Page (Consumer)
271             0x09,
272             0x01,  # Usage (Consumer Control)
273             0xA1,
274             0x01,  # Collection (Application)
275             0x85,
276             0x03,  # Report ID (3)
277             0x75,
278             0x10,  # Report Size (16)
279             0x95,
280             0x01,  # Report Count (1)
281             0x15,
282             0x01,  # Logical Minimum (1)
283             0x26,
284             0x8C,
285             0x02,  # Logical Maximum (652)
286             0x19,
287             0x01,  # Usage Minimum (Consumer Control)
288             0x2A,
289             0x8C,
290             0x02,  # Usage Maximum (AC Send)
291             0x81,
292             0x00,  # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
293             0xC0,  # End Collection
294         )
295     ),
296     usage_page=0x0C,
297     usage=0x01,
298     report_ids=[3],
299     in_report_lengths=[2],
300     out_report_lengths=[0],
301 )
302
303 Device.BOOT_KEYBOARD = Device(
304     descriptor=bytes(
305         (
306             0x05,
307             0x01,  # usage page (generic desktop ctrls)
308             0x09,
309             0x06,  # usage (keyboard)
310             0xA1,
311             0x01,  # collection (application)
312             0x05,
313             0x07,  # usage page (kbrd/keypad)
314             0x19,
315             0xE0,  # usage minimum (0xe0)
316             0x29,
317             0xE7,  # usage maximum (0xe7)
318             0x15,
319             0x00,  # logical minimum (0)
320             0x25,
321             0x01,  # logical maximum (1)
322             0x75,
323             0x01,  # report size (1)
324             0x95,
325             0x08,  # report count (8)
326             0x81,
327             0x02,  # input (data,var,abs,no wrap,linear,preferred state,no null position)
328             0x95,
329             0x01,  # report count (1)
330             0x75,
331             0x08,  # report size (8)
332             0x81,
333             0x01,  # input (const,array,abs,no wrap,linear,preferred state,no null position)
334             0x95,
335             0x03,  # report count (3)
336             0x75,
337             0x01,  # report size (1)
338             0x05,
339             0x08,  # usage page (leds)
340             0x19,
341             0x01,  # usage minimum (num lock)
342             0x29,
343             0x05,  # usage maximum (kana)
344             0x91,
345             0x02,  # output
346             # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
347             0x95,
348             0x01,  # report count (1)
349             0x75,
350             0x05,  # report size (5)
351             0x91,
352             0x01,  # output
353             # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
354             0x95,
355             0x06,  # report count (6)
356             0x75,
357             0x08,  # report size (8)
358             0x15,
359             0x00,  # logical minimum (0)
360             0x26,
361             0xFF,
362             0x00,  # logical maximum (255)
363             0x05,
364             0x07,  # usage page (kbrd/keypad)
365             0x19,
366             0x00,  # usage minimum (0x00)
367             0x2A,
368             0xFF,
369             0x00,  # usage maximum (0xff)
370             0x81,
371             0x00,  # input (data,array,abs,no wrap,linear,preferred state,no null position)
372             0xC0,  # end collection
373         )
374     ),
375     usage_page=0x1,
376     usage=0x6,
377     report_ids=[0x0],
378     in_report_lengths=[8],
379     out_report_lengths=[1],
380 )
381 Device.BOOT_MOUSE = Device(
382     descriptor=bytes(
383         (
384             0x05,
385             0x01,  # Usage Page (Generic Desktop Ctrls)
386             0x09,
387             0x02,  # Usage (Mouse)
388             0xA1,
389             0x01,  # Collection (Application)
390             0x09,
391             0x01,  # Usage (Pointer)
392             0xA1,
393             0x00,  # Collection (Physical)
394             0x05,
395             0x09,  # Usage Page (Button)
396             0x19,
397             0x01,  # Usage Minimum (0x01)
398             0x29,
399             0x05,  # Usage Maximum (0x05)
400             0x15,
401             0x00,  # Logical Minimum (0)
402             0x25,
403             0x01,  # Logical Maximum (1)
404             0x95,
405             0x05,  # Report Count (5)
406             0x75,
407             0x01,  # Report Size (1)
408             0x81,
409             0x02,  # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
410             0x95,
411             0x01,  # Report Count (1)
412             0x75,
413             0x03,  # Report Size (3)
414             0x81,
415             0x01,  # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
416             0x05,
417             0x01,  # Usage Page (Generic Desktop Ctrls)
418             0x09,
419             0x30,  # Usage (X)
420             0x09,
421             0x31,  # Usage (Y)
422             0x15,
423             0x81,  # Logical Minimum (-127)
424             0x25,
425             0x7F,  # Logical Maximum (127)
426             0x75,
427             0x08,  # Report Size (8)
428             0x95,
429             0x02,  # Report Count (2)
430             0x81,
431             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
432             0x09,
433             0x38,  # Usage (Wheel)
434             0x15,
435             0x81,  # Logical Minimum (-127)
436             0x25,
437             0x7F,  # Logical Maximum (127)
438             0x75,
439             0x08,  # Report Size (8)
440             0x95,
441             0x01,  # Report Count (1)
442             0x81,
443             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
444             0xC0,  # End Collection
445             0xC0,  # End Collection
446         )
447     ),
448     usage_page=0x1,
449     usage=0x02,
450     report_ids=[0],
451     in_report_lengths=[4],
452     out_report_lengths=[0],
453 )
454
455
456 def disable() -> None:
457     """Do not present any USB HID devices to the host computer.
458     Can be called in ``boot.py``, before USB is connected.
459     The HID composite device is normally enabled by default,
460     but on some boards with limited endpoints, including STM32F4,
461     it is disabled by default. You must turn off another USB device such
462     as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
463     """
464     try:
465         Path("%s/UDC" % this.gadget_root).write_text("")
466     except FileNotFoundError:
467         pass
468     for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"):
469         symlink.unlink()
470
471     for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"):
472         if strings_file.is_dir():
473             strings_file.rmdir()
474
475     for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"):
476         if strings_file.is_dir():
477             strings_file.rmdir()
478     for config_dir in Path(this.gadget_root).rglob("configs/*"):
479         if config_dir.is_dir():
480             config_dir.rmdir()
481     for function_dir in Path(this.gadget_root).rglob("functions/*"):
482         if function_dir.is_dir():
483             function_dir.rmdir()
484     try:
485         Path(this.gadget_root).rmdir()
486     except FileNotFoundError:
487         pass
488     this.devices = []
489
490
491 atexit.register(disable)
492
493
494 def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
495     """Specify which USB HID devices that will be available.
496     Can be called in ``boot.py``, before USB is connected.
497
498     :param Sequence devices: `Device` objects.
499       If `devices` is empty, HID is disabled. The order of the ``Devices``
500       may matter to the host. For instance, for MacOS, put the mouse device
501       before any Gamepad or Digitizer HID device or else it will not work.
502     :param int boot_device: If non-zero, inform the host that support for a
503       a boot HID device is available.
504       If ``boot_device=1``, a boot keyboard is available.
505       If ``boot_device=2``, a boot mouse is available. No other values are allowed.
506       See below.
507
508     If you enable too many devices at once, you will run out of USB endpoints.
509     The number of available endpoints varies by microcontroller.
510     CircuitPython will go into safe mode after running ``boot.py`` to inform you if
511     not enough endpoints are available.
512
513     **Boot Devices**
514
515     Boot devices implement a fixed, predefined report descriptor, defined in
516     https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
517     can request to use the boot device if the USB device says it is available.
518     Usually only a BIOS or other kind of limited-functionality
519     host needs boot keyboard support.
520
521     For example, to make a boot keyboard available, you can use this code::
522
523       usb_hid.enable((Device.KEYBOARD), boot_device=1)  # 1 for a keyboard
524
525     If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
526     will be ignored, and the predefined report descriptor will be used.
527     But if the host does not request the boot keyboard,
528     the descriptor provided by `Device.KEYBOARD` will be used.
529
530     The HID boot device must usually be the first or only device presented by CircuitPython.
531     The HID device will be USB interface number 0.
532     To make sure it is the first device, disable other USB devices, including CDC and MSC
533     (CIRCUITPY).
534     If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
535     will enter safe mode to report this error.
536     """
537     this.boot_device = boot_device
538
539     if len(requested_devices) == 0:
540         disable()
541         return
542
543     if boot_device == 1:
544         requested_devices = [Device.BOOT_KEYBOARD]
545     if boot_device == 2:
546         requested_devices = [Device.BOOT_MOUSE]
547
548     # """
549     # 1. Creating the gadgets
550     # -----------------------
551     #
552     # For each gadget to be created its corresponding directory must be created::
553     #
554     #     $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
555     #
556     # e.g.::
557     #
558     #     $ mkdir $CONFIGFS_HOME/usb_gadget/g1
559     #
560     #     ...
561     #     ...
562     #     ...
563     #
564     #     $ cd $CONFIGFS_HOME/usb_gadget/g1
565     #
566     # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
567     #
568     #     $ echo <VID> > idVendor
569     #     $ echo <PID> > idProduct
570     #
571     # A gadget also needs its serial number, manufacturer and product strings.
572     # In order to have a place to store them, a strings subdirectory must be created
573     # for each language, e.g.::
574     #
575     #     $ mkdir strings/0x409
576     #
577     # Then the strings can be specified::
578     #
579     #     $ echo <serial number> > strings/0x409/serialnumber
580     #     $ echo <manufacturer> > strings/0x409/manufacturer
581     #     $ echo <product> > strings/0x409/product
582     # """
583     Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True)
584     Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True)
585     Path("%s/bcdDevice" % this.gadget_root).write_text("%s" % 1)  # Version 1.0.0
586     Path("%s/bcdUSB" % this.gadget_root).write_text("%s" % 0x0200)  # USB 2.0
587     Path("%s/bDeviceClass" % this.gadget_root).write_text(
588         "%s" % 0x00
589     )  # multipurpose i guess?
590     Path("%s/bDeviceProtocol" % this.gadget_root).write_text("%s" % 0x00)
591     Path("%s/bDeviceSubClass" % this.gadget_root).write_text("%s" % 0x00)
592     Path("%s/bMaxPacketSize0" % this.gadget_root).write_text("%s" % 0x08)
593     Path("%s/idProduct" % this.gadget_root).write_text(
594         "%s" % 0x0104
595     )  # Multifunction Composite Gadget
596     Path("%s/idVendor" % this.gadget_root).write_text("%s" % 0x1D6B)  # Linux Foundation
597     # """
598     # 2. Creating the configurations
599     # ------------------------------
600     #
601     # Each gadget will consist of a number of configurations, their corresponding
602     # directories must be created:
603     #
604     # $ mkdir configs/<name>.<number>
605     #
606     # where <name> can be any string which is legal in a filesystem and the
607     # <number> is the configuration's number, e.g.::
608     #
609     #     $ mkdir configs/c.1
610     #
611     #     ...
612     #     ...
613     #     ...
614     #
615     # Each configuration also needs its strings, so a subdirectory must be created
616     # for each language, e.g.::
617     #
618     #     $ mkdir configs/c.1/strings/0x409
619     #
620     # Then the configuration string can be specified::
621     #
622     #     $ echo <configuration> > configs/c.1/strings/0x409/configuration
623     #
624     # Some attributes can also be set for a configuration, e.g.::
625     #
626     #     $ echo 120 > configs/c.1/MaxPower
627     #     """
628
629     for device in requested_devices:
630         config_root = "%s/configs/device.1" % this.gadget_root
631         Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
632         Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
633         Path("%s/strings/0x409/configuration" % config_root).write_text(
634             "my configuration"
635         )
636         Path("%s/MaxPower" % config_root).write_text("150")
637         Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
638         this.devices.append(device)
639         # """
640         # 3. Creating the functions
641         # -------------------------
642         #
643         # The gadget will provide some functions, for each function its corresponding
644         # directory must be created::
645         #
646         #     $ mkdir functions/<name>.<instance name>
647         #
648         # where <name> corresponds to one of allowed function names and instance name
649         # is an arbitrary string allowed in a filesystem, e.g.::
650         #
651         #   $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
652         #
653         #   ...
654         #   ...
655         #   ...
656         #
657         # Each function provides its specific set of attributes, with either read-only
658         # or read-write access. Where applicable they need to be written to as
659         # appropriate.
660         # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.  """
661         for report_index, report_id in enumerate(device.report_ids):
662             function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id)
663             try:
664                 Path("%s/" % function_root).mkdir(parents=True)
665             except FileExistsError:
666                 continue
667             Path("%s/protocol" % function_root).write_text("%s" % report_id)
668             Path("%s/report_length" % function_root).write_text(
669                 "%s" % device.in_report_lengths[report_index]
670             )
671             Path("%s/subclass" % function_root).write_text("%s" % 1)
672             Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
673             # """
674             # 4. Associating the functions with their configurations
675             # ------------------------------------------------------
676             #
677             # At this moment a number of gadgets is created, each of which has a number of
678             # configurations specified and a number of functions available. What remains
679             # is specifying which function is available in which configuration (the same
680             # function can be used in multiple configurations). This is achieved with
681             # creating symbolic links::
682             #
683             #     $ ln -s functions/<name>.<instance name> configs/<name>.<number>
684             #
685             # e.g.::
686             #
687             #     $ ln -s functions/ncm.usb0 configs/c.1  """
688             try:
689                 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(
690                     function_root
691                 )
692             except:
693                 pass
694     # """ 5. Enabling the gadget
695     # ----------------------
696     # Such a gadget must be finally enabled so that the USB host can enumerate it.
697     #
698     # In order to enable the gadget it must be bound to a UDC (USB Device
699     # Controller)::
700     #
701     #     $ echo <udc name> > UDC
702     #
703     # where <udc name> is one of those found in /sys/class/udc/*
704     # e.g.::
705     #
706     # $ echo s3c-hsotg > UDC  """
707     udc = next(Path("/sys/class/udc/").glob("*"))
708     Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name)