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
7 # regarding usb_gadget see https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
8 * Author(s): Björn Bösel
11 from typing import Sequence
12 from pathlib import Path
16 for module in ["dwc2", "libcomposite"]:
17 if Path("/proc/modules").read_text().find(module) == -1:
19 "%s module not present in your kernel. did you insmod it?" % module
22 gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
27 def get_boot_device() -> int:
33 HID Device specification: see
34 https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
43 report_ids: Sequence[int],
44 in_report_lengths: Sequence[int],
45 out_report_lengths: Sequence[int],
47 self.out_report_lengths = out_report_lengths
48 self.in_report_lengths = in_report_lengths
49 self.report_ids = report_ids
51 self.usage_page = usage_page
52 self.descriptor = descriptor
53 self._last_received_report = None
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:
60 report = bytearray(report_id.to_bytes(1, "big")) + report
64 def last_received_report(
67 """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
68 Same as `get_last_received_report()` with no argument.
70 Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
72 return self.get_last_received_report()
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.
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
87 def get_device_path(self, report_id):
89 translates the /dev/hidg device from the report id
93 "%s/functions/hid.usb%s/dev"
94 % (gadget_root, report_id or self.report_ids[0])
100 device_path = "/dev/hidg%s" % device
105 CONSUMER_CONTROL = None
108 Device.KEYBOARD = Device(
112 0x01, # usage page (generic desktop ctrls)
114 0x06, # usage (keyboard)
116 0x01, # collection (application)
118 0x01, # Report ID (1)
120 0x07, # usage page (kbrd/keypad)
122 0xE0, # usage minimum (0xe0)
124 0xE7, # usage maximum (0xe7)
126 0x00, # logical minimum (0)
128 0x01, # logical maximum (1)
130 0x01, # report size (1)
132 0x08, # report count (8)
134 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
136 0x01, # report count (1)
138 0x08, # report size (8)
140 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
142 0x03, # report count (3)
144 0x01, # report size (1)
146 0x08, # usage page (leds)
148 0x01, # usage minimum (num lock)
150 0x05, # usage maximum (kana)
153 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
155 0x01, # report count (1)
157 0x05, # report size (5)
160 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
162 0x06, # report count (6)
164 0x08, # report size (8)
166 0x00, # logical minimum (0)
169 0x00, # logical maximum (255)
171 0x07, # usage page (kbrd/keypad)
173 0x00, # usage minimum (0x00)
176 0x00, # usage maximum (0xff)
178 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
179 0xC0, # end collection
185 in_report_lengths=[8],
186 out_report_lengths=[1],
188 Device.MOUSE = Device(
192 0x01, # Usage Page (Generic Desktop Ctrls)
194 0x02, # Usage (Mouse)
196 0x01, # Collection (Application)
198 0x02, # Report ID (2)
200 0x01, # Usage (Pointer)
202 0x00, # Collection (Physical)
204 0x09, # Usage Page (Button)
206 0x01, # Usage Minimum (0x01)
208 0x05, # Usage Maximum (0x05)
210 0x00, # Logical Minimum (0)
212 0x01, # Logical Maximum (1)
214 0x05, # Report Count (5)
216 0x01, # Report Size (1)
218 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
220 0x01, # Report Count (1)
222 0x03, # Report Size (3)
224 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
226 0x01, # Usage Page (Generic Desktop Ctrls)
232 0x81, # Logical Minimum (-127)
234 0x7F, # Logical Maximum (127)
236 0x08, # Report Size (8)
238 0x02, # Report Count (2)
240 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
242 0x38, # Usage (Wheel)
244 0x81, # Logical Minimum (-127)
246 0x7F, # Logical Maximum (127)
248 0x08, # Report Size (8)
250 0x01, # Report Count (1)
252 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
253 0xC0, # End Collection
254 0xC0, # End Collection
260 in_report_lengths=[4],
261 out_report_lengths=[0],
264 Device.CONSUMER_CONTROL = Device(
268 0x0C, # Usage Page (Consumer)
270 0x01, # Usage (Consumer Control)
272 0x01, # Collection (Application)
274 0x03, # Report ID (3)
276 0x10, # Report Size (16)
278 0x01, # Report Count (1)
280 0x01, # Logical Minimum (1)
283 0x02, # Logical Maximum (652)
285 0x01, # Usage Minimum (Consumer Control)
288 0x02, # Usage Maximum (AC Send)
290 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
291 0xC0, # End Collection
297 in_report_lengths=[2],
298 out_report_lengths=[0],
301 Device.BOOT_KEYBOARD = Device(
305 0x01, # usage page (generic desktop ctrls)
307 0x06, # usage (keyboard)
309 0x01, # collection (application)
311 0x07, # usage page (kbrd/keypad)
313 0xE0, # usage minimum (0xe0)
315 0xE7, # usage maximum (0xe7)
317 0x00, # logical minimum (0)
319 0x01, # logical maximum (1)
321 0x01, # report size (1)
323 0x08, # report count (8)
325 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
327 0x01, # report count (1)
329 0x08, # report size (8)
331 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
333 0x03, # report count (3)
335 0x01, # report size (1)
337 0x08, # usage page (leds)
339 0x01, # usage minimum (num lock)
341 0x05, # usage maximum (kana)
344 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
346 0x01, # report count (1)
348 0x05, # report size (5)
351 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
353 0x06, # report count (6)
355 0x08, # report size (8)
357 0x00, # logical minimum (0)
360 0x00, # logical maximum (255)
362 0x07, # usage page (kbrd/keypad)
364 0x00, # usage minimum (0x00)
367 0x00, # usage maximum (0xff)
369 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
370 0xC0, # end collection
376 in_report_lengths=[8],
377 out_report_lengths=[1],
379 Device.BOOT_MOUSE = Device(
383 0x01, # Usage Page (Generic Desktop Ctrls)
385 0x02, # Usage (Mouse)
387 0x01, # Collection (Application)
389 0x01, # Usage (Pointer)
391 0x00, # Collection (Physical)
393 0x09, # Usage Page (Button)
395 0x01, # Usage Minimum (0x01)
397 0x05, # Usage Maximum (0x05)
399 0x00, # Logical Minimum (0)
401 0x01, # Logical Maximum (1)
403 0x05, # Report Count (5)
405 0x01, # Report Size (1)
407 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
409 0x01, # Report Count (1)
411 0x03, # Report Size (3)
413 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
415 0x01, # Usage Page (Generic Desktop Ctrls)
421 0x81, # Logical Minimum (-127)
423 0x7F, # Logical Maximum (127)
425 0x08, # Report Size (8)
427 0x02, # Report Count (2)
429 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
431 0x38, # Usage (Wheel)
433 0x81, # Logical Minimum (-127)
435 0x7F, # Logical Maximum (127)
437 0x08, # Report Size (8)
439 0x01, # Report Count (1)
441 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
442 0xC0, # End Collection
443 0xC0, # End Collection
449 in_report_lengths=[4],
450 out_report_lengths=[0],
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`.
463 Path("%s/UDC" % gadget_root).write_text("")
464 except FileNotFoundError:
466 for symlink in Path(gadget_root).glob("configs/**/hid.usb*"):
469 for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"):
470 if strings_file.is_dir():
473 for strings_file in Path(gadget_root).rglob("configs/*/strings/*"):
474 if strings_file.is_dir():
476 for config_dir in Path(gadget_root).rglob("configs/*"):
477 if config_dir.is_dir():
479 for function_dir in Path(gadget_root).rglob("functions/*"):
480 if function_dir.is_dir():
483 Path(gadget_root).rmdir()
484 except FileNotFoundError:
488 atexit.register(disable)
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.
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.
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.
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.
518 # For example, to make a boot keyboard available, you can use this code::
520 # usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
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.
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.
533 global _boot_device, devices
534 _boot_device = boot_device
536 if len(requested_devices) == 0:
541 requested_devices = [Device.BOOT_KEYBOARD]
543 requested_devices = [Device.BOOT_MOUSE]
546 # 1. Creating the gadgets
547 # -----------------------
549 # For each gadget to be created its corresponding directory must be created::
551 # $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
555 # $ mkdir $CONFIGFS_HOME/usb_gadget/g1
561 # $ cd $CONFIGFS_HOME/usb_gadget/g1
563 # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
565 # $ echo <VID> > idVendor
566 # $ echo <PID> > idProduct
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.::
572 # $ mkdir strings/0x409
574 # Then the strings can be specified::
576 # $ echo <serial number> > strings/0x409/serialnumber
577 # $ echo <manufacturer> > strings/0x409/manufacturer
578 # $ echo <product> > strings/0x409/product
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(
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(
592 ) # Multifunction Composite Gadget
593 Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
595 # 2. Creating the configurations
596 # ------------------------------
598 # Each gadget will consist of a number of configurations, their corresponding
599 # directories must be created:
601 # $ mkdir configs/<name>.<number>
603 # where <name> can be any string which is legal in a filesystem and the
604 # <number> is the configuration's number, e.g.::
606 # $ mkdir configs/c.1
612 # Each configuration also needs its strings, so a subdirectory must be created
613 # for each language, e.g.::
615 # $ mkdir configs/c.1/strings/0x409
617 # Then the configuration string can be specified::
619 # $ echo <configuration> > configs/c.1/strings/0x409/configuration
621 # Some attributes can also be set for a configuration, e.g.::
623 # $ echo 120 > configs/c.1/MaxPower
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(
633 Path("%s/MaxPower" % config_root).write_text("150")
634 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
635 devices.append(device)
637 # 3. Creating the functions
638 # -------------------------
640 # The gadget will provide some functions, for each function its corresponding
641 # directory must be created::
643 # $ mkdir functions/<name>.<instance name>
645 # where <name> corresponds to one of allowed function names and instance name
646 # is an arbitrary string allowed in a filesystem, e.g.::
648 # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
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
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)
661 Path("%s/" % function_root).mkdir(parents=True)
662 except FileExistsError:
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]
668 Path("%s/subclass" % function_root).write_text("%s" % 1)
669 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
671 # 4. Associating the functions with their configurations
672 # ------------------------------------------------------
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::
680 # $ ln -s functions/<name>.<instance name> configs/<name>.<number>
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.
690 # In order to enable the gadget it must be bound to a UDC (USB Device
693 # $ echo <udc name> > UDC
695 # where <udc name> is one of those found in /sys/class/udc/*
698 # $ echo s3c-hsotg > UDC """
699 udc = next(Path("/sys/class/udc/").glob("*"))
700 Path("%s/UDC" % gadget_root).write_text("%s" % udc.name)