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
17 for module in ["dwc2", "libcomposite"]:
18 if Path("/proc/modules").read_text().find(module) == -1:
20 "%s module not present in your kernel. did you insmod it?" % module
22 this = sys.modules[__name__]
24 this.gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
31 HID Device specification: see
32 https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
41 report_ids: Sequence[int],
42 in_report_lengths: Sequence[int],
43 out_report_lengths: Sequence[int],
45 self.out_report_lengths = out_report_lengths
46 self.in_report_lengths = in_report_lengths
47 self.report_ids = report_ids
49 self.usage_page = usage_page
50 self.descriptor = descriptor
51 self._last_received_report = None
53 def send_report(self, report: bytearray, report_id: int = None):
54 """Send an HID report. If the device descriptor specifies zero or one report id's,
55 you can supply `None` (the default) as the value of ``report_id``.
56 Otherwise you must specify which report id to use when sending the report.
58 report_id = report_id or self.report_ids[0]
59 device_path = self.get_device_path(report_id)
60 with open(device_path, "rb+") as fd:
62 report = bytearray(report_id.to_bytes(1, "big")) + report
66 def last_received_report(
69 """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
70 Same as `get_last_received_report()` with no argument.
72 Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
74 return self.get_last_received_report()
76 def get_last_received_report(self, report_id=None) -> bytes:
77 """Get the last received HID OUT or feature report for the given report ID.
78 The report ID may be omitted if there is no report ID, or only one report ID.
79 Return `None` if nothing received.
81 device_path = self.get_device_path(report_id or self.report_ids[0])
82 with open(device_path, "rb+") as fd:
83 os.set_blocking(fd.fileno(), False)
84 report = fd.read(self.out_report_lengths[0])
85 if report is not None:
86 self._last_received_report = report
87 return self._last_received_report
89 def get_device_path(self, report_id):
91 translates the /dev/hidg device from the report id
95 "%s/functions/hid.usb%s/dev"
96 % (this.gadget_root, report_id or self.report_ids[0])
102 device_path = "/dev/hidg%s" % device
107 CONSUMER_CONTROL = None
110 Device.KEYBOARD = Device(
114 0x01, # usage page (generic desktop ctrls)
116 0x06, # usage (keyboard)
118 0x01, # collection (application)
120 0x01, # Report ID (1)
122 0x07, # usage page (kbrd/keypad)
124 0xE0, # usage minimum (0xe0)
126 0xE7, # usage maximum (0xe7)
128 0x00, # logical minimum (0)
130 0x01, # logical maximum (1)
132 0x01, # report size (1)
134 0x08, # report count (8)
136 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
138 0x01, # report count (1)
140 0x08, # report size (8)
142 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
144 0x03, # report count (3)
146 0x01, # report size (1)
148 0x08, # usage page (leds)
150 0x01, # usage minimum (num lock)
152 0x05, # usage maximum (kana)
155 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
157 0x01, # report count (1)
159 0x05, # report size (5)
162 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
164 0x06, # report count (6)
166 0x08, # report size (8)
168 0x00, # logical minimum (0)
171 0x00, # logical maximum (255)
173 0x07, # usage page (kbrd/keypad)
175 0x00, # usage minimum (0x00)
178 0x00, # usage maximum (0xff)
180 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
181 0xC0, # end collection
187 in_report_lengths=[8],
188 out_report_lengths=[1],
190 Device.MOUSE = Device(
194 0x01, # Usage Page (Generic Desktop Ctrls)
196 0x02, # Usage (Mouse)
198 0x01, # Collection (Application)
200 0x02, # Report ID (2)
202 0x01, # Usage (Pointer)
204 0x00, # Collection (Physical)
206 0x09, # Usage Page (Button)
208 0x01, # Usage Minimum (0x01)
210 0x05, # Usage Maximum (0x05)
212 0x00, # Logical Minimum (0)
214 0x01, # Logical Maximum (1)
216 0x05, # Report Count (5)
218 0x01, # Report Size (1)
220 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
222 0x01, # Report Count (1)
224 0x03, # Report Size (3)
226 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
228 0x01, # Usage Page (Generic Desktop Ctrls)
234 0x81, # Logical Minimum (-127)
236 0x7F, # Logical Maximum (127)
238 0x08, # Report Size (8)
240 0x02, # Report Count (2)
242 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
244 0x38, # Usage (Wheel)
246 0x81, # Logical Minimum (-127)
248 0x7F, # Logical Maximum (127)
250 0x08, # Report Size (8)
252 0x01, # Report Count (1)
254 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
255 0xC0, # End Collection
256 0xC0, # End Collection
262 in_report_lengths=[4],
263 out_report_lengths=[0],
266 Device.CONSUMER_CONTROL = Device(
270 0x0C, # Usage Page (Consumer)
272 0x01, # Usage (Consumer Control)
274 0x01, # Collection (Application)
276 0x03, # Report ID (3)
278 0x10, # Report Size (16)
280 0x01, # Report Count (1)
282 0x01, # Logical Minimum (1)
285 0x02, # Logical Maximum (652)
287 0x01, # Usage Minimum (Consumer Control)
290 0x02, # Usage Maximum (AC Send)
292 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
293 0xC0, # End Collection
299 in_report_lengths=[2],
300 out_report_lengths=[0],
303 Device.BOOT_KEYBOARD = Device(
307 0x01, # usage page (generic desktop ctrls)
309 0x06, # usage (keyboard)
311 0x01, # collection (application)
313 0x07, # usage page (kbrd/keypad)
315 0xE0, # usage minimum (0xe0)
317 0xE7, # usage maximum (0xe7)
319 0x00, # logical minimum (0)
321 0x01, # logical maximum (1)
323 0x01, # report size (1)
325 0x08, # report count (8)
327 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
329 0x01, # report count (1)
331 0x08, # report size (8)
333 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
335 0x03, # report count (3)
337 0x01, # report size (1)
339 0x08, # usage page (leds)
341 0x01, # usage minimum (num lock)
343 0x05, # usage maximum (kana)
346 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
348 0x01, # report count (1)
350 0x05, # report size (5)
353 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
355 0x06, # report count (6)
357 0x08, # report size (8)
359 0x00, # logical minimum (0)
362 0x00, # logical maximum (255)
364 0x07, # usage page (kbrd/keypad)
366 0x00, # usage minimum (0x00)
369 0x00, # usage maximum (0xff)
371 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
372 0xC0, # end collection
378 in_report_lengths=[8],
379 out_report_lengths=[1],
381 Device.BOOT_MOUSE = Device(
385 0x01, # Usage Page (Generic Desktop Ctrls)
387 0x02, # Usage (Mouse)
389 0x01, # Collection (Application)
391 0x01, # Usage (Pointer)
393 0x00, # Collection (Physical)
395 0x09, # Usage Page (Button)
397 0x01, # Usage Minimum (0x01)
399 0x05, # Usage Maximum (0x05)
401 0x00, # Logical Minimum (0)
403 0x01, # Logical Maximum (1)
405 0x05, # Report Count (5)
407 0x01, # Report Size (1)
409 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
411 0x01, # Report Count (1)
413 0x03, # Report Size (3)
415 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
417 0x01, # Usage Page (Generic Desktop Ctrls)
423 0x81, # Logical Minimum (-127)
425 0x7F, # Logical Maximum (127)
427 0x08, # Report Size (8)
429 0x02, # Report Count (2)
431 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
433 0x38, # Usage (Wheel)
435 0x81, # Logical Minimum (-127)
437 0x7F, # Logical Maximum (127)
439 0x08, # Report Size (8)
441 0x01, # Report Count (1)
443 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
444 0xC0, # End Collection
445 0xC0, # End Collection
451 in_report_lengths=[4],
452 out_report_lengths=[0],
456 def disable() -> None:
457 """Do not present any USB HID devices to the host computer.
458 Can be called in ``boot.py``, before USB is connected.
459 The HID composite device is normally enabled by default,
460 but on some boards with limited endpoints, including STM32F4,
461 it is disabled by default. You must turn off another USB device such
462 as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
465 Path("%s/UDC" % this.gadget_root).write_text("")
466 except FileNotFoundError:
468 for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"):
471 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"):
472 if strings_file.is_dir():
475 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"):
476 if strings_file.is_dir():
478 for config_dir in Path(this.gadget_root).rglob("configs/*"):
479 if config_dir.is_dir():
481 for function_dir in Path(this.gadget_root).rglob("functions/*"):
482 if function_dir.is_dir():
485 Path(this.gadget_root).rmdir()
486 except FileNotFoundError:
491 atexit.register(disable)
494 def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
495 """Specify which USB HID devices that will be available.
496 Can be called in ``boot.py``, before USB is connected.
498 :param Sequence devices: `Device` objects.
499 If `devices` is empty, HID is disabled. The order of the ``Devices``
500 may matter to the host. For instance, for MacOS, put the mouse device
501 before any Gamepad or Digitizer HID device or else it will not work.
502 :param int boot_device: If non-zero, inform the host that support for a
503 a boot HID device is available.
504 If ``boot_device=1``, a boot keyboard is available.
505 If ``boot_device=2``, a boot mouse is available. No other values are allowed.
508 If you enable too many devices at once, you will run out of USB endpoints.
509 The number of available endpoints varies by microcontroller.
510 CircuitPython will go into safe mode after running ``boot.py`` to inform you if
511 not enough endpoints are available.
515 Boot devices implement a fixed, predefined report descriptor, defined in
516 https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
517 can request to use the boot device if the USB device says it is available.
518 Usually only a BIOS or other kind of limited-functionality
519 host needs boot keyboard support.
521 For example, to make a boot keyboard available, you can use this code::
523 usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
525 If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
526 will be ignored, and the predefined report descriptor will be used.
527 But if the host does not request the boot keyboard,
528 the descriptor provided by `Device.KEYBOARD` will be used.
530 The HID boot device must usually be the first or only device presented by CircuitPython.
531 The HID device will be USB interface number 0.
532 To make sure it is the first device, disable other USB devices, including CDC and MSC
534 If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
535 will enter safe mode to report this error.
537 this.boot_device = boot_device
539 if len(requested_devices) == 0:
544 requested_devices = [Device.BOOT_KEYBOARD]
546 requested_devices = [Device.BOOT_MOUSE]
549 # 1. Creating the gadgets
550 # -----------------------
552 # For each gadget to be created its corresponding directory must be created::
554 # $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
558 # $ mkdir $CONFIGFS_HOME/usb_gadget/g1
564 # $ cd $CONFIGFS_HOME/usb_gadget/g1
566 # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
568 # $ echo <VID> > idVendor
569 # $ echo <PID> > idProduct
571 # A gadget also needs its serial number, manufacturer and product strings.
572 # In order to have a place to store them, a strings subdirectory must be created
573 # for each language, e.g.::
575 # $ mkdir strings/0x409
577 # Then the strings can be specified::
579 # $ echo <serial number> > strings/0x409/serialnumber
580 # $ echo <manufacturer> > strings/0x409/manufacturer
581 # $ echo <product> > strings/0x409/product
583 Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True)
584 Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True)
585 Path("%s/bcdDevice" % this.gadget_root).write_text("%s" % 1) # Version 1.0.0
586 Path("%s/bcdUSB" % this.gadget_root).write_text("%s" % 0x0200) # USB 2.0
587 Path("%s/bDeviceClass" % this.gadget_root).write_text(
589 ) # multipurpose i guess?
590 Path("%s/bDeviceProtocol" % this.gadget_root).write_text("%s" % 0x00)
591 Path("%s/bDeviceSubClass" % this.gadget_root).write_text("%s" % 0x00)
592 Path("%s/bMaxPacketSize0" % this.gadget_root).write_text("%s" % 0x08)
593 Path("%s/idProduct" % this.gadget_root).write_text(
595 ) # Multifunction Composite Gadget
596 Path("%s/idVendor" % this.gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
598 # 2. Creating the configurations
599 # ------------------------------
601 # Each gadget will consist of a number of configurations, their corresponding
602 # directories must be created:
604 # $ mkdir configs/<name>.<number>
606 # where <name> can be any string which is legal in a filesystem and the
607 # <number> is the configuration's number, e.g.::
609 # $ mkdir configs/c.1
615 # Each configuration also needs its strings, so a subdirectory must be created
616 # for each language, e.g.::
618 # $ mkdir configs/c.1/strings/0x409
620 # Then the configuration string can be specified::
622 # $ echo <configuration> > configs/c.1/strings/0x409/configuration
624 # Some attributes can also be set for a configuration, e.g.::
626 # $ echo 120 > configs/c.1/MaxPower
629 for device in requested_devices:
630 config_root = "%s/configs/device.1" % this.gadget_root
631 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
632 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
633 Path("%s/strings/0x409/configuration" % config_root).write_text(
636 Path("%s/MaxPower" % config_root).write_text("150")
637 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
638 this.devices.append(device)
640 # 3. Creating the functions
641 # -------------------------
643 # The gadget will provide some functions, for each function its corresponding
644 # directory must be created::
646 # $ mkdir functions/<name>.<instance name>
648 # where <name> corresponds to one of allowed function names and instance name
649 # is an arbitrary string allowed in a filesystem, e.g.::
651 # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
657 # Each function provides its specific set of attributes, with either read-only
658 # or read-write access. Where applicable they need to be written to as
660 # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. """
661 for report_index, report_id in enumerate(device.report_ids):
662 function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id)
664 Path("%s/" % function_root).mkdir(parents=True)
665 except FileExistsError:
667 Path("%s/protocol" % function_root).write_text("%s" % report_id)
668 Path("%s/report_length" % function_root).write_text(
669 "%s" % device.in_report_lengths[report_index]
671 Path("%s/subclass" % function_root).write_text("%s" % 1)
672 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
674 # 4. Associating the functions with their configurations
675 # ------------------------------------------------------
677 # At this moment a number of gadgets is created, each of which has a number of
678 # configurations specified and a number of functions available. What remains
679 # is specifying which function is available in which configuration (the same
680 # function can be used in multiple configurations). This is achieved with
681 # creating symbolic links::
683 # $ ln -s functions/<name>.<instance name> configs/<name>.<number>
687 # $ ln -s functions/ncm.usb0 configs/c.1 """
689 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(
694 # """ 5. Enabling the gadget
695 # ----------------------
696 # Such a gadget must be finally enabled so that the USB host can enumerate it.
698 # In order to enable the gadget it must be bound to a UDC (USB Device
701 # $ echo <udc name> > UDC
703 # where <udc name> is one of those found in /sys/class/udc/*
706 # $ echo s3c-hsotg > UDC """
707 udc = next(Path("/sys/class/udc/").glob("*"))
708 Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name)