]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/usb_hid.py
basic hid module
[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 = Path('%s/functions/hid.usb%s/dev' % (gadget_root, report_id or self.report_ids[0])).read_text().strip().split(':')[1]
23         with open('/dev/hidg%s' % device, 'rb+') as fd:
24             fd.write(report)
25
26
27 Device.KEYBOARD = Device(
28     descriptor=bytes((
29         0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x75, 0x08, 0x95, 0x01, 0x81, 0x01, 0x75, 0x01, 0x95, 0x03, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, 0x02, 0x75, 0x01, 0x95, 0x05, 0x91, 0x01, 0x75, 0x08, 0x95, 0x06, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0
30     )),
31     usage_page=0x1,
32     usage=0x6,
33     report_ids=[1],
34     in_report_lengths=[8],
35     out_report_lengths=[1]
36 )
37 Device.MOUSE = Device(
38     descriptor=b"05010906a101050719e029e71500250175019508810275089501810175019503050819012903910275019505910175089506150026ff00050719002aff008100c0",
39     # Omitted for brevity.
40     usage_page=0x1,
41     usage=0x02,
42     report_ids=[2],
43     in_report_lengths=[8],
44     out_report_lengths=[1],
45 )
46
47 Device.CONSUMER_CONTROL = Device(
48     descriptor=b"05010906a101050719e029e71500250175019508810275089501810175019503050819012903910275019505910175089506150026ff00050719002aff008100c0",
49     # Omitted for brevity.
50     usage_page=0x0C,
51     usage=0x01,
52     report_ids=[3],
53     in_report_lengths=[1],
54     out_report_lengths=[8],
55 )
56 devices = [Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL]
57
58
59 def disable() -> None:
60     try:
61         Path('%s/UDC' % gadget_root).write_text('')
62     except FileNotFoundError:
63         pass
64     for symlink in Path(gadget_root).glob('configs/**/hid.usb*'):
65         symlink.unlink()
66
67     for strings_file in Path(gadget_root).rglob('configs/*/strings/*/*'):
68         if strings_file.is_dir():
69             strings_file.rmdir()
70
71     for strings_file in Path(gadget_root).rglob('configs/*/strings/*'):
72         if strings_file.is_dir():
73             strings_file.rmdir()
74     for config_dir in Path(gadget_root).rglob('configs/*'):
75         if config_dir.is_dir():
76             config_dir.rmdir()
77     for function_dir in Path(gadget_root).rglob('functions/*'):
78         if function_dir.is_dir():
79             function_dir.rmdir()
80     try:
81         Path(gadget_root).rmdir()
82     except:
83         pass
84 atexit.register(disable)
85
86 def enable(devices: Sequence[Device], boot_device: int = 0) -> None:
87     if len(devices) == 0:
88         disable()
89         return
90     """
91     1. Creating the gadgets
92     -----------------------
93     
94     For each gadget to be created its corresponding directory must be created::
95     
96         $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
97     
98     e.g.::
99     
100         $ mkdir $CONFIGFS_HOME/usb_gadget/g1
101     
102         ...
103         ...
104         ...
105     
106         $ cd $CONFIGFS_HOME/usb_gadget/g1
107     
108     Each gadget needs to have its vendor id <VID> and product id <PID> specified::
109     
110         $ echo <VID> > idVendor
111         $ echo <PID> > idProduct
112     
113     A gadget also needs its serial number, manufacturer and product strings.
114     In order to have a place to store them, a strings subdirectory must be created
115     for each language, e.g.::
116     
117         $ mkdir strings/0x409
118     
119     Then the strings can be specified::
120     
121         $ echo <serial number> > strings/0x409/serialnumber
122         $ echo <manufacturer> > strings/0x409/manufacturer
123         $ echo <product> > strings/0x409/product
124     """
125     Path('%s/functions' % gadget_root).mkdir(parents=True, exist_ok=True)
126     Path('%s/configs' % gadget_root).mkdir(parents=True, exist_ok=True)
127     Path('%s/bcdDevice' % gadget_root).write_text('%s' % 1)  # Version 1.0.0
128     Path('%s/bcdUSB' % gadget_root).write_text('%s' % 0x0200)  # USB 2.0
129     Path('%s/bDeviceClass' % gadget_root).write_text('%s' % 0xEF)  # multipurpose i guess?
130     Path('%s/bDeviceProtocol' % gadget_root).write_text('%s' % 0x01)
131     Path('%s/bDeviceSubClass' % gadget_root).write_text('%s' % 0x02)
132     Path('%s/bMaxPacketSize0' % gadget_root).write_text('%s' % 0x08)
133     Path('%s/idProduct' % gadget_root).write_text('%s' % 0x0104)  # Multifunction Composite Gadget
134     Path('%s/idVendor' % gadget_root).write_text('%s' % 0x1d6b)  # Linux Foundation
135     """
136     2. Creating the configurations
137     ------------------------------
138     
139     Each gadget will consist of a number of configurations, their corresponding
140     directories must be created:
141     
142     $ mkdir configs/<name>.<number>
143     
144     where <name> can be any string which is legal in a filesystem and the
145     <number> is the configuration's number, e.g.::
146     
147         $ mkdir configs/c.1
148     
149         ...
150         ...
151         ...
152     
153     Each configuration also needs its strings, so a subdirectory must be created
154     for each language, e.g.::
155     
156         $ mkdir configs/c.1/strings/0x409
157     
158     Then the configuration string can be specified::
159     
160         $ echo <configuration> > configs/c.1/strings/0x409/configuration
161     
162     Some attributes can also be set for a configuration, e.g.::
163     
164         $ echo 120 > configs/c.1/MaxPower
165         """
166
167     for i, device in enumerate(devices):
168         config_root = '%s/configs/device.%s' % (gadget_root, i + 1)
169         Path('%s/' % config_root).mkdir(parents=True, exist_ok=True)
170         Path('%s/strings/0x409' % config_root).mkdir(parents=True, exist_ok=True)
171         Path('%s/strings/0x409/configuration' % config_root).write_text('my configuration')
172         Path('%s/MaxPower' % config_root).write_text('150')
173         Path('%s/bmAttributes' % config_root).write_text('%s' % 0x080)
174
175         """
176         3. Creating the functions
177         -------------------------
178         
179         The gadget will provide some functions, for each function its corresponding
180         directory must be created::
181         
182             $ mkdir functions/<name>.<instance name>
183         
184         where <name> corresponds to one of allowed function names and instance name
185         is an arbitrary string allowed in a filesystem, e.g.::
186         
187           $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
188         
189           ...
190           ...
191           ...
192         
193         Each function provides its specific set of attributes, with either read-only
194         or read-write access. Where applicable they need to be written to as
195         appropriate.
196         Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
197
198         """
199
200         # create functions
201         for report_index, report_id in enumerate(device.report_ids):
202             function_root = '%s/functions/hid.usb%s' % (gadget_root, report_id)
203             try:
204                 Path('%s/' % function_root).mkdir(parents=True)
205             except FileExistsError:
206                 continue
207             Path('%s/protocol' % function_root).write_text('%s' % 1)
208             Path('%s/report_length' % function_root).write_text('%s' % device.in_report_lengths[report_index])
209             Path('%s/subclass' % function_root).write_text('%s' % 1)
210             Path('%s/report_desc' % function_root).write_bytes(device.descriptor)
211             """
212             4. Associating the functions with their configurations
213             ------------------------------------------------------
214             
215             At this moment a number of gadgets is created, each of which has a number of
216             configurations specified and a number of functions available. What remains
217             is specifying which function is available in which configuration (the same
218             function can be used in multiple configurations). This is achieved with
219             creating symbolic links::
220             
221                 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
222             
223             e.g.::
224             
225                 $ ln -s functions/ncm.usb0 configs/c.1
226             """
227
228             Path('%s/hid.usb%s' % (config_root, report_id)).symlink_to(function_root)
229     """
230     5. Enabling the gadget
231     ----------------------
232     Such a gadget must be finally enabled so that the USB host can enumerate it.
233     
234     In order to enable the gadget it must be bound to a UDC (USB Device
235     Controller)::
236     
237         $ echo <udc name> > UDC
238     
239     where <udc name> is one of those found in /sys/class/udc/*
240     e.g.::
241
242     $ echo s3c-hsotg > UDC
243
244     """
245     udc = next(Path('/sys/class/udc/').glob('*'))
246     Path('%s/UDC' % gadget_root).write_text('%s' % udc.name)