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 https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
42 report_ids: Sequence[int],
43 in_report_lengths: Sequence[int],
44 out_report_lengths: Sequence[int],
46 self.out_report_lengths = out_report_lengths
47 self.in_report_lengths = in_report_lengths
48 self.report_ids = report_ids
50 self.usage_page = usage_page
51 self.descriptor = descriptor
52 self._last_received_report = None
54 def send_report(self, report: bytearray, report_id: int = None):
55 report_id = report_id or self.report_ids[0]
56 device_path = self.get_device_path(report_id)
57 with open(device_path, "rb+") as fd:
59 report = bytearray(report_id.to_bytes(1, "big")) + report
63 def last_received_report(
66 """The HID OUT report as a `bytes` (read-only). `None` if nothing received.
67 Same as `get_last_received_report()` with no argument.
69 Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead.
71 return self.get_last_received_report()
73 def get_last_received_report(self, report_id=None) -> bytes:
74 """Get the last received HID OUT or feature report for the given report ID.
75 The report ID may be omitted if there is no report ID, or only one report ID.
76 Return `None` if nothing received.
78 device_path = self.get_device_path(report_id or self.report_ids[0])
79 with open(device_path, "rb+") as fd:
80 os.set_blocking(fd.fileno(), False)
81 report = fd.read(self.out_report_lengths[0])
82 if report is not None:
83 self._last_received_report = report
84 return self._last_received_report
86 def get_device_path(self, report_id):
88 translates the /dev/hidg device from the report id
92 "%s/functions/hid.usb%s/dev"
93 % (gadget_root, report_id or self.report_ids[0])
99 device_path = "/dev/hidg%s" % device
104 CONSUMER_CONTROL = None
107 Device.KEYBOARD = Device(
111 0x01, # usage page (generic desktop ctrls)
113 0x06, # usage (keyboard)
115 0x01, # collection (application)
117 0x01, # Report ID (1)
119 0x07, # usage page (kbrd/keypad)
121 0xE0, # usage minimum (0xe0)
123 0xE7, # usage maximum (0xe7)
125 0x00, # logical minimum (0)
127 0x01, # logical maximum (1)
129 0x01, # report size (1)
131 0x08, # report count (8)
133 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
135 0x01, # report count (1)
137 0x08, # report size (8)
139 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
141 0x03, # report count (3)
143 0x01, # report size (1)
145 0x08, # usage page (leds)
147 0x01, # usage minimum (num lock)
149 0x05, # usage maximum (kana)
152 # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
154 0x01, # report count (1)
156 0x05, # report size (5)
159 # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
161 0x06, # report count (6)
163 0x08, # report size (8)
165 0x00, # logical minimum (0)
168 0x00, # logical maximum (255)
170 0x07, # usage page (kbrd/keypad)
172 0x00, # usage minimum (0x00)
175 0x00, # usage maximum (0xff)
177 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
178 0xC0, # end collection
184 in_report_lengths=[8],
185 out_report_lengths=[1],
187 Device.MOUSE = Device(
191 0x01, # Usage Page (Generic Desktop Ctrls)
193 0x02, # Usage (Mouse)
195 0x01, # Collection (Application)
197 0x02, # Report ID (2)
199 0x01, # Usage (Pointer)
201 0x00, # Collection (Physical)
203 0x09, # Usage Page (Button)
205 0x01, # Usage Minimum (0x01)
207 0x05, # Usage Maximum (0x05)
209 0x00, # Logical Minimum (0)
211 0x01, # Logical Maximum (1)
213 0x05, # Report Count (5)
215 0x01, # Report Size (1)
217 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
219 0x01, # Report Count (1)
221 0x03, # Report Size (3)
223 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
225 0x01, # Usage Page (Generic Desktop Ctrls)
231 0x81, # Logical Minimum (-127)
233 0x7F, # Logical Maximum (127)
235 0x08, # Report Size (8)
237 0x02, # Report Count (2)
239 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
241 0x38, # Usage (Wheel)
243 0x81, # Logical Minimum (-127)
245 0x7F, # Logical Maximum (127)
247 0x08, # Report Size (8)
249 0x01, # Report Count (1)
251 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
252 0xC0, # End Collection
253 0xC0, # End Collection
259 in_report_lengths=[4],
260 out_report_lengths=[0],
263 Device.CONSUMER_CONTROL = Device(
267 0x0C, # Usage Page (Consumer)
269 0x01, # Usage (Consumer Control)
271 0x01, # Collection (Application)
273 0x03, # Report ID (3)
275 0x10, # Report Size (16)
277 0x01, # Report Count (1)
279 0x01, # Logical Minimum (1)
282 0x02, # Logical Maximum (652)
284 0x01, # Usage Minimum (Consumer Control)
287 0x02, # Usage Maximum (AC Send)
289 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
290 0xC0, # End Collection
296 in_report_lengths=[2],
297 out_report_lengths=[0],
300 Device.BOOT_KEYBOARD = Device(
304 0x01, # usage page (generic desktop ctrls)
306 0x06, # usage (keyboard)
308 0x01, # collection (application)
310 0x07, # usage page (kbrd/keypad)
312 0xE0, # usage minimum (0xe0)
314 0xE7, # usage maximum (0xe7)
316 0x00, # logical minimum (0)
318 0x01, # logical maximum (1)
320 0x01, # report size (1)
322 0x08, # report count (8)
324 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
326 0x01, # report count (1)
328 0x08, # report size (8)
330 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
332 0x03, # report count (3)
334 0x01, # report size (1)
336 0x08, # usage page (leds)
338 0x01, # usage minimum (num lock)
340 0x05, # usage maximum (kana)
342 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
344 0x01, # report count (1)
346 0x05, # report size (5)
348 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
350 0x06, # report count (6)
352 0x08, # report size (8)
354 0x00, # logical minimum (0)
357 0x00, # logical maximum (255)
359 0x07, # usage page (kbrd/keypad)
361 0x00, # usage minimum (0x00)
364 0x00, # usage maximum (0xff)
366 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
367 0xC0, # end collection
373 in_report_lengths=[8],
374 out_report_lengths=[1],
376 Device.BOOT_MOUSE = Device(
380 0x01, # Usage Page (Generic Desktop Ctrls)
382 0x02, # Usage (Mouse)
384 0x01, # Collection (Application)
386 0x01, # Usage (Pointer)
388 0x00, # Collection (Physical)
390 0x09, # Usage Page (Button)
392 0x01, # Usage Minimum (0x01)
394 0x05, # Usage Maximum (0x05)
396 0x00, # Logical Minimum (0)
398 0x01, # Logical Maximum (1)
400 0x05, # Report Count (5)
402 0x01, # Report Size (1)
404 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
406 0x01, # Report Count (1)
408 0x03, # Report Size (3)
410 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
412 0x01, # Usage Page (Generic Desktop Ctrls)
418 0x81, # Logical Minimum (-127)
420 0x7F, # Logical Maximum (127)
422 0x08, # Report Size (8)
424 0x02, # Report Count (2)
426 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
428 0x38, # Usage (Wheel)
430 0x81, # Logical Minimum (-127)
432 0x7F, # Logical Maximum (127)
434 0x08, # Report Size (8)
436 0x01, # Report Count (1)
438 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
439 0xC0, # End Collection
440 0xC0, # End Collection
446 in_report_lengths=[4],
447 out_report_lengths=[0],
451 def disable() -> None:
452 """Do not present any USB HID devices to the host computer.
453 Can be called in ``boot.py``, before USB is connected.
454 The HID composite device is normally enabled by default,
455 but on some boards with limited endpoints, including STM32F4,
456 it is disabled by default. You must turn off another USB device such
457 as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
460 Path("%s/UDC" % gadget_root).write_text("")
461 except FileNotFoundError:
463 for symlink in Path(gadget_root).glob("configs/**/hid.usb*"):
466 for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"):
467 if strings_file.is_dir():
470 for strings_file in Path(gadget_root).rglob("configs/*/strings/*"):
471 if strings_file.is_dir():
473 for config_dir in Path(gadget_root).rglob("configs/*"):
474 if config_dir.is_dir():
476 for function_dir in Path(gadget_root).rglob("functions/*"):
477 if function_dir.is_dir():
480 Path(gadget_root).rmdir()
485 atexit.register(disable)
488 def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
489 """Specify which USB HID devices that will be available.
490 Can be called in ``boot.py``, before USB is connected.
492 :param Sequence devices: `Device` objects.
493 If `devices` is empty, HID is disabled. The order of the ``Devices``
494 may matter to the host. For instance, for MacOS, put the mouse device
495 before any Gamepad or Digitizer HID device or else it will not work.
496 :param int boot_device: If non-zero, inform the host that support for a
497 a boot HID device is available.
498 If ``boot_device=1``, a boot keyboard is available.
499 If ``boot_device=2``, a boot mouse is available. No other values are allowed.
502 If you enable too many devices at once, you will run out of USB endpoints.
503 The number of available endpoints varies by microcontroller.
504 CircuitPython will go into safe mode after running ``boot.py`` to inform you if
505 not enough endpoints are available.
509 Boot devices implement a fixed, predefined report descriptor, defined in
510 https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
511 can request to use the boot device if the USB device says it is available.
512 Usually only a BIOS or other kind of limited-functionality
513 host needs boot keyboard support.
515 For example, to make a boot keyboard available, you can use this code::
517 usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
519 If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
520 will be ignored, and the predefined report descriptor will be used.
521 But if the host does not request the boot keyboard,
522 the descriptor provided by `Device.KEYBOARD` will be used.
524 The HID boot device must usually be the first or only device presented by CircuitPython.
525 The HID device will be USB interface number 0.
526 To make sure it is the first device, disable other USB devices, including CDC and MSC (CIRCUITPY).
527 If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
528 will enter safe mode to report this error.
530 global _boot_device, devices
531 _boot_device = boot_device
534 shim for https://docs.circuitpython.org/en/latest/shared-bindings/usb_hid/index.html#usb_hid.enable
537 if len(requested_devices) == 0:
542 requested_devices = [Device.BOOT_KEYBOARD]
544 requested_devices = [Device.BOOT_MOUSE]
547 1. Creating the gadgets
548 -----------------------
550 For each gadget to be created its corresponding directory must be created::
552 $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
556 $ mkdir $CONFIGFS_HOME/usb_gadget/g1
562 $ cd $CONFIGFS_HOME/usb_gadget/g1
564 Each gadget needs to have its vendor id <VID> and product id <PID> specified::
566 $ echo <VID> > idVendor
567 $ echo <PID> > idProduct
569 A gadget also needs its serial number, manufacturer and product strings.
570 In order to have a place to store them, a strings subdirectory must be created
571 for each language, e.g.::
573 $ mkdir strings/0x409
575 Then the strings can be specified::
577 $ echo <serial number> > strings/0x409/serialnumber
578 $ echo <manufacturer> > strings/0x409/manufacturer
579 $ echo <product> > strings/0x409/product
581 Path("%s/functions" % gadget_root).mkdir(parents=True, exist_ok=True)
582 Path("%s/configs" % gadget_root).mkdir(parents=True, exist_ok=True)
583 Path("%s/bcdDevice" % gadget_root).write_text("%s" % 1) # Version 1.0.0
584 Path("%s/bcdUSB" % gadget_root).write_text("%s" % 0x0200) # USB 2.0
585 Path("%s/bDeviceClass" % gadget_root).write_text(
587 ) # multipurpose i guess?
588 Path("%s/bDeviceProtocol" % gadget_root).write_text("%s" % 0x00)
589 Path("%s/bDeviceSubClass" % gadget_root).write_text("%s" % 0x00)
590 Path("%s/bMaxPacketSize0" % gadget_root).write_text("%s" % 0x08)
591 Path("%s/idProduct" % gadget_root).write_text(
593 ) # Multifunction Composite Gadget
594 Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
596 2. Creating the configurations
597 ------------------------------
599 Each gadget will consist of a number of configurations, their corresponding
600 directories must be created:
602 $ mkdir configs/<name>.<number>
604 where <name> can be any string which is legal in a filesystem and the
605 <number> is the configuration's number, e.g.::
613 Each configuration also needs its strings, so a subdirectory must be created
614 for each language, e.g.::
616 $ mkdir configs/c.1/strings/0x409
618 Then the configuration string can be specified::
620 $ echo <configuration> > configs/c.1/strings/0x409/configuration
622 Some attributes can also be set for a configuration, e.g.::
624 $ echo 120 > configs/c.1/MaxPower
627 for i, device in enumerate(requested_devices):
628 config_root = "%s/configs/device.%s" % (gadget_root, i + 1)
629 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
630 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
631 Path("%s/strings/0x409/configuration" % config_root).write_text(
634 Path("%s/MaxPower" % config_root).write_text("150")
635 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
636 devices.append(device)
638 3. Creating the functions
639 -------------------------
641 The gadget will provide some functions, for each function its corresponding
642 directory must be created::
644 $ mkdir functions/<name>.<instance name>
646 where <name> corresponds to one of allowed function names and instance name
647 is an arbitrary string allowed in a filesystem, e.g.::
649 $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
655 Each function provides its specific set of attributes, with either read-only
656 or read-write access. Where applicable they need to be written to as
658 Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
663 for report_index, report_id in enumerate(device.report_ids):
664 function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id)
666 Path("%s/" % function_root).mkdir(parents=True)
667 except FileExistsError:
669 Path("%s/protocol" % function_root).write_text("%s" % report_id)
670 Path("%s/report_length" % function_root).write_text(
671 "%s" % device.in_report_lengths[report_index]
673 Path("%s/subclass" % function_root).write_text("%s" % 1)
674 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
676 4. Associating the functions with their configurations
677 ------------------------------------------------------
679 At this moment a number of gadgets is created, each of which has a number of
680 configurations specified and a number of functions available. What remains
681 is specifying which function is available in which configuration (the same
682 function can be used in multiple configurations). This is achieved with
683 creating symbolic links::
685 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
689 $ ln -s functions/ncm.usb0 configs/c.1
692 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
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
709 udc = next(Path("/sys/class/udc/").glob("*"))
710 Path("%s/UDC" % gadget_root).write_text("%s" % udc.name)