1 from typing import Sequence
2 from pathlib import Path
5 # https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
7 gadget_root = '/sys/kernel/config/usb_gadget/adafruit-blinka'
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
17 self.usage_page = usage_page
18 self.descriptor = descriptor
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:
27 Device.KEYBOARD = Device(
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
34 in_report_lengths=[8],
35 out_report_lengths=[1]
37 Device.MOUSE = Device(
38 descriptor=b"05010906a101050719e029e71500250175019508810275089501810175019503050819012903910275019505910175089506150026ff00050719002aff008100c0",
39 # Omitted for brevity.
43 in_report_lengths=[8],
44 out_report_lengths=[1],
47 Device.CONSUMER_CONTROL = Device(
48 descriptor=b"05010906a101050719e029e71500250175019508810275089501810175019503050819012903910275019505910175089506150026ff00050719002aff008100c0",
49 # Omitted for brevity.
53 in_report_lengths=[1],
54 out_report_lengths=[8],
56 devices = [Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL]
59 def disable() -> None:
61 Path('%s/UDC' % gadget_root).write_text('')
62 except FileNotFoundError:
64 for symlink in Path(gadget_root).glob('configs/**/hid.usb*'):
67 for strings_file in Path(gadget_root).rglob('configs/*/strings/*/*'):
68 if strings_file.is_dir():
71 for strings_file in Path(gadget_root).rglob('configs/*/strings/*'):
72 if strings_file.is_dir():
74 for config_dir in Path(gadget_root).rglob('configs/*'):
75 if config_dir.is_dir():
77 for function_dir in Path(gadget_root).rglob('functions/*'):
78 if function_dir.is_dir():
81 Path(gadget_root).rmdir()
84 atexit.register(disable)
86 def enable(devices: Sequence[Device], boot_device: int = 0) -> None:
91 1. Creating the gadgets
92 -----------------------
94 For each gadget to be created its corresponding directory must be created::
96 $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
100 $ mkdir $CONFIGFS_HOME/usb_gadget/g1
106 $ cd $CONFIGFS_HOME/usb_gadget/g1
108 Each gadget needs to have its vendor id <VID> and product id <PID> specified::
110 $ echo <VID> > idVendor
111 $ echo <PID> > idProduct
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.::
117 $ mkdir strings/0x409
119 Then the strings can be specified::
121 $ echo <serial number> > strings/0x409/serialnumber
122 $ echo <manufacturer> > strings/0x409/manufacturer
123 $ echo <product> > strings/0x409/product
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
136 2. Creating the configurations
137 ------------------------------
139 Each gadget will consist of a number of configurations, their corresponding
140 directories must be created:
142 $ mkdir configs/<name>.<number>
144 where <name> can be any string which is legal in a filesystem and the
145 <number> is the configuration's number, e.g.::
153 Each configuration also needs its strings, so a subdirectory must be created
154 for each language, e.g.::
156 $ mkdir configs/c.1/strings/0x409
158 Then the configuration string can be specified::
160 $ echo <configuration> > configs/c.1/strings/0x409/configuration
162 Some attributes can also be set for a configuration, e.g.::
164 $ echo 120 > configs/c.1/MaxPower
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)
176 3. Creating the functions
177 -------------------------
179 The gadget will provide some functions, for each function its corresponding
180 directory must be created::
182 $ mkdir functions/<name>.<instance name>
184 where <name> corresponds to one of allowed function names and instance name
185 is an arbitrary string allowed in a filesystem, e.g.::
187 $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
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
196 Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
201 for report_index, report_id in enumerate(device.report_ids):
202 function_root = '%s/functions/hid.usb%s' % (gadget_root, report_id)
204 Path('%s/' % function_root).mkdir(parents=True)
205 except FileExistsError:
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)
212 4. Associating the functions with their configurations
213 ------------------------------------------------------
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::
221 $ ln -s functions/<name>.<instance name> configs/<name>.<number>
225 $ ln -s functions/ncm.usb0 configs/c.1
228 Path('%s/hid.usb%s' % (config_root, report_id)).symlink_to(function_root)
230 5. Enabling the gadget
231 ----------------------
232 Such a gadget must be finally enabled so that the USB host can enumerate it.
234 In order to enable the gadget it must be bound to a UDC (USB Device
237 $ echo <udc name> > UDC
239 where <udc name> is one of those found in /sys/class/udc/*
242 $ echo s3c-hsotg > UDC
245 udc = next(Path('/sys/class/udc/').glob('*'))
246 Path('%s/UDC' % gadget_root).write_text('%s' % udc.name)