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