1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
3 # SPDX-License-Identifier: MIT
5 `usb_hid` - support for usb hid devices via usb_gadget driver
6 ===========================================================
7 See `CircuitPython:usb_hid` in CircuitPython for more details.
8 For now using report ids in the descriptor
10 # regarding usb_gadget see https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
11 * Author(s): Björn Bösel
14 from typing import Sequence
15 from pathlib import Path
20 for module in ["dwc2", "libcomposite"]:
21 if Path("/proc/modules").read_text(encoding="utf-8").find(module) == -1:
22 raise Exception( # pylint: disable=broad-exception-raised
23 "%s module not present in your kernel. did you insmod it?" % module
25 this = sys.modules[__name__]
27 this.gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
34 HID Device specification: see
35 https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
44 report_ids: Sequence[int],
45 in_report_lengths: Sequence[int],
46 out_report_lengths: Sequence[int],
48 self.out_report_lengths = out_report_lengths
49 self.in_report_lengths = in_report_lengths
50 self.report_ids = report_ids
52 self.usage_page = usage_page
53 self.descriptor = descriptor
54 self._last_received_report = None
56 def send_report(self, report: bytearray, report_id: int = None):
57 """Send an HID report. If the device descriptor specifies zero or one report id's,
58 you can supply `None` (the default) as the value of ``report_id``.
59 Otherwise you must specify which report id to use when sending the report.
61 report_id = report_id or self.report_ids[0]
62 device_path = self.get_device_path(report_id)
63 with open(device_path, "rb+") as fd:
65 report = bytearray(report_id.to_bytes(1, "big")) + report
69 def last_received_report(
72 """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
73 Same as `get_last_received_report()` with no argument.
75 Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
77 return self.get_last_received_report()
79 def get_last_received_report(self, report_id=None) -> bytes:
80 """Get the last received HID OUT or feature report for the given report ID.
81 The report ID may be omitted if there is no report ID, or only one report ID.
82 Return `None` if nothing received.
84 device_path = self.get_device_path(report_id or self.report_ids[0])
85 with open(device_path, "rb+") as fd:
86 os.set_blocking(fd.fileno(), False)
87 report = fd.read(self.out_report_lengths[0])
88 if report is not None:
89 self._last_received_report = report
90 return self._last_received_report
92 def get_device_path(self, report_id):
94 translates the /dev/hidg device from the report id
98 "%s/functions/hid.usb%s/dev"
99 % (this.gadget_root, report_id or self.report_ids[0])
101 .read_text(encoding="utf-8")
105 device_path = "/dev/hidg%s" % device
110 CONSUMER_CONTROL = None
113 Device.KEYBOARD = Device(
117 0x01, # usage page (generic desktop ctrls)
119 0x06, # usage (keyboard)
121 0x01, # collection (application)
123 0x01, # Report ID (1)
125 0x07, # usage page (kbrd/keypad)
127 0xE0, # usage minimum (0xe0)
129 0xE7, # usage maximum (0xe7)
131 0x00, # logical minimum (0)
133 0x01, # logical maximum (1)
135 0x01, # report size (1)
137 0x08, # report count (8)
139 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
141 0x01, # report count (1)
143 0x08, # report size (8)
145 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
147 0x03, # report count (3)
149 0x01, # report size (1)
151 0x08, # usage page (leds)
153 0x01, # usage minimum (num lock)
155 0x05, # usage maximum (kana)
158 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
160 0x01, # report count (1)
162 0x05, # report size (5)
165 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
167 0x06, # report count (6)
169 0x08, # report size (8)
171 0x00, # logical minimum (0)
174 0x00, # logical maximum (255)
176 0x07, # usage page (kbrd/keypad)
178 0x00, # usage minimum (0x00)
181 0x00, # usage maximum (0xff)
183 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
184 0xC0, # end collection
190 in_report_lengths=[8],
191 out_report_lengths=[1],
193 Device.MOUSE = Device(
197 0x01, # Usage Page (Generic Desktop Ctrls)
199 0x02, # Usage (Mouse)
201 0x01, # Collection (Application)
203 0x02, # Report ID (2)
205 0x01, # Usage (Pointer)
207 0x00, # Collection (Physical)
209 0x09, # Usage Page (Button)
211 0x01, # Usage Minimum (0x01)
213 0x05, # Usage Maximum (0x05)
215 0x00, # Logical Minimum (0)
217 0x01, # Logical Maximum (1)
219 0x05, # Report Count (5)
221 0x01, # Report Size (1)
223 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
225 0x01, # Report Count (1)
227 0x03, # Report Size (3)
229 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
231 0x01, # Usage Page (Generic Desktop Ctrls)
237 0x81, # Logical Minimum (-127)
239 0x7F, # Logical Maximum (127)
241 0x08, # Report Size (8)
243 0x02, # Report Count (2)
245 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
247 0x38, # Usage (Wheel)
249 0x81, # Logical Minimum (-127)
251 0x7F, # Logical Maximum (127)
253 0x08, # Report Size (8)
255 0x01, # Report Count (1)
257 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
258 0xC0, # End Collection
259 0xC0, # End Collection
265 in_report_lengths=[4],
266 out_report_lengths=[0],
269 Device.CONSUMER_CONTROL = Device(
273 0x0C, # Usage Page (Consumer)
275 0x01, # Usage (Consumer Control)
277 0x01, # Collection (Application)
279 0x03, # Report ID (3)
281 0x10, # Report Size (16)
283 0x01, # Report Count (1)
285 0x01, # Logical Minimum (1)
288 0x02, # Logical Maximum (652)
290 0x01, # Usage Minimum (Consumer Control)
293 0x02, # Usage Maximum (AC Send)
295 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
296 0xC0, # End Collection
302 in_report_lengths=[2],
303 out_report_lengths=[0],
306 Device.BOOT_KEYBOARD = Device(
310 0x01, # usage page (generic desktop ctrls)
312 0x06, # usage (keyboard)
314 0x01, # collection (application)
316 0x07, # usage page (kbrd/keypad)
318 0xE0, # usage minimum (0xe0)
320 0xE7, # usage maximum (0xe7)
322 0x00, # logical minimum (0)
324 0x01, # logical maximum (1)
326 0x01, # report size (1)
328 0x08, # report count (8)
330 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
332 0x01, # report count (1)
334 0x08, # report size (8)
336 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
338 0x03, # report count (3)
340 0x01, # report size (1)
342 0x08, # usage page (leds)
344 0x01, # usage minimum (num lock)
346 0x05, # usage maximum (kana)
349 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
351 0x01, # report count (1)
353 0x05, # report size (5)
356 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
358 0x06, # report count (6)
360 0x08, # report size (8)
362 0x00, # logical minimum (0)
365 0x00, # logical maximum (255)
367 0x07, # usage page (kbrd/keypad)
369 0x00, # usage minimum (0x00)
372 0x00, # usage maximum (0xff)
374 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
375 0xC0, # end collection
381 in_report_lengths=[8],
382 out_report_lengths=[1],
384 Device.BOOT_MOUSE = Device(
388 0x01, # Usage Page (Generic Desktop Ctrls)
390 0x02, # Usage (Mouse)
392 0x01, # Collection (Application)
394 0x01, # Usage (Pointer)
396 0x00, # Collection (Physical)
398 0x09, # Usage Page (Button)
400 0x01, # Usage Minimum (0x01)
402 0x05, # Usage Maximum (0x05)
404 0x00, # Logical Minimum (0)
406 0x01, # Logical Maximum (1)
408 0x05, # Report Count (5)
410 0x01, # Report Size (1)
412 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
414 0x01, # Report Count (1)
416 0x03, # Report Size (3)
418 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
420 0x01, # Usage Page (Generic Desktop Ctrls)
426 0x81, # Logical Minimum (-127)
428 0x7F, # Logical Maximum (127)
430 0x08, # Report Size (8)
432 0x02, # Report Count (2)
434 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
436 0x38, # Usage (Wheel)
438 0x81, # Logical Minimum (-127)
440 0x7F, # Logical Maximum (127)
442 0x08, # Report Size (8)
444 0x01, # Report Count (1)
446 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
447 0xC0, # End Collection
448 0xC0, # End Collection
454 in_report_lengths=[4],
455 out_report_lengths=[0],
459 def disable() -> None:
460 """Do not present any USB HID devices to the host computer.
461 Can be called in ``boot.py``, before USB is connected.
462 The HID composite device is normally enabled by default,
463 but on some boards with limited endpoints, including STM32F4,
464 it is disabled by default. You must turn off another USB device such
465 as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
468 Path("%s/UDC" % this.gadget_root).write_text("", encoding="utf-8")
469 except FileNotFoundError:
471 for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"):
474 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"):
475 if strings_file.is_dir():
478 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"):
479 if strings_file.is_dir():
481 for config_dir in Path(this.gadget_root).rglob("configs/*"):
482 if config_dir.is_dir():
484 for function_dir in Path(this.gadget_root).rglob("functions/*"):
485 if function_dir.is_dir():
488 Path(this.gadget_root).rmdir()
489 except FileNotFoundError:
494 atexit.register(disable)
497 def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
498 """Specify which USB HID devices that will be available.
499 Can be called in ``boot.py``, before USB is connected.
501 :param Sequence devices: `Device` objects.
502 If `devices` is empty, HID is disabled. The order of the ``Devices``
503 may matter to the host. For instance, for MacOS, put the mouse device
504 before any Gamepad or Digitizer HID device or else it will not work.
505 :param int boot_device: If non-zero, inform the host that support for a
506 a boot HID device is available.
507 If ``boot_device=1``, a boot keyboard is available.
508 If ``boot_device=2``, a boot mouse is available. No other values are allowed.
511 If you enable too many devices at once, you will run out of USB endpoints.
512 The number of available endpoints varies by microcontroller.
513 CircuitPython will go into safe mode after running ``boot.py`` to inform you if
514 not enough endpoints are available.
518 Boot devices implement a fixed, predefined report descriptor, defined in
519 https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
520 can request to use the boot device if the USB device says it is available.
521 Usually only a BIOS or other kind of limited-functionality
522 host needs boot keyboard support.
524 For example, to make a boot keyboard available, you can use this code::
526 usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
528 If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
529 will be ignored, and the predefined report descriptor will be used.
530 But if the host does not request the boot keyboard,
531 the descriptor provided by `Device.KEYBOARD` will be used.
533 The HID boot device must usually be the first or only device presented by CircuitPython.
534 The HID device will be USB interface number 0.
535 To make sure it is the first device, disable other USB devices, including CDC and MSC
537 If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
538 will enter safe mode to report this error.
540 this.boot_device = boot_device
542 if len(requested_devices) == 0:
547 requested_devices = [Device.BOOT_KEYBOARD]
549 requested_devices = [Device.BOOT_MOUSE]
552 # 1. Creating the gadgets
553 # -----------------------
555 # For each gadget to be created its corresponding directory must be created::
557 # $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
561 # $ mkdir $CONFIGFS_HOME/usb_gadget/g1
567 # $ cd $CONFIGFS_HOME/usb_gadget/g1
569 # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
571 # $ echo <VID> > idVendor
572 # $ echo <PID> > idProduct
574 # A gadget also needs its serial number, manufacturer and product strings.
575 # In order to have a place to store them, a strings subdirectory must be created
576 # for each language, e.g.::
578 # $ mkdir strings/0x409
580 # Then the strings can be specified::
582 # $ echo <serial number> > strings/0x409/serialnumber
583 # $ echo <manufacturer> > strings/0x409/manufacturer
584 # $ echo <product> > strings/0x409/product
586 Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True)
587 Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True)
588 Path("%s/bcdDevice" % this.gadget_root).write_text(
589 "%s" % 1, encoding="utf-8"
591 Path("%s/bcdUSB" % this.gadget_root).write_text(
592 "%s" % 0x0200, encoding="utf-8"
594 Path("%s/bDeviceClass" % this.gadget_root).write_text(
595 "%s" % 0x00, encoding="utf-8"
596 ) # multipurpose i guess?
597 Path("%s/bDeviceProtocol" % this.gadget_root).write_text(
598 "%s" % 0x00, encoding="utf-8"
600 Path("%s/bDeviceSubClass" % this.gadget_root).write_text(
601 "%s" % 0x00, encoding="utf-8"
603 Path("%s/bMaxPacketSize0" % this.gadget_root).write_text(
604 "%s" % 0x08, encoding="utf-8"
606 Path("%s/idProduct" % this.gadget_root).write_text(
607 "%s" % 0x0104, encoding="utf-8"
608 ) # Multifunction Composite Gadget
609 Path("%s/idVendor" % this.gadget_root).write_text(
610 "%s" % 0x1D6B, encoding="utf-8"
613 # 2. Creating the configurations
614 # ------------------------------
616 # Each gadget will consist of a number of configurations, their corresponding
617 # directories must be created:
619 # $ mkdir configs/<name>.<number>
621 # where <name> can be any string which is legal in a filesystem and the
622 # <number> is the configuration's number, e.g.::
624 # $ mkdir configs/c.1
630 # Each configuration also needs its strings, so a subdirectory must be created
631 # for each language, e.g.::
633 # $ mkdir configs/c.1/strings/0x409
635 # Then the configuration string can be specified::
637 # $ echo <configuration> > configs/c.1/strings/0x409/configuration
639 # Some attributes can also be set for a configuration, e.g.::
641 # $ echo 120 > configs/c.1/MaxPower
644 for device in requested_devices:
645 config_root = "%s/configs/device.1" % this.gadget_root
646 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
647 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
648 Path("%s/strings/0x409/configuration" % config_root).write_text(
649 "my configuration", encoding="utf-8"
651 Path("%s/MaxPower" % config_root).write_text("150", encoding="utf-8")
652 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080, encoding="utf-8")
653 this.devices.append(device)
655 # 3. Creating the functions
656 # -------------------------
658 # The gadget will provide some functions, for each function its corresponding
659 # directory must be created::
661 # $ mkdir functions/<name>.<instance name>
663 # where <name> corresponds to one of allowed function names and instance name
664 # is an arbitrary string allowed in a filesystem, e.g.::
666 # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
672 # Each function provides its specific set of attributes, with either read-only
673 # or read-write access. Where applicable they need to be written to as
675 # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. """
676 for report_index, report_id in enumerate(device.report_ids):
677 function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id)
679 Path("%s/" % function_root).mkdir(parents=True)
680 except FileExistsError:
682 Path("%s/protocol" % function_root).write_text(
683 "%s" % report_id, encoding="utf-8"
685 Path("%s/report_length" % function_root).write_text(
686 "%s" % device.in_report_lengths[report_index], encoding="utf-8"
688 Path("%s/subclass" % function_root).write_text("%s" % 1, encoding="utf-8")
689 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
691 # 4. Associating the functions with their configurations
692 # ------------------------------------------------------
694 # At this moment a number of gadgets is created, each of which has a number of
695 # configurations specified and a number of functions available. What remains
696 # is specifying which function is available in which configuration (the same
697 # function can be used in multiple configurations). This is achieved with
698 # creating symbolic links::
700 # $ ln -s functions/<name>.<instance name> configs/<name>.<number>
704 # $ ln -s functions/ncm.usb0 configs/c.1 """
706 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(
709 except FileNotFoundError:
711 # """ 5. Enabling the gadget
712 # ----------------------
713 # Such a gadget must be finally enabled so that the USB host can enumerate it.
715 # In order to enable the gadget it must be bound to a UDC (USB Device
718 # $ echo <udc name> > UDC
720 # where <udc name> is one of those found in /sys/class/udc/*
723 # $ echo s3c-hsotg > UDC """
724 udc = next(Path("/sys/class/udc/").glob("*"))
725 Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name, encoding="utf-8")