diff options
author | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-24 13:43:37 +0000 |
---|---|---|
committer | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-24 13:43:37 +0000 |
commit | 9d7c6d0400299c05c310efbab3f6909c69bf2a88 (patch) | |
tree | 1ab2871298098dbdc04f783b487900814d257667 /tools/usb_gadget | |
parent | a5e245a08230e705b0342e6eb5db6769e68ae491 (diff) | |
download | chromium_src-9d7c6d0400299c05c310efbab3f6909c69bf2a88.zip chromium_src-9d7c6d0400299c05c310efbab3f6909c69bf2a88.tar.gz chromium_src-9d7c6d0400299c05c310efbab3f6909c69bf2a88.tar.bz2 |
This module provides a class to bind an instance of gadget.Gadget to the
Linux gadgetfs user-space interface for implementing USB gadget devices.
Some knowledge of the USB peripheral controller is required. Capabilities
of the Beaglebone Black's MUSB OTG controller are provided in this patch.
The Tornado library's I/O loop module is used by this class. Tornado will
be used as the HTTP framework in the next patch in this series.
BUG=396682
R=rockot@chromium.org,rpaquay@chromium.org,kalman@chromium.org
Review URL: https://codereview.chromium.org/413853003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285233 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/usb_gadget')
-rw-r--r-- | tools/usb_gadget/linux_gadgetfs.py | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/tools/usb_gadget/linux_gadgetfs.py b/tools/usb_gadget/linux_gadgetfs.py new file mode 100644 index 0000000..cc0c97c --- /dev/null +++ b/tools/usb_gadget/linux_gadgetfs.py @@ -0,0 +1,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: + 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 |