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