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