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:
486 atexit.register(disable)
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.
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.
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.
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.
516 # For example, to make a boot keyboard available, you can use this code::
518 # usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
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.
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.
531 this.boot_device = boot_device
533 if len(requested_devices) == 0:
538 requested_devices = [Device.BOOT_KEYBOARD]
540 requested_devices = [Device.BOOT_MOUSE]
543 # 1. Creating the gadgets
544 # -----------------------
546 # For each gadget to be created its corresponding directory must be created::
548 # $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
552 # $ mkdir $CONFIGFS_HOME/usb_gadget/g1
558 # $ cd $CONFIGFS_HOME/usb_gadget/g1
560 # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
562 # $ echo <VID> > idVendor
563 # $ echo <PID> > idProduct
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.::
569 # $ mkdir strings/0x409
571 # Then the strings can be specified::
573 # $ echo <serial number> > strings/0x409/serialnumber
574 # $ echo <manufacturer> > strings/0x409/manufacturer
575 # $ echo <product> > strings/0x409/product
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(
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(
589 ) # Multifunction Composite Gadget
590 Path("%s/idVendor" % this.gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
592 # 2. Creating the configurations
593 # ------------------------------
595 # Each gadget will consist of a number of configurations, their corresponding
596 # directories must be created:
598 # $ mkdir configs/<name>.<number>
600 # where <name> can be any string which is legal in a filesystem and the
601 # <number> is the configuration's number, e.g.::
603 # $ mkdir configs/c.1
609 # Each configuration also needs its strings, so a subdirectory must be created
610 # for each language, e.g.::
612 # $ mkdir configs/c.1/strings/0x409
614 # Then the configuration string can be specified::
616 # $ echo <configuration> > configs/c.1/strings/0x409/configuration
618 # Some attributes can also be set for a configuration, e.g.::
620 # $ echo 120 > configs/c.1/MaxPower
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(
630 Path("%s/MaxPower" % config_root).write_text("150")
631 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
632 this.devices.append(device)
634 # 3. Creating the functions
635 # -------------------------
637 # The gadget will provide some functions, for each function its corresponding
638 # directory must be created::
640 # $ mkdir functions/<name>.<instance name>
642 # where <name> corresponds to one of allowed function names and instance name
643 # is an arbitrary string allowed in a filesystem, e.g.::
645 # $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
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
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)
658 Path("%s/" % function_root).mkdir(parents=True)
659 except FileExistsError:
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]
665 Path("%s/subclass" % function_root).write_text("%s" % 1)
666 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
668 # 4. Associating the functions with their configurations
669 # ------------------------------------------------------
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::
677 # $ ln -s functions/<name>.<instance name> configs/<name>.<number>
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.
687 # In order to enable the gadget it must be bound to a UDC (USB Device
690 # $ echo <udc name> > UDC
692 # where <udc name> is one of those found in /sys/class/udc/*
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)