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 * Author(s): Björn Bösel
10 from typing import Sequence
11 from pathlib import Path
15 # https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
17 gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
21 """HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c"""
29 report_ids: Sequence[int],
30 in_report_lengths: Sequence[int],
31 out_report_lengths: Sequence[int],
33 self.out_report_lengths = out_report_lengths
34 self.in_report_lengths = in_report_lengths
35 self.report_ids = report_ids
37 self.usage_page = usage_page
38 self.descriptor = descriptor
39 self._last_received_report = None
41 def send_report(self, report: bytearray, report_id: int = None):
42 device_path = self.gets_device_path(report_id)
43 with open(device_path, "rb+") as fd:
44 fd.write(bytearray(report_id) + report)
47 def last_received_report(
50 device_path = self.gets_device_path(self.report_ids[0])
51 with open(device_path, "rb+") as fd:
52 os.set_blocking(fd.fileno(), False)
53 report = fd.read(self.out_report_lengths[0])
54 if report is not None:
55 self._last_received_report = report
56 return self._last_received_report
58 def gets_device_path(self, report_id):
61 "%s/functions/hid.usb%s/dev"
62 % (gadget_root, report_id or self.report_ids[0])
68 device_path = "/dev/hidg%s" % device
72 Device.KEYBOARD = Device(
76 0x01, # usage page (generic desktop ctrls)
78 0x06, # usage (keyboard)
80 0x01, # collection (application)
84 0x07, # usage page (kbrd/keypad)
86 0xE0, # usage minimum (0xe0)
88 0xE7, # usage maximum (0xe7)
90 0x00, # logical minimum (0)
92 0x01, # logical maximum (1)
94 0x01, # report size (1)
96 0x08, # report count (8)
98 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position)
100 0x01, # report count (1)
102 0x08, # report size (8)
104 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position)
106 0x03, # report count (3)
108 0x01, # report size (1)
110 0x08, # usage page (leds)
112 0x01, # usage minimum (num lock)
114 0x05, # usage maximum (kana)
116 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
118 0x01, # report count (1)
120 0x05, # report size (5)
122 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
124 0x06, # report count (6)
126 0x08, # report size (8)
128 0x00, # logical minimum (0)
131 0x00, # logical maximum (255)
133 0x07, # usage page (kbrd/keypad)
135 0x00, # usage minimum (0x00)
138 0x00, # usage maximum (0xff)
140 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position)
141 0xC0, # end collection
147 in_report_lengths=[8],
148 out_report_lengths=[1],
150 Device.MOUSE = Device(
154 0x01, # Usage Page (Generic Desktop Ctrls)
156 0x02, # Usage (Mouse)
158 0x01, # Collection (Application)
160 0x02, # Report ID (2)
162 0x01, # Usage (Pointer)
164 0x00, # Collection (Physical)
166 0x09, # Usage Page (Button)
168 0x01, # Usage Minimum (0x01)
170 0x05, # Usage Maximum (0x05)
172 0x00, # Logical Minimum (0)
174 0x01, # Logical Maximum (1)
176 0x05, # Report Count (5)
178 0x01, # Report Size (1)
180 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
182 0x01, # Report Count (1)
184 0x03, # Report Size (3)
186 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
188 0x01, # Usage Page (Generic Desktop Ctrls)
194 0x81, # Logical Minimum (-127)
196 0x7F, # Logical Maximum (127)
198 0x08, # Report Size (8)
200 0x02, # Report Count (2)
202 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
204 0x38, # Usage (Wheel)
206 0x81, # Logical Minimum (-127)
208 0x7F, # Logical Maximum (127)
210 0x08, # Report Size (8)
212 0x01, # Report Count (1)
214 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
215 0xC0, # End Collection
216 0xC0, # End Collection
219 # Omitted for brevity.
223 in_report_lengths=[4],
224 out_report_lengths=[0],
227 Device.CONSUMER_CONTROL = Device(
231 0x0C, # Usage Page (Consumer)
233 0x01, # Usage (Consumer Control)
235 0x01, # Collection (Application)
237 0x03, # Report ID (3)
239 0x10, # Report Size (16)
241 0x01, # Report Count (1)
243 0x01, # Logical Minimum (1)
246 0x02, # Logical Maximum (652)
248 0x01, # Usage Minimum (Consumer Control)
251 0x02, # Usage Maximum (AC Send)
253 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
254 0xC0, # End Collection
257 # Omitted for brevity.
261 in_report_lengths=[2],
262 out_report_lengths=[0],
264 devices = [Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL]
267 def disable() -> None:
269 Path("%s/UDC" % gadget_root).write_text("")
270 except FileNotFoundError:
272 for symlink in Path(gadget_root).glob("configs/**/hid.usb*"):
275 for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"):
276 if strings_file.is_dir():
279 for strings_file in Path(gadget_root).rglob("configs/*/strings/*"):
280 if strings_file.is_dir():
282 for config_dir in Path(gadget_root).rglob("configs/*"):
283 if config_dir.is_dir():
285 for function_dir in Path(gadget_root).rglob("functions/*"):
286 if function_dir.is_dir():
289 Path(gadget_root).rmdir()
294 # atexit.register(disable)
297 def enable(devices: Sequence[Device], boot_device: int = 0) -> None:
298 if len(devices) == 0:
302 1. Creating the gadgets
303 -----------------------
305 For each gadget to be created its corresponding directory must be created::
307 $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
311 $ mkdir $CONFIGFS_HOME/usb_gadget/g1
317 $ cd $CONFIGFS_HOME/usb_gadget/g1
319 Each gadget needs to have its vendor id <VID> and product id <PID> specified::
321 $ echo <VID> > idVendor
322 $ echo <PID> > idProduct
324 A gadget also needs its serial number, manufacturer and product strings.
325 In order to have a place to store them, a strings subdirectory must be created
326 for each language, e.g.::
328 $ mkdir strings/0x409
330 Then the strings can be specified::
332 $ echo <serial number> > strings/0x409/serialnumber
333 $ echo <manufacturer> > strings/0x409/manufacturer
334 $ echo <product> > strings/0x409/product
336 Path("%s/functions" % gadget_root).mkdir(parents=True, exist_ok=True)
337 Path("%s/configs" % gadget_root).mkdir(parents=True, exist_ok=True)
338 Path("%s/bcdDevice" % gadget_root).write_text("%s" % 1) # Version 1.0.0
339 Path("%s/bcdUSB" % gadget_root).write_text("%s" % 0x0200) # USB 2.0
340 Path("%s/bDeviceClass" % gadget_root).write_text(
342 ) # multipurpose i guess?
343 Path("%s/bDeviceProtocol" % gadget_root).write_text("%s" % 0x00)
344 Path("%s/bDeviceSubClass" % gadget_root).write_text("%s" % 0x00)
345 Path("%s/bMaxPacketSize0" % gadget_root).write_text("%s" % 0x08)
346 Path("%s/idProduct" % gadget_root).write_text(
348 ) # Multifunction Composite Gadget
349 Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation
351 2. Creating the configurations
352 ------------------------------
354 Each gadget will consist of a number of configurations, their corresponding
355 directories must be created:
357 $ mkdir configs/<name>.<number>
359 where <name> can be any string which is legal in a filesystem and the
360 <number> is the configuration's number, e.g.::
368 Each configuration also needs its strings, so a subdirectory must be created
369 for each language, e.g.::
371 $ mkdir configs/c.1/strings/0x409
373 Then the configuration string can be specified::
375 $ echo <configuration> > configs/c.1/strings/0x409/configuration
377 Some attributes can also be set for a configuration, e.g.::
379 $ echo 120 > configs/c.1/MaxPower
382 for i, device in enumerate(devices):
384 config_root = "%s/configs/device.%s" % (gadget_root, 1)
385 Path("%s/" % config_root).mkdir(parents=True, exist_ok=True)
386 Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True)
387 Path("%s/strings/0x409/configuration" % config_root).write_text(
390 Path("%s/MaxPower" % config_root).write_text("150")
391 Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
393 3. Creating the functions
394 -------------------------
396 The gadget will provide some functions, for each function its corresponding
397 directory must be created::
399 $ mkdir functions/<name>.<instance name>
401 where <name> corresponds to one of allowed function names and instance name
402 is an arbitrary string allowed in a filesystem, e.g.::
404 $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
410 Each function provides its specific set of attributes, with either read-only
411 or read-write access. Where applicable they need to be written to as
413 Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
418 for report_index, report_id in enumerate(device.report_ids):
419 function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id)
421 Path("%s/" % function_root).mkdir(parents=True)
422 except FileExistsError:
424 Path("%s/protocol" % function_root).write_text("%s" % report_id)
425 Path("%s/report_length" % function_root).write_text(
426 "%s" % device.in_report_lengths[report_index]
428 Path("%s/subclass" % function_root).write_text("%s" % 1)
429 Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
431 4. Associating the functions with their configurations
432 ------------------------------------------------------
434 At this moment a number of gadgets is created, each of which has a number of
435 configurations specified and a number of functions available. What remains
436 is specifying which function is available in which configuration (the same
437 function can be used in multiple configurations). This is achieved with
438 creating symbolic links::
440 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
444 $ ln -s functions/ncm.usb0 configs/c.1
447 Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
449 5. Enabling the gadget
450 ----------------------
451 Such a gadget must be finally enabled so that the USB host can enumerate it.
453 In order to enable the gadget it must be bound to a UDC (USB Device
456 $ echo <udc name> > UDC
458 where <udc name> is one of those found in /sys/class/udc/*
461 $ echo s3c-hsotg > UDC
464 udc = next(Path("/sys/class/udc/").glob("*"))
465 Path("%s/UDC" % gadget_root).write_text("%s" % udc.name)