summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/config/config_usb.c3
-rw-r--r--src/config/usb.h1
-rw-r--r--src/drivers/usb/usbio.c1722
-rw-r--r--src/drivers/usb/usbio.h153
-rw-r--r--src/include/ipxe/errfile.h1
-rw-r--r--src/include/ipxe/usb.h5
6 files changed, 1884 insertions, 1 deletions
diff --git a/src/config/config_usb.c b/src/config/config_usb.c
index dc0e6e6a..4e5843b9 100644
--- a/src/config/config_usb.c
+++ b/src/config/config_usb.c
@@ -43,6 +43,9 @@ REQUIRE_OBJECT ( ehci );
#ifdef USB_HCD_UHCI
REQUIRE_OBJECT ( uhci );
#endif
+#ifdef USB_HCD_USBIO
+REQUIRE_OBJECT ( usbio );
+#endif
/*
* Drag in USB peripherals
diff --git a/src/config/usb.h b/src/config/usb.h
index 52e82eaa..6d80e7f6 100644
--- a/src/config/usb.h
+++ b/src/config/usb.h
@@ -18,6 +18,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
//#undef USB_HCD_XHCI /* xHCI USB host controller */
//#undef USB_HCD_EHCI /* EHCI USB host controller */
//#undef USB_HCD_UHCI /* UHCI USB host controller */
+//#define USB_HCD_USBIO /* Very slow EFI USB host controller */
/*
* USB peripherals
diff --git a/src/drivers/usb/usbio.c b/src/drivers/usb/usbio.c
new file mode 100644
index 00000000..70aa5095
--- /dev/null
+++ b/src/drivers/usb/usbio.c
@@ -0,0 +1,1722 @@
+/*
+ * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/Protocol/UsbIo.h>
+#include <ipxe/usb.h>
+#include "usbio.h"
+
+/** @file
+ *
+ * EFI_USB_IO_PROTOCOL pseudo Host Controller Interface driver
+ *
+ *
+ * The EFI_USB_IO_PROTOCOL is an almost unbelievably poorly designed
+ * abstraction of a USB device. It would be just about forgivable for
+ * an API to support only synchronous operation for bulk OUT
+ * endpoints. It is imbecilic to support only synchronous operation
+ * for bulk IN endpoints. This apparently intern-designed API
+ * throttles a typical NIC down to 1.5% of its maximum throughput.
+ * That isn't a typo. It really is that slow.
+ *
+ * We can't even work around this stupidity by talking to the host
+ * controller abstraction directly, because an identical limitation
+ * exists in the EFI_USB2_HC_PROTOCOL.
+ *
+ * Unless you derive therapeutic value from watching download progress
+ * indicators lethargically creep through every single integer from 0
+ * to 100, you should use iPXE's native USB host controller drivers
+ * instead. (Or just upgrade from UEFI to "legacy" BIOS, which will
+ * produce a similar speed increase.)
+ *
+ *
+ * For added excitement, the EFI_USB_IO_PROTOCOL makes the
+ * (demonstrably incorrect) assumption that a USB driver needs to
+ * attach to exactly one interface within a USB device, and provides a
+ * helper method to retrieve "the" interface descriptor. Since pretty
+ * much every USB network device requires binding to a pair of
+ * control+data interfaces, this aspect of EFI_USB_IO_PROTOCOL is of
+ * no use to us.
+ *
+ * We have our own existing code for reading USB descriptors, so we
+ * don't actually care that the UsbGetInterfaceDescriptor() method
+ * provided by EFI_USB_IO_PROTOCOL is useless for network devices. We
+ * can read the descriptors ourselves (via UsbControlTransfer()) and
+ * get all of the information we need this way. We can even work
+ * around the fact that EFI_USB_IO_PROTOCOL provides separate handles
+ * for each of the two interfaces comprising our network device.
+ *
+ * However, if we discover that we need to select an alternative
+ * device configuration (e.g. for devices exposing both RNDIS and
+ * ECM), then all hell breaks loose. EFI_USB_IO_PROTOCOL starts to
+ * panic because its cached interface and endpoint descriptors will no
+ * longer be valid. As mentioned above, the cached descriptors are
+ * useless for network devices anyway so we _really_ don't care about
+ * this, but EFI_USB_IO_PROTOCOL certainly cares. It prints out a
+ * manic warning message containing no fewer than six exclamation
+ * marks and then literally commits seppuku in the middle of the
+ * UsbControlTransfer() method by attempting to uninstall itself.
+ * Quite how the caller is supposed to react when asked to stop using
+ * the EFI_USB_IO_PROTOCOL instance while in the middle of an
+ * uninterruptible call to said instance is left as an exercise for
+ * the interested reader.
+ *
+ * There is no sensible way to work around this, so we just
+ * preemptively fail if asked to change the device configuration, on
+ * the basis that reporting a sarcastic error message is often
+ * preferable to jumping through a NULL pointer and crashing the
+ * system.
+ */
+
+/* Disambiguate the various error causes */
+#define ENOTSUP_MORONIC_SPECIFICATION \
+ __einfo_error ( EINFO_ENOTSUP_MORONIC_SPECIFICATION )
+#define EINFO_ENOTSUP_MORONIC_SPECIFICATION \
+ __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \
+ "EFI_USB_IO_PROTOCOL was designed by morons" )
+
+/******************************************************************************
+ *
+ * Device model
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Determine endpoint interface number
+ *
+ * @v usbio USB I/O device
+ * @v ep USB Endpoint
+ * @ret interface Interface number, or negative error
+ */
+static int usbio_interface ( struct usbio_device *usbio,
+ struct usb_endpoint *ep ) {
+ EFI_HANDLE handle = usbio->handle;
+ struct usb_device *usb = ep->usb;
+ struct usb_configuration_descriptor *config;
+ struct usb_interface_descriptor *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_function *func;
+ unsigned int i;
+
+ /* The control endpoint is not part of a described interface */
+ if ( ep->address == USB_EP0_ADDRESS )
+ return 0;
+
+ /* Iterate over all interface descriptors looking for a match */
+ config = usbio->config;
+ for_each_config_descriptor ( interface, config ) {
+
+ /* Skip non-interface descriptors */
+ if ( interface->header.type != USB_INTERFACE_DESCRIPTOR )
+ continue;
+
+ /* Iterate over all endpoint descriptors looking for a match */
+ for_each_interface_descriptor ( endpoint, config, interface ) {
+
+ /* Skip non-endpoint descriptors */
+ if ( endpoint->header.type != USB_ENDPOINT_DESCRIPTOR )
+ continue;
+
+ /* Check endpoint address */
+ if ( endpoint->endpoint != ep->address )
+ continue;
+
+ /* Check interface belongs to this function */
+ list_for_each_entry ( func, &usb->functions, list ) {
+
+ /* Skip non-matching functions */
+ if ( func->interface[0] != usbio->first )
+ continue;
+
+ /* Iterate over all interfaces for a match */
+ for ( i = 0 ; i < func->count ; i++ ) {
+ if ( interface->interface ==
+ func->interface[i] )
+ return interface->interface;
+ }
+ }
+ }
+ }
+
+ DBGC ( usbio, "USBIO %s cannot find interface for %s",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ) );
+ return -ENOENT;
+}
+
+/**
+ * Open USB I/O interface
+ *
+ * @v usbio USB I/O device
+ * @v interface Interface number
+ * @ret rc Return status code
+ */
+static int usbio_open ( struct usbio_device *usbio, unsigned int interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE handle = usbio->handle;
+ struct usbio_interface *intf = &usbio->interface[interface];
+ EFI_DEVICE_PATH_PROTOCOL *path;
+ EFI_DEVICE_PATH_PROTOCOL *end;
+ USB_DEVICE_PATH *usbpath;
+ union {
+ void *interface;
+ EFI_USB_IO_PROTOCOL *io;
+ } u;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Sanity check */
+ assert ( interface < usbio->config->interfaces );
+
+ /* If interface is already open, just increment the usage count */
+ if ( intf->count ) {
+ intf->count++;
+ return 0;
+ }
+
+ /* Construct device path for this interface */
+ path = usbio->path;
+ usbpath = usbio->usbpath;
+ usbpath->InterfaceNumber = interface;
+ end = efi_devpath_end ( path );
+
+ /* Locate handle for this endpoint's interface */
+ if ( ( efirc = bs->LocateDevicePath ( &efi_usb_io_protocol_guid, &path,
+ &intf->handle ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s could not locate ",
+ efi_handle_name ( handle ) );
+ DBGC ( usbio, "%s: %s\n",
+ efi_devpath_text ( usbio->path ), strerror ( rc ) );
+ return rc;
+ }
+
+ /* Check that expected path was located */
+ if ( path != end ) {
+ DBGC ( usbio, "USBIO %s located incomplete ",
+ efi_handle_name ( handle ) );
+ DBGC ( usbio, "%s\n", efi_handle_name ( intf->handle ) );
+ return -EXDEV;
+ }
+
+ /* Open USB I/O protocol on this handle */
+ if ( ( efirc = bs->OpenProtocol ( intf->handle,
+ &efi_usb_io_protocol_guid,
+ &u.interface, efi_image_handle,
+ intf->handle,
+ ( EFI_OPEN_PROTOCOL_BY_DRIVER |
+ EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s cannot open ",
+ efi_handle_name ( handle ) );
+ DBGC ( usbio, "%s: %s\n",
+ efi_handle_name ( intf->handle ), strerror ( rc ) );
+ DBGC_EFI_OPENERS ( usbio, intf->handle,
+ &efi_usb_io_protocol_guid );
+ return rc;
+ }
+ intf->io = u.io;
+
+ /* Increment usage count */
+ intf->count++;
+
+ return 0;
+}
+
+/**
+ * Close USB I/O interface
+ *
+ * @v usbio USB I/O device
+ * @v interface Interface number
+ */
+static void usbio_close ( struct usbio_device *usbio, unsigned int interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct usbio_interface *intf = &usbio->interface[interface];
+
+ /* Sanity checks */
+ assert ( interface < usbio->config->interfaces );
+ assert ( intf->count > 0 );
+
+ /* Decrement usage count */
+ intf->count--;
+
+ /* Do nothing if interface is still in use */
+ if ( intf->count )
+ return;
+
+ /* Close USB I/O protocol */
+ bs->CloseProtocol ( intf->handle, &efi_usb_io_protocol_guid,
+ efi_image_handle, intf->handle );
+}
+
+/******************************************************************************
+ *
+ * Control endpoints
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open control endpoint
+ *
+ * @v endpoint Endpoint
+ * @ret rc Return status code
+ */
+static int usbio_control_open ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close control endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_control_close ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Poll control endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_control_poll ( struct usbio_endpoint *endpoint ) {
+ struct usbio_device *usbio = endpoint->usbio;
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_HANDLE handle = usbio->handle;
+ EFI_USB_IO_PROTOCOL *io;
+ union {
+ struct usb_setup_packet setup;
+ EFI_USB_DEVICE_REQUEST efi;
+ } *msg;
+ EFI_USB_DATA_DIRECTION direction;
+ struct io_buffer *iobuf;
+ unsigned int index;
+ unsigned int flags;
+ unsigned int recipient;
+ unsigned int interface;
+ uint16_t request;
+ void *data;
+ size_t len;
+ UINT32 status;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing if ring is empty */
+ if ( endpoint->cons == endpoint->prod )
+ return;
+
+ /* Consume next transfer */
+ index = ( endpoint->cons++ % USBIO_RING_COUNT );
+ iobuf = endpoint->iobuf[index];
+ flags = endpoint->flags[index];
+
+ /* Sanity check */
+ if ( ! ( flags & USBIO_MESSAGE ) ) {
+ DBGC ( usbio, "USBIO %s %s non-message transfer\n",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ) );
+ rc = -ENOTSUP;
+ goto err_not_message;
+ }
+
+ /* Construct transfer */
+ assert ( iob_len ( iobuf ) >= sizeof ( *msg ) );
+ msg = iobuf->data;
+ iob_pull ( iobuf, sizeof ( *msg ) );
+ request = le16_to_cpu ( msg->setup.request );
+ len = iob_len ( iobuf );
+ if ( len ) {
+ data = iobuf->data;
+ direction = ( ( request & USB_DIR_IN ) ?
+ EfiUsbDataIn : EfiUsbDataOut );
+ } else {
+ data = NULL;
+ direction = EfiUsbNoData;
+ }
+
+ /* Determine interface for this transfer */
+ recipient = ( request & USB_RECIP_MASK );
+ if ( recipient == USB_RECIP_INTERFACE ) {
+ /* Recipient is an interface: use interface number directly */
+ interface = le16_to_cpu ( msg->setup.index );
+ } else {
+ /* Route all other requests through the first interface */
+ interface = 0;
+ }
+
+ /* Open interface */
+ if ( ( rc = usbio_open ( usbio, interface ) ) != 0 )
+ goto err_open;
+ io = usbio->interface[interface].io;
+
+ /* Due to the design of EFI_USB_IO_PROTOCOL, attempting to set
+ * the configuration to a non-default value is basically a
+ * self-destruct button.
+ */
+ if ( ( request == USB_SET_CONFIGURATION ) &&
+ ( le16_to_cpu ( msg->setup.value ) != usbio->config->config ) ) {
+ rc = -ENOTSUP_MORONIC_SPECIFICATION;
+ DBGC ( usbio, "USBIO %s cannot change configuration: %s\n",
+ efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_moronic_specification;
+ }
+
+ /* Submit transfer */
+ if ( ( efirc = io->UsbControlTransfer ( io, &msg->efi, direction, 0,
+ data, len, &status ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s %s could not submit control transfer ",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ) );
+ DBGC ( usbio, "via %s: %s (status %04x)\n",
+ efi_handle_name ( usbio->interface[interface].handle ),
+ strerror ( rc ), status );
+ goto err_transfer;
+ }
+
+ /* Close interface */
+ usbio_close ( usbio, interface );
+
+ /* Complete transfer */
+ usb_complete ( ep, iobuf );
+
+ return;
+
+ err_transfer:
+ err_moronic_specification:
+ usbio_close ( usbio, interface );
+ err_open:
+ err_not_message:
+ usb_complete_err ( ep, iobuf, rc );
+}
+
+/** Control endpoint operations */
+static struct usbio_operations usbio_control_operations = {
+ .open = usbio_control_open,
+ .close = usbio_control_close,
+ .poll = usbio_control_poll,
+};
+
+/******************************************************************************
+ *
+ * Bulk IN endpoints
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open bulk IN endpoint
+ *
+ * @v endpoint Endpoint
+ * @ret rc Return status code
+ */
+static int usbio_bulk_in_open ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close bulk IN endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_bulk_in_close ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Poll bulk IN endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_bulk_in_poll ( struct usbio_endpoint *endpoint ) {
+ struct usbio_device *usbio = endpoint->usbio;
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_USB_IO_PROTOCOL *io = endpoint->io;
+ EFI_HANDLE handle = usbio->handle;
+ struct io_buffer *iobuf;
+ unsigned int index;
+ UINTN len;
+ UINT32 status;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing if ring is empty */
+ if ( endpoint->cons == endpoint->prod )
+ return;
+
+ /* Attempt (but do not yet consume) next transfer */
+ index = ( endpoint->cons % USBIO_RING_COUNT );
+ iobuf = endpoint->iobuf[index];
+
+ /* Construct transfer */
+ len = iob_len ( iobuf );
+
+ /* Upon being turned on, the EFI_USB_IO_PROTOCOL did nothing
+ * for several minutes before firing a small ARP packet a few
+ * millimetres into the ether.
+ */
+ efirc = io->UsbBulkTransfer ( io, ep->address, iobuf->data,
+ &len, 1, &status );
+ if ( efirc == EFI_TIMEOUT )
+ return;
+
+ /* Consume transfer */
+ endpoint->cons++;
+
+ /* Check for failure */
+ if ( efirc != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC2 ( usbio, "USBIO %s %s could not submit bulk IN transfer: "
+ "%s (status %04x)\n", efi_handle_name ( handle ),
+ usb_endpoint_name ( ep ), strerror ( rc ), status );
+ usb_complete_err ( ep, iobuf, rc );
+ return;
+ }
+
+ /* Update length */
+ iob_put ( iobuf, ( len - iob_len ( iobuf ) ) );
+
+ /* Complete transfer */
+ usb_complete ( ep, iobuf );
+}
+
+/** Bulk endpoint operations */
+static struct usbio_operations usbio_bulk_in_operations = {
+ .open = usbio_bulk_in_open,
+ .close = usbio_bulk_in_close,
+ .poll = usbio_bulk_in_poll,
+};
+
+/******************************************************************************
+ *
+ * Bulk OUT endpoints
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open bulk OUT endpoint
+ *
+ * @v endpoint Endpoint
+ * @ret rc Return status code
+ */
+static int usbio_bulk_out_open ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close bulk OUT endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_bulk_out_close ( struct usbio_endpoint *endpoint __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Poll bulk OUT endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_bulk_out_poll ( struct usbio_endpoint *endpoint ) {
+ struct usbio_device *usbio = endpoint->usbio;
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_USB_IO_PROTOCOL *io = endpoint->io;
+ EFI_HANDLE handle = usbio->handle;
+ struct io_buffer *iobuf;
+ unsigned int index;
+ unsigned int flags;
+ UINTN len;
+ UINT32 status;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing if ring is empty */
+ if ( endpoint->cons == endpoint->prod )
+ return;
+
+ /* Consume next transfer */
+ index = ( endpoint->cons++ % USBIO_RING_COUNT );
+ iobuf = endpoint->iobuf[index];
+ flags = endpoint->flags[index];
+
+ /* Construct transfer */
+ len = iob_len ( iobuf );
+
+ /* Submit transfer */
+ if ( ( efirc = io->UsbBulkTransfer ( io, ep->address, iobuf->data,
+ &len, 0, &status ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s %s could not submit bulk OUT transfer: "
+ "%s (status %04x)\n", efi_handle_name ( handle ),
+ usb_endpoint_name ( ep ), strerror ( rc ), status );
+ goto err;
+ }
+
+ /* Update length */
+ iob_put ( iobuf, ( len - iob_len ( iobuf ) ) );
+
+ /* Submit zero-length transfer if required */
+ len = 0;
+ if ( ( flags & USBIO_ZLEN ) &&
+ ( efirc = io->UsbBulkTransfer ( io, ep->address, NULL, &len, 0,
+ &status ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s %s could not submit zero-length "
+ "transfer: %s (status %04x)\n",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ),
+ strerror ( rc ), status );
+ goto err;
+ }
+
+ /* Complete transfer */
+ usb_complete ( ep, iobuf );
+
+ return;
+
+ err:
+ usb_complete_err ( ep, iobuf, rc );
+}
+
+/** Bulk endpoint operations */
+static struct usbio_operations usbio_bulk_out_operations = {
+ .open = usbio_bulk_out_open,
+ .close = usbio_bulk_out_close,
+ .poll = usbio_bulk_out_poll,
+};
+
+/******************************************************************************
+ *
+ * Interrupt endpoints
+ *
+ ******************************************************************************
+ *
+ * The EFI_USB_IO_PROTOCOL provides two ways to interact with
+ * interrupt endpoints, neither of which naturally model the hardware
+ * interaction. The UsbSyncInterruptTransfer() method allows imposes
+ * a 1ms overhead for every interrupt transfer (which could result in
+ * up to a 50% decrease in overall throughput for the device). The
+ * UsbAsyncInterruptTransfer() method provides no way for us to
+ * prevent transfers when no I/O buffers are available.
+ *
+ * We work around this design by utilising a small, fixed ring buffer
+ * into which the interrupt callback delivers the data. This aims to
+ * provide buffer space even if no I/O buffers have yet been enqueued.
+ * The scheme is not guaranteed since the fixed ring buffer may also
+ * become full. However:
+ *
+ * - devices which send a constant stream of interrupts (and which
+ * therefore might exhaust the fixed ring buffer) tend to be
+ * responding to every interrupt request, and can tolerate lost
+ * packets, and
+ *
+ * - devices which cannot tolerate lost interrupt packets tend to send
+ * only a few small messages.
+ *
+ * The scheme should therefore work in practice.
+ */
+
+/**
+ * Interrupt endpoint callback
+ *
+ * @v data Received data
+ * @v len Length of received data
+ * @v context Callback context
+ * @v status Transfer status
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI usbio_interrupt_callback ( VOID *data, UINTN len,
+ VOID *context,
+ UINT32 status ) {
+ struct usbio_interrupt_ring *intr = context;
+ struct usbio_endpoint *endpoint = intr->endpoint;
+ struct usbio_device *usbio = endpoint->usbio;
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_HANDLE handle = usbio->handle;
+ unsigned int fill;
+ unsigned int index;
+
+ /* Sanity check */
+ assert ( len <= ep->mtu );
+
+ /* Do nothing if ring is empty */
+ fill = ( intr->prod - intr->cons );
+ if ( fill >= USBIO_INTR_COUNT ) {
+ DBGC ( usbio, "USBIO %s %s dropped interrupt completion\n",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ) );
+ return 0;
+ }
+
+ /* Do nothing if transfer was unsuccessful */
+ if ( status != 0 ) {
+ DBGC ( usbio, "USBIO %s %s interrupt completion status %04x\n",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ),
+ status );
+ return 0; /* Unclear what failure actually means here */
+ }
+
+ /* Copy data to buffer and increment producer counter */
+ index = ( intr->prod % USBIO_INTR_COUNT );
+ memcpy ( intr->data[index], data, len );
+ intr->len[index] = len;
+ intr->prod++;
+
+ return 0;
+}
+
+/**
+ * Open interrupt endpoint
+ *
+ * @v endpoint Endpoint
+ * @ret rc Return status code
+ */
+static int usbio_interrupt_open ( struct usbio_endpoint *endpoint ) {
+ struct usbio_device *usbio = endpoint->usbio;
+ struct usbio_interrupt_ring *intr;
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_USB_IO_PROTOCOL *io = endpoint->io;
+ EFI_HANDLE handle = usbio->handle;
+ unsigned int interval;
+ unsigned int i;
+ void *data;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Allocate interrupt ring buffer */
+ intr = zalloc ( sizeof ( *intr ) + ( USBIO_INTR_COUNT * ep->mtu ) );
+ if ( ! intr ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ endpoint->intr = intr;
+ intr->endpoint = endpoint;
+ data = ( ( ( void * ) intr ) + sizeof ( *intr ) );
+ for ( i = 0 ; i < USBIO_INTR_COUNT ; i++ ) {
+ intr->data[i] = data;
+ data += ep->mtu;
+ }
+
+ /* Determine polling interval */
+ interval = ( ep->interval >> 3 /* microframes -> milliseconds */ );
+ if ( ! interval )
+ interval = 1; /* May not be zero */
+
+ /* Add to periodic schedule */
+ if ( ( efirc = io->UsbAsyncInterruptTransfer ( io, ep->address, TRUE,
+ interval, ep->mtu,
+ usbio_interrupt_callback,
+ intr ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s %s could not schedule interrupt "
+ "transfer: %s\n", efi_handle_name ( handle ),
+ usb_endpoint_name ( ep ), strerror ( rc ) );
+ goto err_schedule;
+ }
+
+ return 0;
+
+ io->UsbAsyncInterruptTransfer ( io, ep->address, FALSE, 0, 0,
+ NULL, NULL );
+ err_schedule:
+ free ( intr );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Close interrupt endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_interrupt_close ( struct usbio_endpoint *endpoint ) {
+ struct usb_endpoint *ep = endpoint->ep;
+ EFI_USB_IO_PROTOCOL *io = endpoint->io;
+
+ /* Remove from periodic schedule */
+ io->UsbAsyncInterruptTransfer ( io, ep->address, FALSE, 0, 0,
+ NULL, NULL );
+
+ /* Free interrupt ring buffer */
+ free ( endpoint->intr );
+}
+
+/**
+ * Poll interrupt endpoint
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_interrupt_poll ( struct usbio_endpoint *endpoint ) {
+ struct usbio_interrupt_ring *intr = endpoint->intr;
+ struct usb_endpoint *ep = endpoint->ep;
+ struct io_buffer *iobuf;
+ unsigned int index;
+ unsigned int intr_index;
+ size_t len;
+
+ /* Do nothing if ring is empty */
+ if ( endpoint->cons == endpoint->prod )
+ return;
+
+ /* Do nothing if interrupt ring is empty */
+ if ( intr->cons == intr->prod )
+ return;
+
+ /* Consume next transfer */
+ index = ( endpoint->cons++ % USBIO_RING_COUNT );
+ iobuf = endpoint->iobuf[index];
+
+ /* Populate I/O buffer */
+ intr_index = ( intr->cons++ % USBIO_INTR_COUNT );
+ len = intr->len[intr_index];
+ assert ( len <= iob_len ( iobuf ) );
+ iob_put ( iobuf, ( len - iob_len ( iobuf ) ) );
+ memcpy ( iobuf->data, intr->data[intr_index], len );
+
+ /* Complete transfer */
+ usb_complete ( ep, iobuf );
+}
+
+/** Interrupt endpoint operations */
+static struct usbio_operations usbio_interrupt_operations = {
+ .open = usbio_interrupt_open,
+ .close = usbio_interrupt_close,
+ .poll = usbio_interrupt_poll,
+};
+
+/******************************************************************************
+ *
+ * Endpoint operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open endpoint
+ *
+ * @v ep USB endpoint
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_open ( struct usb_endpoint *ep ) {
+ struct usb_bus *bus = ep->usb->port->hub->bus;
+ struct usbio_device *usbio = usb_bus_get_hostdata ( bus );
+ struct usbio_endpoint *endpoint;
+ EFI_HANDLE handle = usbio->handle;
+ unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK );
+ int interface;
+ int rc;
+
+ /* Allocate and initialise structure */
+ endpoint = zalloc ( sizeof ( *endpoint ) );
+ if ( ! endpoint ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ usb_endpoint_set_hostdata ( ep, endpoint );
+ endpoint->usbio = usbio;
+ endpoint->ep = ep;
+
+ /* Identify endpoint operations */
+ if ( attr == USB_ENDPOINT_ATTR_CONTROL ) {
+ endpoint->op = &usbio_control_operations;
+ } else if ( attr == USB_ENDPOINT_ATTR_BULK ) {
+ endpoint->op = ( ( ep->address & USB_DIR_IN ) ?
+ &usbio_bulk_in_operations :
+ &usbio_bulk_out_operations );
+ } else if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) {
+ endpoint->op = &usbio_interrupt_operations;
+ } else {
+ rc = -ENOTSUP;
+ goto err_operations;
+ }
+
+ /* Identify interface for this endpoint */
+ interface = usbio_interface ( usbio, ep );
+ if ( interface < 0 ) {
+ rc = interface;
+ goto err_interface;
+ }
+ endpoint->interface = interface;
+
+ /* Open interface */
+ if ( ( rc = usbio_open ( usbio, interface ) ) != 0 )
+ goto err_open_interface;
+ endpoint->handle = usbio->interface[interface].handle;
+ endpoint->io = usbio->interface[interface].io;
+ DBGC ( usbio, "USBIO %s %s using ",
+ efi_handle_name ( handle ), usb_endpoint_name ( ep ) );
+ DBGC ( usbio, "%s\n", efi_handle_name ( endpoint->handle ) );
+
+ /* Open endpoint */
+ if ( ( rc = endpoint->op->open ( endpoint ) ) != 0 )
+ goto err_open_endpoint;
+
+ /* Add to list of endpoints */
+ list_add_tail ( &endpoint->list, &usbio->endpoints );
+
+ return 0;
+
+ list_del ( &endpoint->list );
+ endpoint->op->close ( endpoint );
+ err_open_endpoint:
+ usbio_close ( usbio, interface );
+ err_open_interface:
+ err_interface:
+ err_operations:
+ free ( endpoint );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Close endpoint
+ *
+ * @v ep USB endpoint
+ */
+static void usbio_endpoint_close ( struct usb_endpoint *ep ) {
+ struct usbio_endpoint *endpoint = usb_endpoint_get_hostdata ( ep );
+ struct usbio_device *usbio = endpoint->usbio;
+ struct io_buffer *iobuf;
+ unsigned int index;
+
+ /* Remove from list of endpoints */
+ list_del ( &endpoint->list );
+
+ /* Close endpoint */
+ endpoint->op->close ( endpoint );
+
+ /* Close interface */
+ usbio_close ( usbio, endpoint->interface );
+
+ /* Cancel any incomplete transfers */
+ while ( endpoint->cons != endpoint->prod ) {
+ index = ( endpoint->cons++ % USBIO_RING_COUNT );
+ iobuf = endpoint->iobuf[index];
+ usb_complete_err ( ep, iobuf, -ECANCELED );
+ }
+
+ /* Free endpoint */
+ free ( endpoint );
+}
+
+/**
+ * Reset endpoint
+ *
+ * @v ep USB endpoint
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_reset ( struct usb_endpoint *ep __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Update MTU
+ *
+ * @v ep USB endpoint
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_mtu ( struct usb_endpoint *ep __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Enqueue transfer
+ *
+ * @v ep USB endpoint
+ * @v iobuf I/O buffer
+ * @v flags Transfer flags
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_enqueue ( struct usb_endpoint *ep,
+ struct io_buffer *iobuf,
+ unsigned int flags ) {
+ struct usbio_endpoint *endpoint = usb_endpoint_get_hostdata ( ep );
+ unsigned int fill;
+ unsigned int index;
+
+ /* Fail if transfer ring is full */
+ fill = ( endpoint->prod - endpoint->cons );
+ if ( fill >= USBIO_RING_COUNT )
+ return -ENOBUFS;
+
+ /* Add to ring */
+ index = ( endpoint->prod++ % USBIO_RING_COUNT );
+ endpoint->iobuf[index] = iobuf;
+ endpoint->flags[index] = flags;
+
+ return 0;
+}
+
+/**
+ * Enqueue message transfer
+ *
+ * @v ep USB endpoint
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_message ( struct usb_endpoint *ep,
+ struct io_buffer *iobuf ) {
+
+ /* Enqueue transfer */
+ return usbio_endpoint_enqueue ( ep, iobuf, USBIO_MESSAGE );
+}
+
+/**
+ * Enqueue stream transfer
+ *
+ * @v ep USB endpoint
+ * @v iobuf I/O buffer
+ * @v terminate Terminate using a short packet
+ * @ret rc Return status code
+ */
+static int usbio_endpoint_stream ( struct usb_endpoint *ep,
+ struct io_buffer *iobuf, int terminate ) {
+ size_t len = iob_len ( iobuf );
+ int zlen;
+
+ /* Enqueue transfer */
+ zlen = ( terminate && ( ( len & ( ep->mtu - 1 ) ) == 0 ) );
+ return usbio_endpoint_enqueue ( ep, iobuf, ( zlen ? USBIO_ZLEN : 0 ) );
+}
+
+/**
+ * Poll for completions
+ *
+ * @v endpoint Endpoint
+ */
+static void usbio_endpoint_poll ( struct usbio_endpoint *endpoint ) {
+
+ /* Poll endpoint */
+ endpoint->op->poll ( endpoint );
+}
+
+/******************************************************************************
+ *
+ * Device operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open device
+ *
+ * @v usb USB device
+ * @ret rc Return status code
+ */
+static int usbio_device_open ( struct usb_device *usb ) {
+ struct usbio_device *usbio =
+ usb_bus_get_hostdata ( usb->port->hub->bus );
+
+ usb_set_hostdata ( usb, usbio );
+ return 0;
+}
+
+/**
+ * Close device
+ *
+ * @v usb USB device
+ */
+static void usbio_device_close ( struct usb_device *usb __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Assign device address
+ *
+ * @v usb USB device
+ * @ret rc Return status code
+ */
+static int usbio_device_address ( struct usb_device *usb __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/******************************************************************************
+ *
+ * Hub operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open hub
+ *
+ * @v hub USB hub
+ * @ret rc Return status code
+ */
+static int usbio_hub_open ( struct usb_hub *hub ) {
+
+ /* Disallow non-root hubs */
+ if ( hub->usb )
+ return -ENOTSUP;
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close hub
+ *
+ * @v hub USB hub
+ */
+static void usbio_hub_close ( struct usb_hub *hub __unused ) {
+
+ /* Nothing to do */
+}
+
+/******************************************************************************
+ *
+ * Root hub operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open root hub
+ *
+ * @v hub USB hub
+ * @ret rc Return status code
+ */
+static int usbio_root_open ( struct usb_hub *hub __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close root hub
+ *
+ * @v hub USB hub
+ */
+static void usbio_root_close ( struct usb_hub *hub __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Enable port
+ *
+ * @v hub USB hub
+ * @v port USB port
+ * @ret rc Return status code
+ */
+static int usbio_root_enable ( struct usb_hub *hub __unused,
+ struct usb_port *port __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Disable port
+ *
+ * @v hub USB hub
+ * @v port USB port
+ * @ret rc Return status code
+ */
+static int usbio_root_disable ( struct usb_hub *hub __unused,
+ struct usb_port *port __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Update root hub port speed
+ *
+ * @v hub USB hub
+ * @v port USB port
+ * @ret rc Return status code
+ */
+static int usbio_root_speed ( struct usb_hub *hub __unused,
+ struct usb_port *port ) {
+
+ /* Not actually exposed via EFI_USB_IO_PROTOCOL */
+ port->speed = USB_SPEED_HIGH;
+ return 0;
+}
+
+/**
+ * Clear transaction translator buffer
+ *
+ * @v hub USB hub
+ * @v port USB port
+ * @v ep USB endpoint
+ * @ret rc Return status code
+ */
+static int usbio_root_clear_tt ( struct usb_hub *hub __unused,
+ struct usb_port *port __unused,
+ struct usb_endpoint *ep __unused ) {
+
+ /* Should never be called; this is a root hub */
+ return -ENOTSUP;
+}
+
+/******************************************************************************
+ *
+ * Bus operations
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open USB bus
+ *
+ * @v bus USB bus
+ * @ret rc Return status code
+ */
+static int usbio_bus_open ( struct usb_bus *bus __unused ) {
+
+ /* Nothing to do */
+ return 0;
+}
+
+/**
+ * Close USB bus
+ *
+ * @v bus USB bus
+ */
+static void usbio_bus_close ( struct usb_bus *bus __unused ) {
+
+ /* Nothing to do */
+}
+
+/**
+ * Poll USB bus
+ *
+ * @v bus USB bus
+ */
+static void usbio_bus_poll ( struct usb_bus *bus ) {
+ struct usbio_device *usbio = usb_bus_get_hostdata ( bus );
+ struct usbio_endpoint *endpoint;
+
+ /* Poll all endpoints. We trust that completion handlers are
+ * minimal and will not do anything that could plausibly
+ * affect the endpoint list itself.
+ */
+ list_for_each_entry ( endpoint, &usbio->endpoints, list )
+ usbio_endpoint_poll ( endpoint );
+}
+
+/******************************************************************************
+ *
+ * EFI driver interface
+ *
+ ******************************************************************************
+ */
+
+/** USB I/O host controller driver operations */
+static struct usb_host_operations usbio_operations = {
+ .endpoint = {
+ .open = usbio_endpoint_open,
+ .close = usbio_endpoint_close,
+ .reset = usbio_endpoint_reset,
+ .mtu = usbio_endpoint_mtu,
+ .message = usbio_endpoint_message,
+ .stream = usbio_endpoint_stream,
+ },
+ .device = {
+ .open = usbio_device_open,
+ .close = usbio_device_close,
+ .address = usbio_device_address,
+ },
+ .bus = {
+ .open = usbio_bus_open,
+ .close = usbio_bus_close,
+ .poll = usbio_bus_poll,
+ },
+ .hub = {
+ .open = usbio_hub_open,
+ .close = usbio_hub_close,
+ },
+ .root = {
+ .open = usbio_root_open,
+ .close = usbio_root_close,
+ .enable = usbio_root_enable,
+ .disable = usbio_root_disable,
+ .speed = usbio_root_speed,
+ .clear_tt = usbio_root_clear_tt,
+ },
+};
+
+/**
+ * Check to see if driver supports a device
+ *
+ * @v handle EFI device handle
+ * @ret rc Return status code
+ */
+static int usbio_supported ( EFI_HANDLE handle ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_USB_DEVICE_DESCRIPTOR device;
+ EFI_USB_INTERFACE_DESCRIPTOR interface;
+ struct usb_class class;
+ struct usb_driver *driver;
+ struct usb_device_id *id;
+ union {
+ void *interface;
+ EFI_USB_IO_PROTOCOL *io;
+ } usb;
+ unsigned int vendor;
+ unsigned int product;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Get protocol */
+ if ( ( efirc = bs->OpenProtocol ( handle, &efi_usb_io_protocol_guid,
+ &usb.interface, efi_image_handle,
+ handle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
+ rc = -EEFI ( efirc );
+ DBGCP ( handle, "USB %s is not a USB device\n",
+ efi_handle_name ( handle ) );
+ goto err_open_protocol;
+ }
+
+ /* Get device descriptor */
+ if ( ( efirc = usb.io->UsbGetDeviceDescriptor ( usb.io,
+ &device ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( handle, "USB %s could not get device descriptor: "
+ "%s\n", efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_get_device_descriptor;
+ }
+ vendor = device.IdVendor;
+ product = device.IdProduct;
+
+ /* Get interface descriptor */
+ if ( ( efirc = usb.io->UsbGetInterfaceDescriptor ( usb.io,
+ &interface ) ) !=0){
+ rc = -EEFI ( efirc );
+ DBGC ( handle, "USB %s could not get interface descriptor: "
+ "%s\n", efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_get_interface_descriptor;
+ }
+ class.class = interface.InterfaceClass;
+ class.subclass = interface.InterfaceSubClass;
+ class.protocol = interface.InterfaceProtocol;
+
+ /* Look for a driver for this interface */
+ driver = usb_find_driver ( vendor, product, &class, &id );
+ if ( ! driver ) {
+ rc = -ENOTSUP;
+ goto err_unsupported;
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_unsupported:
+ err_get_interface_descriptor:
+ err_get_device_descriptor:
+ bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid,
+ efi_image_handle, handle );
+ err_open_protocol:
+ return rc;
+}
+
+/**
+ * Fetch configuration descriptor
+ *
+ * @v usbio USB I/O device
+ * @ret rc Return status code
+ */
+static int usbio_config ( struct usbio_device *usbio ) {
+ EFI_HANDLE handle = usbio->handle;
+ EFI_USB_IO_PROTOCOL *io = usbio->io;
+ EFI_USB_DEVICE_DESCRIPTOR device;
+ EFI_USB_CONFIG_DESCRIPTOR partial;
+ union {
+ struct usb_setup_packet setup;
+ EFI_USB_DEVICE_REQUEST efi;
+ } msg;
+ UINT32 status;
+ size_t len;
+ unsigned int count;
+ unsigned int value;
+ unsigned int i;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Get device descriptor */
+ if ( ( efirc = io->UsbGetDeviceDescriptor ( io, &device ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USB %s could not get device descriptor: "
+ "%s\n", efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_get_device_descriptor;
+ }
+ count = device.NumConfigurations;
+
+ /* Get current partial configuration descriptor */
+ if ( ( efirc = io->UsbGetConfigDescriptor ( io, &partial ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USB %s could not get partial configuration "
+ "descriptor: %s\n", efi_handle_name ( handle ),
+ strerror ( rc ) );
+ goto err_get_configuration_descriptor;
+ }
+ len = le16_to_cpu ( partial.TotalLength );
+
+ /* Allocate configuration descriptor */
+ usbio->config = malloc ( len );
+ if ( ! usbio->config ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* There is, naturally, no way to retrieve the entire device
+ * configuration descriptor via EFI_USB_IO_PROTOCOL. Worse,
+ * there is no way to even retrieve the index of the current
+ * configuration descriptor. We have to iterate over all
+ * possible configuration descriptors looking for the
+ * descriptor that matches the current configuration value.
+ */
+ for ( i = 0 ; i < count ; i++ ) {
+
+ /* Construct request */
+ msg.setup.request = cpu_to_le16 ( USB_GET_DESCRIPTOR );
+ value = ( ( USB_CONFIGURATION_DESCRIPTOR << 8 ) | i );
+ msg.setup.value = cpu_to_le16 ( value );
+ msg.setup.index = 0;
+ msg.setup.len = cpu_to_le16 ( len );
+
+ /* Get full configuration descriptor */
+ if ( ( efirc = io->UsbControlTransfer ( io, &msg.efi,
+ EfiUsbDataIn, 0,
+ usbio->config, len,
+ &status ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USB %s could not get configuration %d "
+ "descriptor: %s\n", efi_handle_name ( handle ),
+ i, strerror ( rc ) );
+ goto err_control_transfer;
+ }
+
+ /* Ignore unless this is the current configuration */
+ if ( usbio->config->config != partial.ConfigurationValue )
+ continue;
+
+ /* Check length */
+ if ( le16_to_cpu ( usbio->config->len ) != len ) {
+ DBGC ( usbio, "USB %s configuration descriptor length "
+ "mismatch\n", efi_handle_name ( handle ) );
+ rc = -EINVAL;
+ goto err_len;
+ }
+
+ return 0;
+ }
+
+ /* No match found */
+ DBGC ( usbio, "USB %s could not find current configuration "
+ "descriptor\n", efi_handle_name ( handle ) );
+ rc = -ENOENT;
+
+ err_len:
+ err_control_transfer:
+ free ( usbio->config );
+ err_alloc:
+ err_get_configuration_descriptor:
+ err_get_device_descriptor:
+ return rc;
+}
+
+/**
+ * Construct device path for opening other interfaces
+ *
+ * @v usbio USB I/O device
+ * @ret rc Return status code
+ */
+static int usbio_path ( struct usbio_device *usbio ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE handle = usbio->handle;
+ EFI_DEVICE_PATH_PROTOCOL *path;
+ EFI_DEVICE_PATH_PROTOCOL *end;
+ USB_DEVICE_PATH *usbpath;
+ union {
+ void *interface;
+ EFI_DEVICE_PATH_PROTOCOL *path;
+ } u;
+ size_t len;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Open device path protocol */
+ if ( ( efirc = bs->OpenProtocol ( handle,
+ &efi_device_path_protocol_guid,
+ &u.interface, efi_image_handle,
+ handle,
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s cannot open device path protocol: "
+ "%s\n", efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_open_protocol;
+ }
+ path = u.interface;
+
+ /* Locate end of device path and sanity check */
+ end = efi_devpath_end ( path );
+ len = ( ( ( void * ) end ) - ( ( void * ) path ) );
+ if ( len < sizeof ( *usbpath ) ) {
+ DBGC ( usbio, "USBIO %s underlength device path\n",
+ efi_handle_name ( handle ) );
+ rc = -EINVAL;
+ goto err_underlength;
+ }
+ usbpath = ( ( ( void * ) end ) - sizeof ( *usbpath ) );
+ if ( ! ( ( usbpath->Header.Type == MESSAGING_DEVICE_PATH ) &&
+ ( usbpath->Header.SubType == MSG_USB_DP ) ) ) {
+ DBGC ( usbio, "USBIO %s not a USB device path: ",
+ efi_handle_name ( handle ) );
+ DBGC ( usbio, "%s\n", efi_devpath_text ( path ) );
+ rc = -EINVAL;
+ goto err_non_usb;
+ }
+
+ /* Allocate copy of device path */
+ usbio->path = malloc ( len + sizeof ( *end ) );
+ if ( ! usbio->path ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ memcpy ( usbio->path, path, ( len + sizeof ( *end ) ) );
+ usbio->usbpath = ( ( ( void * ) usbio->path ) + len -
+ sizeof ( *usbpath ) );
+
+ /* Close protocol */
+ bs->CloseProtocol ( handle, &efi_device_path_protocol_guid,
+ efi_image_handle, handle );
+
+ return 0;
+
+ free ( usbio->path );
+ err_alloc:
+ err_non_usb:
+ err_underlength:
+ bs->CloseProtocol ( handle, &efi_device_path_protocol_guid,
+ efi_image_handle, handle );
+ err_open_protocol:
+ return rc;
+}
+
+/**
+ * Construct interface list
+ *
+ * @v usbio USB I/O device
+ * @ret rc Return status code
+ */
+static int usbio_interfaces ( struct usbio_device *usbio ) {
+ EFI_HANDLE handle = usbio->handle;
+ EFI_USB_IO_PROTOCOL *io = usbio->io;
+ EFI_USB_INTERFACE_DESCRIPTOR interface;
+ unsigned int first;
+ unsigned int count;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Get interface descriptor */
+ if ( ( efirc = io->UsbGetInterfaceDescriptor ( io, &interface ) ) != 0){
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USB %s could not get interface descriptor: "
+ "%s\n", efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_get_interface_descriptor;
+ }
+
+ /* Record first interface number */
+ first = interface.InterfaceNumber;
+ count = usbio->config->interfaces;
+ assert ( first < count );
+ usbio->first = first;
+
+ /* Allocate interface list */
+ usbio->interface = zalloc ( count * sizeof ( usbio->interface[0] ) );
+ if ( ! usbio->interface ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Use already-opened protocol for control transfers and for
+ * the first interface.
+ */
+ usbio->interface[0].handle = handle;
+ usbio->interface[0].io = io;
+ usbio->interface[0].count = 1;
+ usbio->interface[first].handle = handle;
+ usbio->interface[first].io = io;
+ usbio->interface[first].count = 1;
+
+ return 0;
+
+ free ( usbio->interface );
+ err_alloc:
+ err_get_interface_descriptor:
+ return rc;
+}
+
+/**
+ * Attach driver to device
+ *
+ * @v efidev EFI device
+ * @ret rc Return status code
+ */
+static int usbio_start ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE handle = efidev->device;
+ struct usbio_device *usbio;
+ struct usb_port *port;
+ union {
+ void *interface;
+ EFI_USB_IO_PROTOCOL *io;
+ } u;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Allocate and initialise structure */
+ usbio = zalloc ( sizeof ( *usbio ) );
+ if ( ! usbio ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ efidev_set_drvdata ( efidev, usbio );
+ usbio->handle = handle;
+ INIT_LIST_HEAD ( &usbio->endpoints );
+
+ /* Open USB I/O protocol */
+ if ( ( efirc = bs->OpenProtocol ( handle, &efi_usb_io_protocol_guid,
+ &u.interface, efi_image_handle,
+ handle,
+ ( EFI_OPEN_PROTOCOL_BY_DRIVER |
+ EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( usbio, "USBIO %s cannot open USB I/O protocol: %s\n",
+ efi_handle_name ( handle ), strerror ( rc ) );
+ DBGC_EFI_OPENERS ( usbio, handle, &efi_usb_io_protocol_guid );
+ goto err_open_usbio;
+ }
+ usbio->io = u.io;
+
+ /* Describe generic device */
+ efi_device_info ( handle, "USB", &usbio->dev );
+ usbio->dev.parent = &efidev->dev;
+ list_add ( &usbio->dev.siblings, &efidev->dev.children );
+ INIT_LIST_HEAD ( &usbio->dev.children );
+
+ /* Fetch configuration descriptor */
+ if ( ( rc = usbio_config ( usbio ) ) != 0 )
+ goto err_config;
+
+ /* Construct device path */
+ if ( ( rc = usbio_path ( usbio ) ) != 0 )
+ goto err_path;
+
+ /* Construct interface list */
+ if ( ( rc = usbio_interfaces ( usbio ) ) != 0 )
+ goto err_interfaces;
+
+ /* Allocate USB bus */
+ usbio->bus = alloc_usb_bus ( &usbio->dev, 1 /* single "port" */,
+ USBIO_MTU, &usbio_operations );
+ if ( ! usbio->bus ) {
+ rc = -ENOMEM;
+ goto err_alloc_bus;
+ }
+ usb_bus_set_hostdata ( usbio->bus, usbio );
+ usb_hub_set_drvdata ( usbio->bus->hub, usbio );
+
+ /* Set port protocol */
+ port = usb_port ( usbio->bus->hub, 1 );
+ port->protocol = USB_PROTO_2_0;
+
+ /* Register USB bus */
+ if ( ( rc = register_usb_bus ( usbio->bus ) ) != 0 )
+ goto err_register;
+
+ return 0;
+
+ unregister_usb_bus ( usbio->bus );
+ err_register:
+ free_usb_bus ( usbio->bus );
+ err_alloc_bus:
+ free ( usbio->interface );
+ err_interfaces:
+ free ( usbio->path );
+ err_path:
+ free ( usbio->config );
+ err_config:
+ list_del ( &usbio->dev.siblings );
+ bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid,
+ efi_image_handle, handle );
+ err_open_usbio:
+ free ( usbio );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Detach driver from device
+ *
+ * @v efidev EFI device
+ */
+static void usbio_stop ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE handle = efidev->device;
+ struct usbio_device *usbio = efidev_get_drvdata ( efidev );
+
+ unregister_usb_bus ( usbio->bus );
+ free_usb_bus ( usbio->bus );
+ free ( usbio->interface );
+ free ( usbio->path );
+ free ( usbio->config );
+ list_del ( &usbio->dev.siblings );
+ bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid,
+ efi_image_handle, handle );
+ free ( usbio );
+}
+
+/** EFI USB I/O driver */
+struct efi_driver usbio_driver __efi_driver ( EFI_DRIVER_NORMAL ) = {
+ .name = "USBIO",
+ .supported = usbio_supported,
+ .start = usbio_start,
+ .stop = usbio_stop,
+};
diff --git a/src/drivers/usb/usbio.h b/src/drivers/usb/usbio.h
new file mode 100644
index 00000000..1d02876f
--- /dev/null
+++ b/src/drivers/usb/usbio.h
@@ -0,0 +1,153 @@
+#ifndef _USBIO_H
+#define _USBIO_H
+
+/** @file
+ *
+ * EFI_USB_IO_PROTOCOL pseudo Host Controller Interface driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/list.h>
+#include <ipxe/device.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/UsbIo.h>
+#include <ipxe/efi/Protocol/DevicePath.h>
+#include <ipxe/usb.h>
+
+/** USB I/O maximum transfer size
+ *
+ * The API provides no way to discover the maximum transfer size.
+ * Assume the 16kB supported by EHCI.
+ */
+#define USBIO_MTU 16384
+
+/** USB I/O interrupt ring buffer size
+ *
+ * This is a policy decision.
+ */
+#define USBIO_INTR_COUNT 4
+
+/** A USB interrupt ring buffer */
+struct usbio_interrupt_ring {
+ /** USB I/O endpoint */
+ struct usbio_endpoint *endpoint;
+ /** Producer counter */
+ unsigned int prod;
+ /** Consumer counter */
+ unsigned int cons;
+ /** Data buffers */
+ void *data[USBIO_INTR_COUNT];
+ /** Lengths */
+ size_t len[USBIO_INTR_COUNT];
+};
+
+/** USB I/O ring buffer size
+ *
+ * This is a policy decision.
+ */
+#define USBIO_RING_COUNT 64
+
+/** A USB I/O endpoint */
+struct usbio_endpoint {
+ /** USB I/O device */
+ struct usbio_device *usbio;
+ /** USB endpoint */
+ struct usb_endpoint *ep;
+ /** List of endpoints */
+ struct list_head list;
+ /** USB I/O endpoint operations */
+ struct usbio_operations *op;
+
+ /** Containing interface number */
+ unsigned int interface;
+ /** EFI handle */
+ EFI_HANDLE handle;
+ /** USB I/O protocol */
+ EFI_USB_IO_PROTOCOL *io;
+
+ /** Producer counter */
+ unsigned int prod;
+ /** Consumer counter */
+ unsigned int cons;
+ /** I/O buffers */
+ struct io_buffer *iobuf[USBIO_RING_COUNT];
+ /** Flags */
+ uint8_t flags[USBIO_RING_COUNT];
+
+ /** Interrupt ring buffer (if applicable) */
+ struct usbio_interrupt_ring *intr;
+};
+
+/** USB I/O transfer flags */
+enum usbio_flags {
+ /** This is a message transfer */
+ USBIO_MESSAGE = 0x01,
+ /** This transfer requires zero-length packet termination */
+ USBIO_ZLEN = 0x02,
+};
+
+/** USB I/O endpoint operations */
+struct usbio_operations {
+ /** Open endpoint
+ *
+ * @v endpoint Endpoint
+ * @ret rc Return status code
+ */
+ int ( * open ) ( struct usbio_endpoint *endpoint );
+ /** Close endpoint
+ *
+ * @v endpoint Endpoint
+ */
+ void ( * close ) ( struct usbio_endpoint *endpoint );
+ /** Poll endpoint
+ *
+ * @v endpoint Endpoint
+ */
+ void ( * poll ) ( struct usbio_endpoint *endpoint );
+};
+
+/** A USB I/O protocol interface */
+struct usbio_interface {
+ /** EFI device handle */
+ EFI_HANDLE handle;
+ /** USB I/O protocol */
+ EFI_USB_IO_PROTOCOL *io;
+ /** Usage count */
+ unsigned int count;
+};
+
+/** A USB I/O protocol device
+ *
+ * We model each externally-provided USB I/O protocol device as a host
+ * controller containing a root hub with a single port.
+ */
+struct usbio_device {
+ /** EFI device handle */
+ EFI_HANDLE handle;
+ /** USB I/O protocol */
+ EFI_USB_IO_PROTOCOL *io;
+ /** Generic device */
+ struct device dev;
+
+ /** Configuration descriptor */
+ struct usb_configuration_descriptor *config;
+
+ /** Device path */
+ EFI_DEVICE_PATH_PROTOCOL *path;
+ /** Final component of USB device path */
+ USB_DEVICE_PATH *usbpath;
+
+ /** First interface number */
+ uint8_t first;
+ /** USB I/O protocol interfaces */
+ struct usbio_interface *interface;
+
+ /** USB bus */
+ struct usb_bus *bus;
+ /** List of endpoints */
+ struct list_head endpoints;
+};
+
+#endif /* _USBIO_H */
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h
index 00f8f981..aff911e3 100644
--- a/src/include/ipxe/errfile.h
+++ b/src/include/ipxe/errfile.h
@@ -85,6 +85,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_uhci ( ERRFILE_DRIVER | 0x000b0000 )
#define ERRFILE_usbhid ( ERRFILE_DRIVER | 0x000c0000 )
#define ERRFILE_usbkbd ( ERRFILE_DRIVER | 0x000d0000 )
+#define ERRFILE_usbio ( ERRFILE_DRIVER | 0x000e0000 )
#define ERRFILE_nvs ( ERRFILE_DRIVER | 0x00100000 )
#define ERRFILE_spi ( ERRFILE_DRIVER | 0x00110000 )
diff --git a/src/include/ipxe/usb.h b/src/include/ipxe/usb.h
index f7c0b96e..dfe0f348 100644
--- a/src/include/ipxe/usb.h
+++ b/src/include/ipxe/usb.h
@@ -68,7 +68,7 @@ enum usb_pid {
struct usb_setup_packet {
/** Request */
uint16_t request;
- /** Value paramer */
+ /** Value parameter */
uint16_t value;
/** Index parameter */
uint16_t index;
@@ -91,6 +91,9 @@ struct usb_setup_packet {
/** Vendor-specific request type */
#define USB_TYPE_VENDOR ( 2 << 5 )
+/** Request recipient mask */
+#define USB_RECIP_MASK ( 0x1f << 0 )
+
/** Request recipient is the device */
#define USB_RECIP_DEVICE ( 0 << 0 )