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