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