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