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. 4 5 """Linux gadgetfs glue. 6 7 Exposes a USB gadget using a USB peripheral controller on Linux. The userspace 8 ABI is documented here: 9 10 https://github.com/torvalds/linux/blob/master/drivers/usb/gadget/inode.c 11 """ 12 13 import errno 14 import multiprocessing 15 import os 16 import struct 17 18 from tornado import ioloop 19 20 import usb_constants 21 import usb_descriptors 22 23 GADGETFS_NOP = 0 24 GADGETFS_CONNECT = 1 25 GADGETFS_DISCONNECT = 2 26 GADGETFS_SETUP = 3 27 GADGETFS_SUSPEND = 4 28 29 BULK = 0x01 30 INTERRUPT = 0x02 31 ISOCHRONOUS = 0x04 32 33 USB_TRANSFER_TYPE_TO_MASK = { 34 usb_constants.TransferType.BULK: BULK, 35 usb_constants.TransferType.INTERRUPT: INTERRUPT, 36 usb_constants.TransferType.ISOCHRONOUS: ISOCHRONOUS 37 } 38 39 IN = 0x01 40 OUT = 0x02 41 42 HARDWARE = { 43 'beaglebone-black': ( 44 'musb-hdrc', # Gadget controller name, 45 { 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), 76 } 77 ) 78 } 79 80 81 class LinuxGadgetfs(object): 82 """Linux gadgetfs-based gadget driver. 83 """ 84 85 def __init__(self, hardware, mountpoint='/dev/gadget'): 86 """Initialize bindings to the Linux gadgetfs interface. 87 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() 99 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) 110 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 118 119 def IsConfigured(self): 120 return self._gadget is not None 121 122 def HandleEvent(self, unused_fd, unused_events): 123 buf = os.read(self._fd, 12) 124 event_type, = struct.unpack_from('=I', buf, 8) 125 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 141 142 def Connected(self, speed): 143 print 'CONNECT speed={}'.format(speed) 144 self._gadget.Connected(self, speed) 145 146 def Disconnected(self): 147 print 'DISCONNECT' 148 for endpoint_addr in self._ep_fds.keys(): 149 self.StopEndpoint(endpoint_addr) 150 self._ep_fds.clear() 151 self._gadget.Disconnected() 152 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)) 157 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) 188 189 def StartEndpoint(self, endpoint_desc): 190 """Activate an endpoint. 191 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. 197 198 Args: 199 endpoint_desc: Endpoint descriptor. 200 201 Raises: 202 RuntimeError: If the hardware endpoint is in use or the configuration 203 is not supported by the hardware. 204 """ 205 endpoint_addr = endpoint_desc.bEndpointAddress 206 name, hw_ep_type, hw_ep_size = self._hw_eps[endpoint_addr] 207 208 if name in self._ep_fds: 209 raise RuntimeError('Hardware endpoint {} already in use.'.format(name)) 210 211 ep_type = USB_TRANSFER_TYPE_TO_MASK[ 212 endpoint_desc.bmAttributes & usb_constants.TransferType.MASK] 213 ep_size = endpoint_desc.wMaxPacketSize 214 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)) 222 223 fd = os.open(os.path.join(self._ep_dir, name), os.O_RDWR) 224 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) 238 239 pipe_r, pipe_w = multiprocessing.Pipe(False) 240 child = None 241 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)) 251 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) 260 261 def ReaderProcess(): 262 while True: 263 data = os.read(fd, ep_size) 264 pipe_w.send(data) 265 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 270 271 child.start() 272 print 'Started endpoint 0x{:02X}.'.format(endpoint_addr) 273 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) 284 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) 289 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 303