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 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:
58 report = bytearray(report_id.to_bytes(1, "big")) + report
62 def last_received_report(
65 """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
66 Same as `get_last_received_report()` with no argument.
68 Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
70 return self.get_last_received_report()
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.
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
85 def get_device_path(self, report_id):
87 translates the /dev/hidg device from the report id
91 "%s/functions/hid.usb%s/dev"
92 % (this.gadget_root, report_id or self.report_ids[0])
98 device_path = "/dev/hidg%s" % device
103 CONSUMER_CONTROL = None
106 Device.KEYBOARD = Device(
110 0x01, # usage page (generic desktop ctrls)
112 0x06, # usage (keyboard)
114 0x01, # collection (application)
116 0x01, # Report ID (1)
118 0x07, # usage page (kbrd/keypad)
120 0xE0, # usage minimum (0xe0)
122 0xE7, # usage maximum (0xe7)
124 0x00, # logical minimum (0)
126 0x01, # logical maximum (1)
128 0x01, # report size (1)
130 0x08, # report count (8)
132 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
134 0x01, # report count (1)
136 0x08, # report size (8)
138 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
140 0x03, # report count (3)
142 0x01, # report size (1)
144 0x08, # usage page (leds)
146 0x01, # usage minimum (num lock)
148 0x05, # usage maximum (kana)
151 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
153 0x01, # report count (1)
155 0x05, # report size (5)
158 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
160 0x06, # report count (6)
162 0x08, # report size (8)
164 0x00, # logical minimum (0)
167 0x00, # logical maximum (255)
169 0x07, # usage page (kbrd/keypad)
171 0x00, # usage minimum (0x00)
174 0x00, # usage maximum (0xff)
176 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
177 0xC0, # end collection
183 in_report_lengths=[8],
184 out_report_lengths=[1],
186 Device.MOUSE = Device(
190 0x01, # Usage Page (Generic Desktop Ctrls)
192 0x02, # Usage (Mouse)
194 0x01, # Collection (Application)
196 0x02, # Report ID (2)
198 0x01, # Usage (Pointer)
200 0x00, # Collection (Physical)
202 0x09, # Usage Page (Button)
204 0x01, # Usage Minimum (0x01)
206 0x05, # Usage Maximum (0x05)
208 0x00, # Logical Minimum (0)
210 0x01, # Logical Maximum (1)
212 0x05, # Report Count (5)
214 0x01, # Report Size (1)
216 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
218 0x01, # Report Count (1)
220 0x03, # Report Size (3)
222 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
224 0x01, # Usage Page (Generic Desktop Ctrls)
230 0x81, # Logical Minimum (-127)
232 0x7F, # Logical Maximum (127)
234 0x08, # Report Size (8)
236 0x02, # Report Count (2)
238 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
240 0x38, # Usage (Wheel)
242 0x81, # Logical Minimum (-127)
244 0x7F, # Logical Maximum (127)
246 0x08, # Report Size (8)
248 0x01, # Report Count (1)
250 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
251 0xC0, # End Collection
252 0xC0, # End Collection
258 in_report_lengths=[4],
259 out_report_lengths=[0],
262 Device.CONSUMER_CONTROL = Device(
266 0x0C, # Usage Page (Consumer)
268 0x01, # Usage (Consumer Control)
270 0x01, # Collection (Application)
272 0x03, # Report ID (3)
274 0x10, # Report Size (16)
276 0x01, # Report Count (1)
278 0x01, # Logical Minimum (1)
281 0x02, # Logical Maximum (652)
283 0x01, # Usage Minimum (Consumer Control)
286 0x02, # Usage Maximum (AC Send)
288 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
289 0xC0, # End Collection
295 in_report_lengths=[2],
296 out_report_lengths=[0],
299 Device.BOOT_KEYBOARD = Device(
303 0x01, # usage page (generic desktop ctrls)
305 0x06, # usage (keyboard)
307 0x01, # collection (application)
309 0x07, # usage page (kbrd/keypad)
311 0xE0, # usage minimum (0xe0)
313 0xE7, # usage maximum (0xe7)
315 0x00, # logical minimum (0)
317 0x01, # logical maximum (1)
319 0x01, # report size (1)
321 0x08, # report count (8)
323 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
325 0x01, # report count (1)
327 0x08, # report size (8)
329 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
331 0x03, # report count (3)
333 0x01, # report size (1)
335 0x08, # usage page (leds)
337 0x01, # usage minimum (num lock)
339 0x05, # usage maximum (kana)
342 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
344 0x01, # report count (1)
346 0x05, # report size (5)
349 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
351 0x06, # report count (6)
353 0x08, # report size (8)
355 0x00, # logical minimum (0)
358 0x00, # logical maximum (255)
360 0x07, # usage page (kbrd/keypad)
362 0x00, # usage minimum (0x00)
365 0x00, # usage maximum (0xff)
367 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
368 0xC0, # end collection
374 in_report_lengths=[8],
375 out_report_lengths=[1],
377 Device.BOOT_MOUSE = Device(
381 0x01, # Usage Page (Generic Desktop Ctrls)
383 0x02, # Usage (Mouse)
385 0x01, # Collection (Application)
387 0x01, # Usage (Pointer)
389 0x00, # Collection (Physical)
391 0x09, # Usage Page (Button)
393 0x01, # Usage Minimum (0x01)
395 0x05, # Usage Maximum (0x05)
397 0x00, # Logical Minimum (0)
399 0x01, # Logical Maximum (1)
401 0x05, # Report Count (5)
403 0x01, # Report Size (1)
405 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
407 0x01, # Report Count (1)
409 0x03, # Report Size (3)
411 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
413 0x01, # Usage Page (Generic Desktop Ctrls)
419 0x81, # Logical Minimum (-127)
421 0x7F, # Logical Maximum (127)
423 0x08, # Report Size (8)
425 0x02, # Report Count (2)
427 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
429 0x38, # Usage (Wheel)
431 0x81, # Logical Minimum (-127)
433 0x7F, # Logical Maximum (127)
435 0x08, # Report Size (8)
437 0x01, # Report Count (1)
439 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
440 0xC0, # End Collection
441 0xC0, # End Collection
447 in_report_lengths=[4],
448 out_report_lengths=[0],
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`.
461 Path("%s/UDC" % this.gadget_root).write_text("")
462 except FileNotFoundError:
464 for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"):
467 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"):
468 if strings_file.is_dir():
471 for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"):
472 if strings_file.is_dir():
474 for config_dir in Path(this.gadget_root).rglob("configs/*"):
475 if config_dir.is_dir():
477 for function_dir in Path(this.gadget_root).rglob("functions/*"):
478 if function_dir.is_dir():
481 Path(this.gadget_root).rmdir()
482 except FileNotFoundError:
487 atexit.register(disable)
490 def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
491 # """Specify which USB HID devices that will be available.
492 # Can be called in ``boot.py``, before USB is connected.
494 # :param Sequence devices: `Device` objects.
495 # If `devices` is empty, HID is disabled. The order of the ``Devices``
496 # may matter to the host. For instance, for MacOS, put the mouse device
497 # before any Gamepad or Digitizer HID device or else it will not work.
498 # :param int boot_device: If non-zero, inform the host that support for a
499 # a boot HID device is available.
500 # If ``boot_device=1``, a boot keyboard is available.
501 # If ``boot_device=2``, a boot mouse is available. No other values are allowed.
504 # If you enable too many devices at once, you will run out of USB endpoints.
505 # The number of available endpoints varies by microcontroller.
506 # CircuitPython will go into safe mode after running ``boot.py`` to inform you if
507 # not enough endpoints are available.
511 # Boot devices implement a fixed, predefined report descriptor, defined in
512 # https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
513 # can request to use the boot device if the USB device says it is available.
514 # Usually only a BIOS or other kind of limited-functionality
515 # host needs boot keyboard support.
517 # For example, to make a boot keyboard available, you can use this code::
519 # usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
521 # If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
522 # will be ignored, and the predefined report descriptor will be used.
523 # But if the host does not request the boot keyboard,
524 # the descriptor provided by `Device.KEYBOARD` will be used.
526 # The HID boot device must usually be the first or only device presented by CircuitPython.
527 # The HID device will be USB interface number 0.
528 # To make sure it is the first device, disable other USB devices, including CDC and MSC (CIRCUITPY).
529 # If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
530 # will enter safe mode to report this error.
532 this.boot_device = boot_device
534 if len(requested_devices) == 0:
539 requested_devices = [Device.BOOT_KEYBOARD]
541 requested_devices = [Device.BOOT_MOUSE]
544 # 1. Creating the gadgets
545 # -----------------------
547 # For each gadget to be created its corresponding directory must be created::
549 # $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
553 # $ mkdir $CONFIGFS_HOME/usb_gadget/g1
559 # $ cd $CONFIGFS_HOME/usb_gadget/g1
561 # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
563 # $ echo <VID> > idVendor
564 # $ echo <PID> > idProduct
566 # A gadget also needs its serial number, manufacturer and product strings.
567 # In order to have a place to store them, a strings subdirectory must be created
568 # for each language, e.g.::
570 # $ mkdir strings/0x409
572 # Then the strings can be specified::
574 # $ echo <serial number> > strings/0x409/serialnumber
575 # $ echo <manufacturer> > strings/0x409/manufacturer
576 # $ echo <product> > strings/0x409/product
578 Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True)
579 Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True)
580 Path("%s/bcdDevice" % this.gadget_root).write_text("%s" % 1) # Version 1.0.0
581 Path("%s/bcdUSB" % this.gadget_root).write_text("%s" % 0x0200) # USB 2.0
582 Path("%s/bDeviceClass" % this.gadget_root).write_text(
584 ) # multipurpose i guess?
585 Path("%s/bDeviceProtocol" % this.gadget_root).write_text("%s" % 0x00)
586 Path("%s/bDeviceSubClass" % this.gadget_root).write_text("%s" % 0x00)
587 Path("%s/bMaxPacketSize0" % this.gadget_root).write_text("%s" % 0x08)
588 Path("%s/idProduct" % this.gadget_root).write_text(
590 ) # Multifunction Composite Gadget
591 Path("%s/idVendor" % this.gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
593 # 2. Creating the configurations
594 # ------------------------------
596 # Each gadget will consist of a number of configurations, their corresponding
597 # directories must be created:
599 # $ mkdir configs/<name>.<number>
601 # where <name> can be any string which is legal in a filesystem and the
602 # <number> is the configuration's number, e.g.::
604 # $ mkdir configs/c.1
610 # Each configuration also needs its strings, so a subdirectory must be created
611 # for each language, e.g.::
613 # $ mkdir configs/c.1/strings/0x409
615 # Then the configuration string can be specified::
617 # $ echo <configuration> > configs/c.1/strings/0x409/configuration
619 # Some attributes can also be set for a configuration, e.g.::
621 # $ echo 120 > configs/c.1/MaxPower
624 for i, device in enumerate(requested_devices):
625 config_root = "%s/configs/device.%s" % (this.gadget_root, i + 1)
626 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
627 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
628 Path("%s/strings/0x409/configuration" % config_root).write_text(
631 Path("%s/MaxPower" % config_root).write_text("150")
632 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
633 this.devices.append(device)
635 # 3. Creating the functions
636 # -------------------------
638 # The gadget will provide some functions, for each function its corresponding
639 # directory must be created::
641 # $ mkdir functions/<name>.<instance name>
643 # where <name> corresponds to one of allowed function names and instance name
644 # is an arbitrary string allowed in a filesystem, e.g.::
646 # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
652 # Each function provides its specific set of attributes, with either read-only
653 # or read-write access. Where applicable they need to be written to as
655 # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. """
656 for report_index, report_id in enumerate(device.report_ids):
657 function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id)
659 Path("%s/" % function_root).mkdir(parents=True)
660 except FileExistsError:
662 Path("%s/protocol" % function_root).write_text("%s" % report_id)
663 Path("%s/report_length" % function_root).write_text(
664 "%s" % device.in_report_lengths[report_index]
666 Path("%s/subclass" % function_root).write_text("%s" % 1)
667 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
669 # 4. Associating the functions with their configurations
670 # ------------------------------------------------------
672 # At this moment a number of gadgets is created, each of which has a number of
673 # configurations specified and a number of functions available. What remains
674 # is specifying which function is available in which configuration (the same
675 # function can be used in multiple configurations). This is achieved with
676 # creating symbolic links::
678 # $ ln -s functions/<name>.<instance name> configs/<name>.<number>
682 # $ ln -s functions/ncm.usb0 configs/c.1 """
683 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
684 # """ 5. Enabling the gadget
685 # ----------------------
686 # Such a gadget must be finally enabled so that the USB host can enumerate it.
688 # In order to enable the gadget it must be bound to a UDC (USB Device
691 # $ echo <udc name> > UDC
693 # where <udc name> is one of those found in /sys/class/udc/*
696 # $ echo s3c-hsotg > UDC """
697 udc = next(Path("/sys/class/udc/").glob("*"))
698 Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name)