1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
|
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Linux gadgetfs glue.
Exposes a USB gadget using a USB peripheral controller on Linux. The userspace
ABI is documented here:
https://github.com/torvalds/linux/blob/master/drivers/usb/gadget/inode.c
"""
import errno
import multiprocessing
import os
import struct
from tornado import ioloop
import usb_constants
import usb_descriptors
GADGETFS_NOP = 0
GADGETFS_CONNECT = 1
GADGETFS_DISCONNECT = 2
GADGETFS_SETUP = 3
GADGETFS_SUSPEND = 4
BULK = 0x01
INTERRUPT = 0x02
ISOCHRONOUS = 0x04
USB_TRANSFER_TYPE_TO_MASK = {
usb_constants.TransferType.BULK: BULK,
usb_constants.TransferType.INTERRUPT: INTERRUPT,
usb_constants.TransferType.ISOCHRONOUS: ISOCHRONOUS
}
IN = 0x01
OUT = 0x02
HARDWARE = {
'beaglebone-black': (
'musb-hdrc', # Gadget controller name,
{
0x01: ('ep1out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x81: ('ep1in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x02: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x82: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x03: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x83: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x04: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x84: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x05: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x85: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x06: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x86: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x07: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x87: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x08: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x88: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x09: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x89: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x0A: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x8A: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x0B: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x8B: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x0C: ('ep2out', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x8C: ('ep2in', BULK | INTERRUPT | ISOCHRONOUS, 512),
0x0D: ('ep13', BULK | INTERRUPT | ISOCHRONOUS, 4096),
0x8D: ('ep13', BULK | INTERRUPT | ISOCHRONOUS, 4096),
0x0E: ('ep14', BULK | INTERRUPT | ISOCHRONOUS, 1024),
0x8E: ('ep14', BULK | INTERRUPT | ISOCHRONOUS, 1024),
0x0F: ('ep15', BULK | INTERRUPT | ISOCHRONOUS, 1024),
0x8F: ('ep15', BULK | INTERRUPT | ISOCHRONOUS, 1024),
}
)
}
class LinuxGadgetfs(object):
"""Linux gadgetfs-based gadget driver.
"""
def __init__(self, hardware, mountpoint='/dev/gadget'):
"""Initialize bindings to the Linux gadgetfs interface.
Args:
hardware: Hardware type.
mountpoint: Gadget filesystem mount point.
"""
self._chip, self._hw_eps = HARDWARE[hardware]
self._ep_dir = mountpoint
self._gadget = None
self._fd = None
# map from bEndpointAddress to hardware ep name and open file descriptor
self._ep_fds = {}
self._io_loop = ioloop.IOLoop.current()
def Create(self, gadget):
"""Bind a gadget to the USB peripheral controller."""
self._gadget = gadget
self._fd = os.open(os.path.join(self._ep_dir, self._chip), os.O_RDWR)
buf = ''.join([struct.pack('=I', 0),
gadget.GetFullSpeedConfigurationDescriptor().Encode(),
gadget.GetHighSpeedConfigurationDescriptor().Encode(),
gadget.GetDeviceDescriptor().Encode()])
os.write(self._fd, buf)
self._io_loop.add_handler(self._fd, self.HandleEvent, self._io_loop.READ)
def Destroy(self):
"""Unbind the gadget from the USB peripheral controller."""
self.Disconnected()
self._io_loop.remove_handler(self._fd)
os.close(self._fd)
self._gadget = None
self._fd = None
def IsConfigured(self):
return self._gadget is not None
def HandleEvent(self, unused_fd, unused_events):
buf = os.read(self._fd, 12)
event_type, = struct.unpack_from('=I', buf, 8)
if event_type == GADGETFS_NOP:
print 'NOP'
elif event_type == GADGETFS_CONNECT:
speed, = struct.unpack('=Ixxxxxxxx', buf)
self.Connected(speed)
elif event_type == GADGETFS_DISCONNECT:
self.Disconnected()
elif event_type == GADGETFS_SETUP:
request_type, request, value, index, length = struct.unpack(
'<BBHHHxxxx', buf)
self.HandleSetup(request_type, request, value, index, length)
elif event_type == GADGETFS_SUSPEND:
print 'SUSPEND'
else:
print 'Unknown gadgetfs event type:', event_type
def Connected(self, speed):
print 'CONNECT speed={}'.format(speed)
self._gadget.Connected(self, speed)
def Disconnected(self):
print 'DISCONNECT'
for endpoint_addr in self._ep_fds.keys():
self.StopEndpoint(endpoint_addr)
self._ep_fds.clear()
self._gadget.Disconnected()
def HandleSetup(self, request_type, request, value, index, length):
print ('SETUP bmRequestType=0x{:02X} bRequest=0x{:02X} wValue=0x{:04X} '
'wIndex=0x{:04X} wLength={}'
.format(request_type, request, value, index, length))
if request_type & usb_constants.Dir.IN:
data = self._gadget.ControlRead(
request_type, request, value, index, length)
if data is None:
print 'SETUP STALL'
try:
os.read(self._fd, 0) # Backwards I/O stalls the pipe.
except OSError, e:
# gadgetfs always returns EL2HLT which we should ignore.
if e.errno != errno.EL2HLT:
raise
else:
os.write(self._fd, data)
else:
data = ''
if length:
data = os.read(self._fd, length)
result = self._gadget.ControlWrite(
request_type, request, value, index, data)
if result is None:
print 'SETUP STALL'
try:
os.write(self._fd, '') # Backwards I/O stalls the pipe.
except OSError, e:
# gadgetfs always returns EL2HLT which we should ignore.
if e.errno != errno.EL2HLT:
raise
elif not length:
# Only empty OUT transfers can be ACKed.
os.read(self._fd, 0)
def StartEndpoint(self, endpoint_desc):
"""Activate an endpoint.
To enable a hardware endpoint the appropriate endpoint file must be opened
and the endpoint descriptors written to it. Linux requires both full- and
high-speed descriptors to be written for a high-speed device but since the
endpoint is always reinitialized after disconnect only the high-speed
endpoint will be valid in this case.
Args:
endpoint_desc: Endpoint descriptor.
Raises:
RuntimeError: If the hardware endpoint is in use or the configuration
is not supported by the hardware.
"""
endpoint_addr = endpoint_desc.bEndpointAddress
name, hw_ep_type, hw_ep_size = self._hw_eps[endpoint_addr]
if name in self._ep_fds:
raise RuntimeError('Hardware endpoint {} already in use.'.format(name))
ep_type = USB_TRANSFER_TYPE_TO_MASK[
endpoint_desc.bmAttributes & usb_constants.TransferType.MASK]
ep_size = endpoint_desc.wMaxPacketSize
if not hw_ep_type & ep_type:
raise RuntimeError('Hardware endpoint {} does not support this transfer '
'type.'.format(name))
elif hw_ep_size < ep_size:
raise RuntimeError('Hardware endpoint {} only supports a maximum packet '
'size of {}, {} requested.'
.format(name, hw_ep_size, ep_size))
fd = os.open(os.path.join(self._ep_dir, name), os.O_RDWR)
buf = struct.pack('=I', 1)
if self._gadget.GetSpeed() == usb_constants.Speed.HIGH:
# The full speed endpoint descriptor will not be used but Linux requires
# one to be provided.
full_speed_endpoint = usb_descriptors.EndpointDescriptor(
bEndpointAddress=endpoint_desc.bEndpointAddress,
bmAttributes=0,
wMaxPacketSize=0,
bInterval=0)
buf = ''.join([buf, full_speed_endpoint.Encode(), endpoint_desc.Encode()])
else:
buf = ''.join([buf, endpoint_desc.Encode()])
os.write(fd, buf)
pipe_r, pipe_w = multiprocessing.Pipe(False)
child = None
# gadgetfs doesn't support polling on the endpoint file descriptors (why?)
# so we have to start background threads for each.
if endpoint_addr & usb_constants.Dir.IN:
def WriterProcess():
while True:
data = pipe_r.recv()
written = os.write(fd, data)
print('IN bEndpointAddress=0x{:02X} length={}'
.format(endpoint_addr, written))
child = multiprocessing.Process(target=WriterProcess)
self._ep_fds[endpoint_addr] = fd, child, pipe_w
else:
def ReceivePacket(unused_fd, unused_events):
data = pipe_r.recv()
print('OUT bEndpointAddress=0x{:02X} length={}'
.format(endpoint_addr, len(data)))
self._gadget.ReceivePacket(endpoint_addr, data)
def ReaderProcess():
while True:
data = os.read(fd, ep_size)
pipe_w.send(data)
child = multiprocessing.Process(target=ReaderProcess)
pipe_fd = pipe_r.fileno()
self._io_loop.add_handler(pipe_fd, ReceivePacket, self._io_loop.READ)
self._ep_fds[endpoint_addr] = fd, child, pipe_r
child.start()
print 'Started endpoint 0x{:02X}.'.format(endpoint_addr)
def StopEndpoint(self, endpoint_addr):
"""Deactivate the given endpoint."""
fd, child, pipe = self._ep_fds.pop(endpoint_addr)
pipe_fd = pipe.fileno()
child.terminate()
child.join()
if not endpoint_addr & usb_constants.Dir.IN:
self._io_loop.remove_handler(pipe_fd)
os.close(fd)
print 'Stopped endpoint 0x{:02X}.'.format(endpoint_addr)
def SendPacket(self, endpoint_addr, data):
"""Send a packet on the given endpoint."""
_, _, pipe = self._ep_fds[endpoint_addr]
pipe.send(data)
def HaltEndpoint(self, endpoint_addr):
"""Signal a stall condition on the given endpoint."""
fd, _ = self._ep_fds[endpoint_addr]
# Reverse I/O direction sets the halt condition on the pipe.
try:
if endpoint_addr & usb_constants.Dir.IN:
os.read(fd, 0)
else:
os.write(fd, '')
except OSError, e:
# gadgetfs always returns EBADMSG which we should ignore.
if e.errno != errno.EBADMSG:
raise
|