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
14 import multiprocessing
18 from tornado
import ioloop
21 import usb_descriptors
25 GADGETFS_DISCONNECT
= 2
33 USB_TRANSFER_TYPE_TO_MASK
= {
34 usb_constants
.TransferType
.BULK
: BULK
,
35 usb_constants
.TransferType
.INTERRUPT
: INTERRUPT
,
36 usb_constants
.TransferType
.ISOCHRONOUS
: ISOCHRONOUS
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.
85 def __init__(self
, hardware
, mountpoint
='/dev/gadget'):
86 """Initialize bindings to the Linux gadgetfs interface.
89 hardware: Hardware type.
90 mountpoint: Gadget filesystem mount point.
92 self
._chip
, self
._hw
_eps
= HARDWARE
[hardware
]
93 self
._ep
_dir
= mountpoint
96 # map from bEndpointAddress to hardware ep name and open file descriptor
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
)
112 """Unbind the gadget from the USB peripheral controller."""
114 self
._io
_loop
.remove_handler(self
._fd
)
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
:
128 elif event_type
== GADGETFS_CONNECT
:
129 speed
, = struct
.unpack('=Ixxxxxxxx', buf
)
130 self
.Connected(speed
)
131 elif event_type
== GADGETFS_DISCONNECT
:
133 elif event_type
== GADGETFS_SETUP
:
134 request_type
, request
, value
, index
, length
= struct
.unpack(
136 self
.HandleSetup(request_type
, request
, value
, index
, length
)
137 elif event_type
== GADGETFS_SUSPEND
:
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
):
148 for endpoint_addr
in self
._ep
_fds
.keys():
149 self
.StopEndpoint(endpoint_addr
)
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
)
164 os
.read(self
._fd
, 0) # Backwards I/O stalls the pipe.
166 # gadgetfs always returns EL2HLT which we should ignore.
167 if e
.errno
!= errno
.EL2HLT
:
170 os
.write(self
._fd
, data
)
174 data
= os
.read(self
._fd
, length
)
175 result
= self
._gadget
.ControlWrite(
176 request_type
, request
, value
, index
, data
)
180 os
.write(self
._fd
, '') # Backwards I/O stalls the pipe.
182 # gadgetfs always returns EL2HLT which we should ignore.
183 if e
.errno
!= errno
.EL2HLT
:
186 # Only empty OUT transfers can be ACKed.
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.
199 endpoint_desc: Endpoint descriptor.
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
,
234 buf
= ''.join([buf
, full_speed_endpoint
.Encode(), endpoint_desc
.Encode()])
236 buf
= ''.join([buf
, endpoint_desc
.Encode()])
239 pipe_r
, pipe_w
= multiprocessing
.Pipe(False)
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
:
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
255 def ReceivePacket(unused_fd
, unused_events
):
257 print('OUT bEndpointAddress=0x{:02X} length={}'
258 .format(endpoint_addr
, len(data
)))
259 self
._gadget
.ReceivePacket(endpoint_addr
, data
)
263 data
= os
.read(fd
, ep_size
)
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
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()
280 if not endpoint_addr
& usb_constants
.Dir
.IN
:
281 self
._io
_loop
.remove_handler(pipe_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
]
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.
295 if endpoint_addr
& usb_constants
.Dir
.IN
:
300 # gadgetfs always returns EBADMSG which we should ignore.
301 if e
.errno
!= errno
.EBADMSG
: