]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/usb_hid.py
1bf6c3e273f593709ee1f0704656f4d1cdcde76c
[Adafruit_Blinka-hackapet.git] / src / usb_hid.py
1 """
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
6
7 * Author(s): Björn Bösel
8 """
9
10 from typing import Sequence
11 from pathlib import Path
12 import os
13 import atexit
14
15 # https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
16
17 gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka"
18
19
20 class Device:
21     """HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c"""
22
23     def __init__(
24         self,
25         *,
26         descriptor: bytes,
27         usage_page: int,
28         usage: int,
29         report_ids: Sequence[int],
30         in_report_lengths: Sequence[int],
31         out_report_lengths: Sequence[int],
32     ) -> None:
33         self.out_report_lengths = out_report_lengths
34         self.in_report_lengths = in_report_lengths
35         self.report_ids = report_ids
36         self.usage = usage
37         self.usage_page = usage_page
38         self.descriptor = descriptor
39         self._last_received_report = None
40
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)
45
46     @property
47     def last_received_report(
48         self,
49     ):
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
57
58     def gets_device_path(self, report_id):
59         device = (
60             Path(
61                 "%s/functions/hid.usb%s/dev"
62                 % (gadget_root, report_id or self.report_ids[0])
63             )
64             .read_text()
65             .strip()
66             .split(":")[1]
67         )
68         device_path = "/dev/hidg%s" % device
69         return device_path
70
71
72 Device.KEYBOARD = Device(
73     descriptor=bytes(
74         (
75             0x05,
76             0x01,  # usage page (generic desktop ctrls)
77             0x09,
78             0x06,  # usage (keyboard)
79             0xA1,
80             0x01,  # collection (application)
81             0x85,
82             0x01,  # Report ID (1)
83             0x05,
84             0x07,  # usage page (kbrd/keypad)
85             0x19,
86             0xE0,  # usage minimum (0xe0)
87             0x29,
88             0xE7,  # usage maximum (0xe7)
89             0x15,
90             0x00,  # logical minimum (0)
91             0x25,
92             0x01,  # logical maximum (1)
93             0x75,
94             0x01,  # report size (1)
95             0x95,
96             0x08,  # report count (8)
97             0x81,
98             0x02,  # input (data,var,abs,no wrap,linear,preferred state,no null position)
99             0x95,
100             0x01,  # report count (1)
101             0x75,
102             0x08,  # report size (8)
103             0x81,
104             0x01,  # input (const,array,abs,no wrap,linear,preferred state,no null position)
105             0x95,
106             0x03,  # report count (3)
107             0x75,
108             0x01,  # report size (1)
109             0x05,
110             0x08,  # usage page (leds)
111             0x19,
112             0x01,  # usage minimum (num lock)
113             0x29,
114             0x05,  # usage maximum (kana)
115             0x91,
116             0x02,  # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile)
117             0x95,
118             0x01,  # report count (1)
119             0x75,
120             0x05,  # report size (5)
121             0x91,
122             0x01,  # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile)
123             0x95,
124             0x06,  # report count (6)
125             0x75,
126             0x08,  # report size (8)
127             0x15,
128             0x00,  # logical minimum (0)
129             0x26,
130             0xFF,
131             0x00,  # logical maximum (255)
132             0x05,
133             0x07,  # usage page (kbrd/keypad)
134             0x19,
135             0x00,  # usage minimum (0x00)
136             0x2A,
137             0xFF,
138             0x00,  # usage maximum (0xff)
139             0x81,
140             0x00,  # input (data,array,abs,no wrap,linear,preferred state,no null position)
141             0xC0,  # end collection
142         )
143     ),
144     usage_page=0x1,
145     usage=0x6,
146     report_ids=[0x1],
147     in_report_lengths=[8],
148     out_report_lengths=[1],
149 )
150 Device.MOUSE = Device(
151     descriptor=bytes(
152         (
153             0x05,
154             0x01,  # Usage Page (Generic Desktop Ctrls)
155             0x09,
156             0x02,  # Usage (Mouse)
157             0xA1,
158             0x01,  # Collection (Application)
159             0x85,
160             0x02,  # Report ID (2)
161             0x09,
162             0x01,  # Usage (Pointer)
163             0xA1,
164             0x00,  # Collection (Physical)
165             0x05,
166             0x09,  # Usage Page (Button)
167             0x19,
168             0x01,  # Usage Minimum (0x01)
169             0x29,
170             0x05,  # Usage Maximum (0x05)
171             0x15,
172             0x00,  # Logical Minimum (0)
173             0x25,
174             0x01,  # Logical Maximum (1)
175             0x95,
176             0x05,  # Report Count (5)
177             0x75,
178             0x01,  # Report Size (1)
179             0x81,
180             0x02,  # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
181             0x95,
182             0x01,  # Report Count (1)
183             0x75,
184             0x03,  # Report Size (3)
185             0x81,
186             0x01,  # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
187             0x05,
188             0x01,  # Usage Page (Generic Desktop Ctrls)
189             0x09,
190             0x30,  # Usage (X)
191             0x09,
192             0x31,  # Usage (Y)
193             0x15,
194             0x81,  # Logical Minimum (-127)
195             0x25,
196             0x7F,  # Logical Maximum (127)
197             0x75,
198             0x08,  # Report Size (8)
199             0x95,
200             0x02,  # Report Count (2)
201             0x81,
202             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
203             0x09,
204             0x38,  # Usage (Wheel)
205             0x15,
206             0x81,  # Logical Minimum (-127)
207             0x25,
208             0x7F,  # Logical Maximum (127)
209             0x75,
210             0x08,  # Report Size (8)
211             0x95,
212             0x01,  # Report Count (1)
213             0x81,
214             0x06,  # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
215             0xC0,  # End Collection
216             0xC0,  # End Collection
217         )
218     ),
219     # Omitted for brevity.
220     usage_page=0x1,
221     usage=0x02,
222     report_ids=[2],
223     in_report_lengths=[4],
224     out_report_lengths=[0],
225 )
226
227 Device.CONSUMER_CONTROL = Device(
228     descriptor=bytes(
229         (
230             0x05,
231             0x0C,  # Usage Page (Consumer)
232             0x09,
233             0x01,  # Usage (Consumer Control)
234             0xA1,
235             0x01,  # Collection (Application)
236             0x85,
237             0x03,  # Report ID (3)
238             0x75,
239             0x10,  # Report Size (16)
240             0x95,
241             0x01,  # Report Count (1)
242             0x15,
243             0x01,  # Logical Minimum (1)
244             0x26,
245             0x8C,
246             0x02,  # Logical Maximum (652)
247             0x19,
248             0x01,  # Usage Minimum (Consumer Control)
249             0x2A,
250             0x8C,
251             0x02,  # Usage Maximum (AC Send)
252             0x81,
253             0x00,  # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
254             0xC0,  # End Collection
255         )
256     ),
257     # Omitted for brevity.
258     usage_page=0x0C,
259     usage=0x01,
260     report_ids=[3],
261     in_report_lengths=[2],
262     out_report_lengths=[0],
263 )
264 devices = [Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL]
265
266
267 def disable() -> None:
268     try:
269         Path("%s/UDC" % gadget_root).write_text("")
270     except FileNotFoundError:
271         pass
272     for symlink in Path(gadget_root).glob("configs/**/hid.usb*"):
273         symlink.unlink()
274
275     for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"):
276         if strings_file.is_dir():
277             strings_file.rmdir()
278
279     for strings_file in Path(gadget_root).rglob("configs/*/strings/*"):
280         if strings_file.is_dir():
281             strings_file.rmdir()
282     for config_dir in Path(gadget_root).rglob("configs/*"):
283         if config_dir.is_dir():
284             config_dir.rmdir()
285     for function_dir in Path(gadget_root).rglob("functions/*"):
286         if function_dir.is_dir():
287             function_dir.rmdir()
288     try:
289         Path(gadget_root).rmdir()
290     except:
291         pass
292
293
294 # atexit.register(disable)
295
296
297 def enable(devices: Sequence[Device], boot_device: int = 0) -> None:
298     if len(devices) == 0:
299         disable()
300         return
301     """
302     1. Creating the gadgets
303     -----------------------
304
305     For each gadget to be created its corresponding directory must be created::
306
307         $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
308
309     e.g.::
310
311         $ mkdir $CONFIGFS_HOME/usb_gadget/g1
312
313         ...
314         ...
315         ...
316
317         $ cd $CONFIGFS_HOME/usb_gadget/g1
318
319     Each gadget needs to have its vendor id <VID> and product id <PID> specified::
320
321         $ echo <VID> > idVendor
322         $ echo <PID> > idProduct
323
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.::
327
328         $ mkdir strings/0x409
329
330     Then the strings can be specified::
331
332         $ echo <serial number> > strings/0x409/serialnumber
333         $ echo <manufacturer> > strings/0x409/manufacturer
334         $ echo <product> > strings/0x409/product
335     """
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(
341         "%s" % 0x00
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(
347         "%s" % 0x0104
348     )  # Multifunction Composite Gadget
349     Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B)  # Linux Foundation
350     """
351     2. Creating the configurations
352     ------------------------------
353
354     Each gadget will consist of a number of configurations, their corresponding
355     directories must be created:
356
357     $ mkdir configs/<name>.<number>
358
359     where <name> can be any string which is legal in a filesystem and the
360     <number> is the configuration's number, e.g.::
361
362         $ mkdir configs/c.1
363
364         ...
365         ...
366         ...
367
368     Each configuration also needs its strings, so a subdirectory must be created
369     for each language, e.g.::
370
371         $ mkdir configs/c.1/strings/0x409
372
373     Then the configuration string can be specified::
374
375         $ echo <configuration> > configs/c.1/strings/0x409/configuration
376
377     Some attributes can also be set for a configuration, e.g.::
378
379         $ echo 120 > configs/c.1/MaxPower
380         """
381
382     for i, device in enumerate(devices):
383
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(
388             "my configuration"
389         )
390         Path("%s/MaxPower" % config_root).write_text("150")
391         Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
392         """
393         3. Creating the functions
394         -------------------------
395
396         The gadget will provide some functions, for each function its corresponding
397         directory must be created::
398
399             $ mkdir functions/<name>.<instance name>
400
401         where <name> corresponds to one of allowed function names and instance name
402         is an arbitrary string allowed in a filesystem, e.g.::
403
404           $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
405
406           ...
407           ...
408           ...
409
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
412         appropriate.
413         Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
414
415         """
416
417         # create functions
418         for report_index, report_id in enumerate(device.report_ids):
419             function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id)
420             try:
421                 Path("%s/" % function_root).mkdir(parents=True)
422             except FileExistsError:
423                 continue
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]
427             )
428             Path("%s/subclass" % function_root).write_text("%s" % 1)
429             Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
430             """
431             4. Associating the functions with their configurations
432             ------------------------------------------------------
433
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::
439
440                 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
441
442             e.g.::
443
444                 $ ln -s functions/ncm.usb0 configs/c.1
445             """
446
447             Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
448     """
449     5. Enabling the gadget
450     ----------------------
451     Such a gadget must be finally enabled so that the USB host can enumerate it.
452
453     In order to enable the gadget it must be bound to a UDC (USB Device
454     Controller)::
455
456         $ echo <udc name> > UDC
457
458     where <udc name> is one of those found in /sys/class/udc/*
459     e.g.::
460
461     $ echo s3c-hsotg > UDC
462
463     """
464     udc = next(Path("/sys/class/udc/").glob("*"))
465     Path("%s/UDC" % gadget_root).write_text("%s" % udc.name)