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