Home | History | Annotate | Download | only in usb_gadget
      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