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"
29 return _active_devices
32 def get_boot_device() -> int:
37 """HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c"""
45 report_ids: Sequence[int],
46 in_report_lengths: Sequence[int],
47 out_report_lengths: Sequence[int],
49 self.out_report_lengths = out_report_lengths
50 self.in_report_lengths = in_report_lengths
51 self.report_ids = report_ids
53 self.usage_page = usage_page
54 self.descriptor = descriptor
55 self._last_received_report = None
57 def send_report(self, report: bytearray, report_id: int = None):
59 report_id = report_id or self.report_ids[0]
60 device_path = self.gets_device_path(report_id)
61 with open(device_path, "rb+") as fd:
62 fd.write(bytearray(report_id) + report)
65 def last_received_report(
68 return self.get_last_received_report()
70 def get_last_received_report(self, report_id=None) -> bytes:
71 device_path = self.gets_device_path(report_id or self.report_ids[0])
72 with open(device_path, "rb+") as fd:
73 os.set_blocking(fd.fileno(), False)
74 report = fd.read(self.out_report_lengths[0])
75 if report is not None:
76 self._last_received_report = report
77 return self._last_received_report
79 def gets_device_path(self, report_id):
82 "%s/functions/hid.usb%s/dev"
83 % (gadget_root, report_id or self.report_ids[0])
89 device_path = "/dev/hidg%s" % device
93 Device.KEYBOARD = Device(
97 0x01, # usage page (generic desktop ctrls)
99 0x06, # usage (keyboard)
101 0x01, # collection (application)
103 0x01, # Report ID (1)
105 0x07, # usage page (kbrd/keypad)
107 0xE0, # usage minimum (0xe0)
109 0xE7, # usage maximum (0xe7)
111 0x00, # logical minimum (0)
113 0x01, # logical maximum (1)
115 0x01, # report size (1)
117 0x08, # report count (8)
119 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
121 0x01, # report count (1)
123 0x08, # report size (8)
125 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
127 0x03, # report count (3)
129 0x01, # report size (1)
131 0x08, # usage page (leds)
133 0x01, # usage minimum (num lock)
135 0x05, # usage maximum (kana)
137 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
139 0x01, # report count (1)
141 0x05, # report size (5)
143 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
145 0x06, # report count (6)
147 0x08, # report size (8)
149 0x00, # logical minimum (0)
152 0x00, # logical maximum (255)
154 0x07, # usage page (kbrd/keypad)
156 0x00, # usage minimum (0x00)
159 0x00, # usage maximum (0xff)
161 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
162 0xC0, # end collection
168 in_report_lengths=[8],
169 out_report_lengths=[1],
171 Device.MOUSE = Device(
175 0x01, # Usage Page (Generic Desktop Ctrls)
177 0x02, # Usage (Mouse)
179 0x01, # Collection (Application)
181 0x02, # Report ID (2)
183 0x01, # Usage (Pointer)
185 0x00, # Collection (Physical)
187 0x09, # Usage Page (Button)
189 0x01, # Usage Minimum (0x01)
191 0x05, # Usage Maximum (0x05)
193 0x00, # Logical Minimum (0)
195 0x01, # Logical Maximum (1)
197 0x05, # Report Count (5)
199 0x01, # Report Size (1)
201 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
203 0x01, # Report Count (1)
205 0x03, # Report Size (3)
207 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
209 0x01, # Usage Page (Generic Desktop Ctrls)
215 0x81, # Logical Minimum (-127)
217 0x7F, # Logical Maximum (127)
219 0x08, # Report Size (8)
221 0x02, # Report Count (2)
223 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
225 0x38, # Usage (Wheel)
227 0x81, # Logical Minimum (-127)
229 0x7F, # Logical Maximum (127)
231 0x08, # Report Size (8)
233 0x01, # Report Count (1)
235 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
236 0xC0, # End Collection
237 0xC0, # End Collection
240 # Omitted for brevity.
244 in_report_lengths=[4],
245 out_report_lengths=[0],
248 Device.CONSUMER_CONTROL = Device(
252 0x0C, # Usage Page (Consumer)
254 0x01, # Usage (Consumer Control)
256 0x01, # Collection (Application)
258 0x03, # Report ID (3)
260 0x10, # Report Size (16)
262 0x01, # Report Count (1)
264 0x01, # Logical Minimum (1)
267 0x02, # Logical Maximum (652)
269 0x01, # Usage Minimum (Consumer Control)
272 0x02, # Usage Maximum (AC Send)
274 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
275 0xC0, # End Collection
278 # Omitted for brevity.
282 in_report_lengths=[2],
283 out_report_lengths=[0],
287 def disable() -> None:
289 Path("%s/UDC" % gadget_root).write_text("")
290 except FileNotFoundError:
292 for symlink in Path(gadget_root).glob("configs/**/hid.usb*"):
295 for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"):
296 if strings_file.is_dir():
299 for strings_file in Path(gadget_root).rglob("configs/*/strings/*"):
300 if strings_file.is_dir():
302 for config_dir in Path(gadget_root).rglob("configs/*"):
303 if config_dir.is_dir():
305 for function_dir in Path(gadget_root).rglob("functions/*"):
306 if function_dir.is_dir():
309 Path(gadget_root).rmdir()
314 atexit.register(disable)
317 def enable(devices: Sequence[Device], boot_device: int = 0) -> None:
319 Specify which USB HID devices that will be available. Can be called in boot.py, before USB is connected.
324 global _boot_device, _active_devices
325 _boot_device = boot_device
328 shim for https://docs.circuitpython.org/en/latest/shared-bindings/usb_hid/index.html#usb_hid.enable
331 if len(devices) == 0:
336 raise NotImplementedError("not supported yet")
339 1. Creating the gadgets
340 -----------------------
342 For each gadget to be created its corresponding directory must be created::
344 $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
348 $ mkdir $CONFIGFS_HOME/usb_gadget/g1
354 $ cd $CONFIGFS_HOME/usb_gadget/g1
356 Each gadget needs to have its vendor id <VID> and product id <PID> specified::
358 $ echo <VID> > idVendor
359 $ echo <PID> > idProduct
361 A gadget also needs its serial number, manufacturer and product strings.
362 In order to have a place to store them, a strings subdirectory must be created
363 for each language, e.g.::
365 $ mkdir strings/0x409
367 Then the strings can be specified::
369 $ echo <serial number> > strings/0x409/serialnumber
370 $ echo <manufacturer> > strings/0x409/manufacturer
371 $ echo <product> > strings/0x409/product
373 Path("%s/functions" % gadget_root).mkdir(parents=True, exist_ok=True)
374 Path("%s/configs" % gadget_root).mkdir(parents=True, exist_ok=True)
375 Path("%s/bcdDevice" % gadget_root).write_text("%s" % 1) # Version 1.0.0
376 Path("%s/bcdUSB" % gadget_root).write_text("%s" % 0x0200) # USB 2.0
377 Path("%s/bDeviceClass" % gadget_root).write_text(
379 ) # multipurpose i guess?
380 Path("%s/bDeviceProtocol" % gadget_root).write_text("%s" % 0x00)
381 Path("%s/bDeviceSubClass" % gadget_root).write_text("%s" % 0x00)
382 Path("%s/bMaxPacketSize0" % gadget_root).write_text("%s" % 0x08)
383 Path("%s/idProduct" % gadget_root).write_text(
385 ) # Multifunction Composite Gadget
386 Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
388 2. Creating the configurations
389 ------------------------------
391 Each gadget will consist of a number of configurations, their corresponding
392 directories must be created:
394 $ mkdir configs/<name>.<number>
396 where <name> can be any string which is legal in a filesystem and the
397 <number> is the configuration's number, e.g.::
405 Each configuration also needs its strings, so a subdirectory must be created
406 for each language, e.g.::
408 $ mkdir configs/c.1/strings/0x409
410 Then the configuration string can be specified::
412 $ echo <configuration> > configs/c.1/strings/0x409/configuration
414 Some attributes can also be set for a configuration, e.g.::
416 $ echo 120 > configs/c.1/MaxPower
419 for i, device in enumerate(devices):
421 config_root = "%s/configs/device.%s" % (gadget_root, 1)
422 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
423 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
424 Path("%s/strings/0x409/configuration" % config_root).write_text(
427 Path("%s/MaxPower" % config_root).write_text("150")
428 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
429 _active_devices.append(device)
431 3. Creating the functions
432 -------------------------
434 The gadget will provide some functions, for each function its corresponding
435 directory must be created::
437 $ mkdir functions/<name>.<instance name>
439 where <name> corresponds to one of allowed function names and instance name
440 is an arbitrary string allowed in a filesystem, e.g.::
442 $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
448 Each function provides its specific set of attributes, with either read-only
449 or read-write access. Where applicable they need to be written to as
451 Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
456 for report_index, report_id in enumerate(device.report_ids):
457 function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id)
459 Path("%s/" % function_root).mkdir(parents=True)
460 except FileExistsError:
462 Path("%s/protocol" % function_root).write_text("%s" % report_id)
463 Path("%s/report_length" % function_root).write_text(
464 "%s" % device.in_report_lengths[report_index]
466 Path("%s/subclass" % function_root).write_text("%s" % 1)
467 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
469 4. Associating the functions with their configurations
470 ------------------------------------------------------
472 At this moment a number of gadgets is created, each of which has a number of
473 configurations specified and a number of functions available. What remains
474 is specifying which function is available in which configuration (the same
475 function can be used in multiple configurations). This is achieved with
476 creating symbolic links::
478 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
482 $ ln -s functions/ncm.usb0 configs/c.1
485 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
487 5. Enabling the gadget
488 ----------------------
489 Such a gadget must be finally enabled so that the USB host can enumerate it.
491 In order to enable the gadget it must be bound to a UDC (USB Device
494 $ echo <udc name> > UDC
496 where <udc name> is one of those found in /sys/class/udc/*
499 $ echo s3c-hsotg > UDC
502 udc = next(Path("/sys/class/udc/").glob("*"))
503 Path("%s/UDC" % gadget_root).write_text("%s" % udc.name)