]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Expose unused USB devices via EFI_USB_IO_PROTOCOL
authorMichael Brown <mcb30@ipxe.org>
Thu, 10 Sep 2015 16:22:03 +0000 (17:22 +0100)
committerMichael Brown <mcb30@ipxe.org>
Mon, 14 Sep 2015 21:11:37 +0000 (22:11 +0100)
Allow the UEFI platform firmware to provide drivers for unrecognised
devices, by exposing our own implementation of EFI_USB_IO_PROTOCOL.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/config/config_usb.c
src/config/defaults/efi.h
src/config/usb.h
src/include/ipxe/efi/efi_usb.h [new file with mode: 0644]
src/include/ipxe/errfile.h
src/interface/efi/efi_usb.c [new file with mode: 0644]

index 4e5843b9d87264bd2df4a95def44258c5537fde3..17296d2776a596c07f43f54000dff9ba1e9a0c36 100644 (file)
@@ -53,3 +53,10 @@ REQUIRE_OBJECT ( usbio );
 #ifdef USB_KEYBOARD
 REQUIRE_OBJECT ( usbkbd );
 #endif
+
+/*
+ * Drag in USB external interfaces
+ */
+#ifdef USB_EFI
+REQUIRE_OBJECT ( efi_usb );
+#endif
index cdf41c54d419b34c0bc8d5ecb2ee07dbeed0018d..502bef1d95d34969128375e9f40759f1a9cb13fb 100644 (file)
@@ -26,6 +26,11 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define        IMAGE_EFI               /* EFI image support */
 #define        IMAGE_SCRIPT            /* iPXE script image support */
 
+#define        USB_HCD_XHCI            /* xHCI USB host controller */
+#define        USB_HCD_EHCI            /* EHCI USB host controller */
+#define        USB_HCD_UHCI            /* UHCI USB host controller */
+#define        USB_EFI                 /* Provide EFI_USB_IO_PROTOCOL interface */
+
 #define        REBOOT_CMD              /* Reboot command */
 #define        CPUID_CMD               /* x86 CPU feature detection command */
 
index 6d80e7f6f1aa19062640f8d159eb778ecd7fbd11..d2519d8777672233a8103f093a4daffb70c78c50 100644 (file)
@@ -15,16 +15,22 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  * USB host controllers (all enabled by default)
  *
  */
-//#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 */
+//#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
  *
  */
-//#undef       USB_KEYBOARD            /* USB keyboards */
+//#undef       USB_KEYBOARD    /* USB keyboards */
+
+/*
+ * USB external interfaces
+ *
+ */
+//#undef       USB_EFI         /* Provide EFI_USB_IO_PROTOCOL interface */
 
 #include <config/named.h>
 #include NAMED_CONFIG(usb.h)
diff --git a/src/include/ipxe/efi/efi_usb.h b/src/include/ipxe/efi/efi_usb.h
new file mode 100644 (file)
index 0000000..05b4fad
--- /dev/null
@@ -0,0 +1,80 @@
+#ifndef _IPXE_EFI_USB_H
+#define _IPXE_EFI_USB_H
+
+/** @file
+ *
+ * USB I/O protocol
+ *
+ */
+
+#include <ipxe/list.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/Protocol/UsbIo.h>
+#include <ipxe/usb.h>
+
+/** An EFI USB device */
+struct efi_usb_device {
+       /** Name */
+       const char *name;
+       /** The underlying USB device */
+       struct usb_device *usb;
+       /** The underlying EFI device */
+       struct efi_device *efidev;
+       /** Configuration descriptor */
+       struct usb_configuration_descriptor *config;
+       /** Supported languages */
+       struct usb_descriptor_header *languages;
+       /** List of interfaces */
+       struct list_head interfaces;
+};
+
+/** An EFI USB device interface */
+struct efi_usb_interface {
+       /** Name */
+       char name[32];
+       /** Containing USB device */
+       struct efi_usb_device *usbdev;
+       /** List of interfaces */
+       struct list_head list;
+
+       /** Interface number */
+       unsigned int interface;
+       /** Alternate setting */
+       unsigned int alternate;
+       /** EFI handle */
+       EFI_HANDLE handle;
+       /** USB I/O protocol */
+       EFI_USB_IO_PROTOCOL usbio;
+       /** Device path */
+       EFI_DEVICE_PATH_PROTOCOL *path;
+
+       /** Opened endpoints */
+       struct efi_usb_endpoint *endpoint[32];
+};
+
+/** An EFI USB device endpoint */
+struct efi_usb_endpoint {
+       /** EFI USB device interface */
+       struct efi_usb_interface *usbintf;
+       /** USB endpoint */
+       struct usb_endpoint ep;
+
+       /** Most recent synchronous completion status */
+       int rc;
+
+       /** Asynchronous timer event */
+       EFI_EVENT event;
+       /** Asynchronous callback handler */
+       EFI_ASYNC_USB_TRANSFER_CALLBACK callback;
+       /** Asynchronous callback context */
+       void *context;
+};
+
+/** Asynchronous transfer fill level
+ *
+ * This is a policy decision.
+ */
+#define EFI_USB_ASYNC_FILL 2
+
+#endif /* _IPXE_EFI_USB_H */
index aff911e3b635a3c4e8fcfac2ae71d80ca0fe4d3f..6c4dcf503265868e7c165fb2dbdf0b85a886b564 100644 (file)
@@ -341,6 +341,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_efi_time             ( ERRFILE_OTHER | 0x00480000 )
 #define ERRFILE_efi_watchdog         ( ERRFILE_OTHER | 0x00490000 )
 #define ERRFILE_efi_pxe                      ( ERRFILE_OTHER | 0x004a0000 )
+#define ERRFILE_efi_usb                      ( ERRFILE_OTHER | 0x004b0000 )
 
 /** @} */
 
diff --git a/src/interface/efi/efi_usb.c b/src/interface/efi/efi_usb.c
new file mode 100644 (file)
index 0000000..94f2161
--- /dev/null
@@ -0,0 +1,1305 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_usb.h>
+#include <ipxe/usb.h>
+
+/** @file
+ *
+ * EFI USB I/O PROTOCOL
+ *
+ */
+
+/**
+ * Transcribe data direction (for debugging)
+ *
+ * @v direction                Data direction
+ * @ret text           Transcribed data direction
+ */
+static const char * efi_usb_direction_name ( EFI_USB_DATA_DIRECTION direction ){
+
+       switch ( direction ) {
+       case EfiUsbDataIn:      return "in";
+       case EfiUsbDataOut:     return "out";
+       case EfiUsbNoData:      return "none";
+       default:                return "<UNKNOWN>";
+       }
+}
+
+/******************************************************************************
+ *
+ * Endpoints
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Poll USB bus
+ *
+ * @v usbdev           EFI USB device
+ */
+static void efi_usb_poll ( struct efi_usb_device *usbdev ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct usb_bus *bus = usbdev->usb->port->hub->bus;
+       EFI_TPL tpl;
+
+       /* UEFI manages to ingeniously combine the worst aspects of
+        * both polling and interrupt-driven designs.  There is no way
+        * to support proper interrupt-driven operation, since there
+        * is no way to hook in an interrupt service routine.  A
+        * mockery of interrupts is provided by UEFI timers, which
+        * trigger at a preset rate and can fire at any time.
+        *
+        * We therefore have all of the downsides of a polling design
+        * (inefficiency and inability to sleep until something
+        * interesting happens) combined with all of the downsides of
+        * an interrupt-driven design (the complexity of code that
+        * could be preempted at any time).
+        *
+        * The UEFI specification expects us to litter the entire
+        * codebase with calls to RaiseTPL() as needed for sections of
+        * code that are not reentrant.  Since this doesn't actually
+        * gain us any substantive benefits (since even with such
+        * calls we would still be suffering from the limitations of a
+        * polling design), we instead choose to wrap only calls to
+        * usb_poll().  This should be sufficient for most practical
+        * purposes.
+        *
+        * A "proper" solution would involve rearchitecting the whole
+        * codebase to support interrupt-driven operation.
+        */
+       tpl = bs->RaiseTPL ( TPL_NOTIFY );
+
+       /* Poll bus */
+       usb_poll ( bus );
+
+       /* Restore task priority level */
+       bs->RestoreTPL ( tpl );
+}
+
+/**
+ * Poll USB bus (from endpoint event timer)
+ *
+ * @v event            EFI event
+ * @v context          EFI USB endpoint
+ */
+static VOID EFIAPI efi_usb_timer ( EFI_EVENT event __unused,
+                                  VOID *context ) {
+       struct efi_usb_endpoint *usbep = context;
+       struct usb_bus *bus = usbep->usbintf->usbdev->usb->port->hub->bus;
+
+       /* Poll bus */
+       usb_poll ( bus );
+
+       /* Refill endpoint */
+       usb_refill ( &usbep->ep );
+}
+
+/**
+ * Get endpoint MTU
+ *
+ * @v usbintf          EFI USB interface
+ * @v endpoint         Endpoint address
+ * @ret mtu            Endpoint MTU, or negative error
+ */
+static int efi_usb_mtu ( struct efi_usb_interface *usbintf,
+                        unsigned int endpoint ) {
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct usb_interface_descriptor *interface;
+       struct usb_endpoint_descriptor *desc;
+
+       /* Locate cached interface descriptor */
+       interface = usb_interface_descriptor ( usbdev->config,
+                                              usbintf->interface,
+                                              usbintf->alternate );
+       if ( ! interface ) {
+               DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n",
+                      usbintf->name, usbintf->alternate );
+               return -ENOENT;
+       }
+
+       /* Locate and copy cached endpoint descriptor */
+       for_each_interface_descriptor ( desc, usbdev->config, interface ) {
+               if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) &&
+                    ( desc->endpoint == endpoint ) )
+                       return USB_ENDPOINT_MTU ( le16_to_cpu ( desc->sizes ) );
+       }
+
+       DBGC ( usbdev, "USBDEV %s alt %d ep %02x has no descriptor\n",
+              usbintf->name, usbintf->alternate, endpoint );
+       return -ENOENT;
+}
+
+/**
+ * Open endpoint
+ *
+ * @v usbintf          EFI USB interface
+ * @v endpoint         Endpoint address
+ * @v attributes       Endpoint attributes
+ * @v interval         Interval (in milliseconds)
+ * @v driver           Driver operations
+ * @ret rc             Return status code
+ */
+static int efi_usb_open ( struct efi_usb_interface *usbintf,
+                         unsigned int endpoint, unsigned int attributes,
+                         unsigned int interval,
+                         struct usb_endpoint_driver_operations *driver ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct efi_usb_endpoint *usbep;
+       unsigned int index = USB_ENDPOINT_IDX ( endpoint );
+       int mtu;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Get endpoint MTU */
+       mtu = efi_usb_mtu ( usbintf, endpoint );
+       if ( mtu < 0 ) {
+               rc = mtu;
+               goto err_mtu;
+       }
+
+       /* Allocate and initialise structure */
+       usbep = zalloc ( sizeof ( *usbep ) );
+       if ( ! usbep ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       usbep->usbintf = usbintf;
+       usb_endpoint_init ( &usbep->ep, usbdev->usb, driver );
+       usb_endpoint_describe ( &usbep->ep, endpoint, attributes, mtu, 0,
+                               ( interval << 3 /* microframes */ ) );
+
+       /* Open endpoint */
+       if ( ( rc = usb_endpoint_open ( &usbep->ep ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s %s could not open: %s\n",
+                      usbintf->name, usb_endpoint_name ( &usbep->ep ),
+                      strerror ( rc ) );
+               goto err_open;
+       }
+
+       /* Record opened endpoint */
+       usbintf->endpoint[index] = usbep;
+       DBGC ( usbdev, "USBDEV %s %s opened\n",
+              usbintf->name, usb_endpoint_name ( &usbep->ep ) );
+
+       /* Create event */
+       if ( ( efirc = bs->CreateEvent ( ( EVT_TIMER | EVT_NOTIFY_SIGNAL ),
+                                        TPL_NOTIFY, efi_usb_timer, usbep,
+                                        &usbep->event ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( usbdev, "USBDEV %s %s could not create event: %s\n",
+                      usbintf->name, usb_endpoint_name ( &usbep->ep ),
+                      strerror ( rc ) );
+               goto err_event;
+       }
+
+       return 0;
+
+       bs->CloseEvent ( usbep->event );
+ err_event:
+       usbintf->endpoint[index] = usbep;
+       usb_endpoint_close ( &usbep->ep );
+ err_open:
+       free ( usbep );
+ err_alloc:
+ err_mtu:
+       return rc;
+}
+
+/**
+ * Close endpoint
+ *
+ * @v usbep            EFI USB endpoint
+ */
+static void efi_usb_close ( struct efi_usb_endpoint *usbep ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_usb_interface *usbintf = usbep->usbintf;
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       unsigned int index = USB_ENDPOINT_IDX ( usbep->ep.address );
+
+       /* Sanity check */
+       assert ( usbintf->endpoint[index] == usbep );
+
+       /* Cancel timer (if applicable) and close event */
+       bs->SetTimer ( usbep->event, TimerCancel, 0 );
+       bs->CloseEvent ( usbep->event );
+
+       /* Close endpoint */
+       usb_endpoint_close ( &usbep->ep );
+       DBGC ( usbdev, "USBDEV %s %s closed\n",
+              usbintf->name, usb_endpoint_name ( &usbep->ep ) );
+
+       /* Free endpoint */
+       free ( usbep );
+
+       /* Record closed endpoint */
+       usbintf->endpoint[index] = NULL;
+}
+
+/**
+ * Close all endpoints
+ *
+ * @v usbintf          EFI USB interface
+ */
+static void efi_usb_close_all ( struct efi_usb_interface *usbintf ) {
+       struct efi_usb_endpoint *usbep;
+       unsigned int i;
+
+       for ( i = 0 ; i < ( sizeof ( usbintf->endpoint ) /
+                           sizeof ( usbintf->endpoint[0] ) ) ; i++ ) {
+               usbep = usbintf->endpoint[i];
+               if ( usbep )
+                       efi_usb_close ( usbep );
+       }
+}
+
+/**
+ * Complete synchronous transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void efi_usb_sync_complete ( struct usb_endpoint *ep,
+                                   struct io_buffer *iobuf __unused, int rc ) {
+       struct efi_usb_endpoint *usbep =
+               container_of ( ep, struct efi_usb_endpoint, ep );
+
+       /* Record completion status */
+       usbep->rc = rc;
+}
+
+/** Synchronous endpoint operations */
+static struct usb_endpoint_driver_operations efi_usb_sync_driver = {
+       .complete = efi_usb_sync_complete,
+};
+
+/**
+ * Perform synchronous transfer
+ *
+ * @v usbintf          USB endpoint
+ * @v endpoint         Endpoint address
+ * @v attributes       Endpoint attributes
+ * @v timeout          Timeout (in milliseconds)
+ * @v data             Data buffer
+ * @v len              Length of data buffer
+ * @ret rc             Return status code
+ */
+static int efi_usb_sync_transfer ( struct efi_usb_interface *usbintf,
+                                  unsigned int endpoint,
+                                  unsigned int attributes,
+                                  unsigned int timeout,
+                                  void *data, size_t *len ) {
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct efi_usb_endpoint *usbep;
+       struct io_buffer *iobuf;
+       unsigned int index = USB_ENDPOINT_IDX ( endpoint );
+       unsigned int i;
+       int rc;
+
+       /* Open endpoint, if applicable */
+       if ( ( ! usbintf->endpoint[index] ) &&
+            ( ( rc = efi_usb_open ( usbintf, endpoint, attributes, 0,
+                                    &efi_usb_sync_driver ) ) != 0 ) ) {
+               goto err_open;
+       }
+       usbep = usbintf->endpoint[index];
+
+       /* Allocate and construct I/O buffer */
+       iobuf = alloc_iob ( *len );
+       if ( ! iobuf ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       iob_put ( iobuf, *len );
+       if ( ! ( endpoint & USB_ENDPOINT_IN ) )
+               memcpy ( iobuf->data, data, *len );
+
+       /* Initialise completion status */
+       usbep->rc = -EINPROGRESS;
+
+       /* Enqueue transfer */
+       if ( ( rc = usb_stream ( &usbep->ep, iobuf, 0 ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s %s could not enqueue: %s\n",
+                      usbintf->name, usb_endpoint_name ( &usbep->ep ),
+                      strerror ( rc ) );
+               goto err_stream;
+       }
+
+       /* Wait for completion */
+       rc = -ETIMEDOUT;
+       for ( i = 0 ; ( ( timeout == 0 ) || ( i < timeout ) ) ; i++ ) {
+
+               /* Poll bus */
+               efi_usb_poll ( usbdev );
+
+               /* Check for completion */
+               if ( usbep->rc != -EINPROGRESS ) {
+                       rc = usbep->rc;
+                       break;
+               }
+
+               /* Delay */
+               mdelay ( 1 );
+       }
+
+       /* Check for errors */
+       if ( rc != 0 ) {
+               DBGC ( usbdev, "USBDEV %s %s failed: %s\n", usbintf->name,
+                      usb_endpoint_name ( &usbep->ep ), strerror ( rc ) );
+               goto err_completion;
+       }
+
+       /* Copy completion to data buffer, if applicable */
+       assert ( iob_len ( iobuf ) <= *len );
+       if ( endpoint & USB_ENDPOINT_IN )
+               memcpy ( data, iobuf->data, iob_len ( iobuf ) );
+       *len = iob_len ( iobuf );
+
+       /* Free I/O buffer */
+       free_iob ( iobuf );
+
+       /* Leave endpoint open */
+       return 0;
+
+ err_completion:
+ err_stream:
+       free_iob ( iobuf );
+ err_alloc:
+       efi_usb_close ( usbep );
+ err_open:
+       return EFIRC ( rc );
+}
+
+/**
+ * Complete asynchronous transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void efi_usb_async_complete ( struct usb_endpoint *ep,
+                                    struct io_buffer *iobuf, int rc ) {
+       struct efi_usb_endpoint *usbep =
+               container_of ( ep, struct efi_usb_endpoint, ep );
+       UINT32 status;
+
+       /* Ignore packets cancelled when the endpoint closes */
+       if ( ! ep->open )
+               goto drop;
+
+       /* Construct status */
+       status = ( ( rc == 0 ) ? 0 : EFI_USB_ERR_STALL );
+
+       /* Report completion */
+       usbep->callback ( iobuf->data, iob_len ( iobuf ), usbep->context,
+                         status );
+
+ drop:
+       /* Recycle I/O buffer */
+       usb_recycle ( &usbep->ep, iobuf );
+}
+
+/** Asynchronous endpoint operations */
+static struct usb_endpoint_driver_operations efi_usb_async_driver = {
+       .complete = efi_usb_async_complete,
+};
+
+/**
+ * Start asynchronous transfer
+ *
+ * @v usbintf          EFI USB interface
+ * @v endpoint         Endpoint address
+ * @v interval         Interval (in milliseconds)
+ * @v len              Transfer length
+ * @v callback         Callback function
+ * @v context          Context for callback function
+ * @ret rc             Return status code
+ */
+static int efi_usb_async_start ( struct efi_usb_interface *usbintf,
+                                unsigned int endpoint, unsigned int interval,
+                                size_t len,
+                                EFI_ASYNC_USB_TRANSFER_CALLBACK callback,
+                                void *context ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct efi_usb_endpoint *usbep;
+       unsigned int index = USB_ENDPOINT_IDX ( endpoint );
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Open endpoint */
+       if ( ( rc = efi_usb_open ( usbintf, endpoint,
+                                  USB_ENDPOINT_ATTR_INTERRUPT, interval,
+                                  &efi_usb_async_driver ) ) != 0 )
+               goto err_open;
+       usbep = usbintf->endpoint[index];
+
+       /* Record callback parameters */
+       usbep->callback = callback;
+       usbep->context = context;
+
+       /* Prefill endpoint */
+       usb_refill_init ( &usbep->ep, len, EFI_USB_ASYNC_FILL );
+       if ( ( rc = usb_prefill ( &usbep->ep ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s %s could not prefill: %s\n",
+                      usbintf->name, usb_endpoint_name ( &usbep->ep ),
+                      strerror ( rc ) );
+               goto err_prefill;
+       }
+
+       /* Start timer */
+       if ( ( efirc = bs->SetTimer ( usbep->event, TimerPeriodic,
+                                     ( interval * 10000 ) ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( usbdev, "USBDEV %s %s could not set timer: %s\n",
+                      usbintf->name, usb_endpoint_name ( &usbep->ep ),
+                      strerror ( rc ) );
+               goto err_timer;
+       }
+
+       return 0;
+
+       bs->SetTimer ( usbep->event, TimerCancel, 0 );
+ err_timer:
+ err_prefill:
+       efi_usb_close ( usbep );
+ err_open:
+       return rc;
+}
+
+/**
+ * Stop asynchronous transfer
+ *
+ * @v usbintf          EFI USB interface
+ * @v endpoint         Endpoint address
+ */
+static void efi_usb_async_stop ( struct efi_usb_interface *usbintf,
+                                unsigned int endpoint ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_usb_endpoint *usbep;
+       unsigned int index = USB_ENDPOINT_IDX ( endpoint );
+
+       /* Do nothing if endpoint is already closed */
+       usbep = usbintf->endpoint[index];
+       if ( ! usbep )
+               return;
+
+       /* Stop timer */
+       bs->SetTimer ( usbep->event, TimerCancel, 0 );
+
+       /* Close endpoint */
+       efi_usb_close ( usbep );
+}
+
+/******************************************************************************
+ *
+ * USB I/O protocol
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Perform control transfer
+ *
+ * @v usbio            USB I/O protocol
+ * @v packet           Setup packet
+ * @v direction                Data direction
+ * @v timeout          Timeout (in milliseconds)
+ * @v data             Data buffer
+ * @v len              Length of data
+ * @ret status         Transfer status
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_control_transfer ( EFI_USB_IO_PROTOCOL *usbio,
+                          EFI_USB_DEVICE_REQUEST *packet,
+                          EFI_USB_DATA_DIRECTION direction,
+                          UINT32 timeout, VOID *data, UINTN len,
+                          UINT32 *status ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       unsigned int request = ( packet->RequestType |
+                                USB_REQUEST_TYPE ( packet->Request ) );
+       unsigned int value = le16_to_cpu ( packet->Value );
+       unsigned int index = le16_to_cpu ( packet->Index );
+       int rc;
+
+       DBGC2 ( usbdev, "USBDEV %s control %04x:%04x:%04x:%04x %s %dms "
+               "%p+%zx\n", usbintf->name, request, value, index,
+               le16_to_cpu ( packet->Length ),
+               efi_usb_direction_name ( direction ), timeout, data,
+               ( ( size_t ) len ) );
+
+       /* Clear status */
+       *status = 0;
+
+       /* Block attempts to change the device configuration, since
+        * this is logically impossible to do given the constraints of
+        * the EFI_USB_IO_PROTOCOL design.
+        */
+       if ( ( request == USB_SET_CONFIGURATION ) &&
+            ( value != usbdev->config->config ) ) {
+               DBGC ( usbdev, "USBDEV %s cannot set configuration %d: not "
+                      "logically possible\n", usbintf->name, index );
+               rc = -ENOTSUP;
+               goto err_change_config;
+       }
+
+       /* If we are selecting a new alternate setting then close all
+        * open endpoints.
+        */
+       if ( ( request == USB_SET_INTERFACE ) &&
+            ( value != usbintf->alternate ) )
+               efi_usb_close_all ( usbintf );
+
+       /* Issue control transfer */
+       if ( ( rc = usb_control ( usbdev->usb, request, value, index,
+                                 data, len ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s control %04x:%04x:%04x:%04x %p+%zx "
+                      "failed: %s\n", usbintf->name, request, value, index,
+                      le16_to_cpu ( packet->Length ), data, ( ( size_t ) len ),
+                      strerror ( rc ) );
+               /* Assume that any error represents a stall */
+               *status = EFI_USB_ERR_STALL;
+               goto err_control;
+       }
+
+       /* Update alternate setting, if applicable */
+       if ( request == USB_SET_INTERFACE ) {
+               usbintf->alternate = value;
+               DBGC ( usbdev, "USBDEV %s alt %d selected\n",
+                      usbintf->name, usbintf->alternate );
+       }
+
+ err_control:
+ err_change_config:
+       return EFIRC ( rc );
+}
+
+/**
+ * Perform bulk transfer
+ *
+ * @v usbio            USB I/O protocol
+ * @v endpoint         Endpoint address
+ * @v data             Data buffer
+ * @v len              Length of data
+ * @v timeout          Timeout (in milliseconds)
+ * @ret status         Transfer status
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_bulk_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, VOID *data,
+                       UINTN *len, UINTN timeout, UINT32 *status ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       size_t actual = *len;
+       int rc;
+
+       DBGC2 ( usbdev, "USBDEV %s bulk %s %p+%zx %dms\n", usbintf->name,
+               ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data,
+               ( ( size_t ) *len ), ( ( unsigned int ) timeout ) );
+
+       /* Clear status */
+       *status = 0;
+
+       /* Perform synchronous transfer */
+       if ( ( rc = efi_usb_sync_transfer ( usbintf, endpoint,
+                                           USB_ENDPOINT_ATTR_BULK, timeout,
+                                           data, &actual ) ) != 0 ) {
+               /* Assume that any error represents a timeout */
+               *status = EFI_USB_ERR_TIMEOUT;
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Perform synchronous interrupt transfer
+ *
+ * @v usbio            USB I/O protocol
+ * @v endpoint         Endpoint address
+ * @v data             Data buffer
+ * @v len              Length of data
+ * @v timeout          Timeout (in milliseconds)
+ * @ret status         Transfer status
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_sync_interrupt_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint,
+                                 VOID *data, UINTN *len, UINTN timeout,
+                                 UINT32 *status ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       size_t actual = *len;
+       int rc;
+
+       DBGC2 ( usbdev, "USBDEV %s sync intr %s %p+%zx %dms\n", usbintf->name,
+               ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data,
+               ( ( size_t ) *len ), ( ( unsigned int ) timeout ) );
+
+       /* Clear status */
+       *status = 0;
+
+       /* Perform synchronous transfer */
+       if ( ( rc = efi_usb_sync_transfer ( usbintf, endpoint,
+                                           USB_ENDPOINT_ATTR_INTERRUPT,
+                                           timeout, data, &actual ) ) != 0 ) {
+               /* Assume that any error represents a timeout */
+               *status = EFI_USB_ERR_TIMEOUT;
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Perform asynchronous interrupt transfer
+ *
+ * @v usbio            USB I/O protocol
+ * @v endpoint         Endpoint address
+ * @v start            Start (rather than stop) transfer
+ * @v interval         Polling interval (in milliseconds)
+ * @v len              Data length
+ * @v callback         Callback function
+ * @v context          Context for callback function
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_async_interrupt_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint,
+                                  BOOLEAN start, UINTN interval, UINTN len,
+                                  EFI_ASYNC_USB_TRANSFER_CALLBACK callback,
+                                  VOID *context ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       int rc;
+
+       DBGC2 ( usbdev, "USBDEV %s async intr %s len %#zx int %d %p/%p\n",
+               usbintf->name,
+               ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ),
+               ( ( size_t ) len ), ( ( unsigned int ) interval ),
+               callback, context );
+
+       /* Start/stop transfer as applicable */
+       if ( start ) {
+
+               /* Start new transfer */
+               if ( ( rc = efi_usb_async_start ( usbintf, endpoint, interval,
+                                                 len, callback,
+                                                 context ) ) != 0 )
+                       goto err_start;
+
+       } else {
+
+               /* Stop transfer */
+               efi_usb_async_stop ( usbintf, endpoint );
+
+       }
+
+       return 0;
+
+ err_start:
+       return EFIRC ( rc );
+}
+
+/**
+ * Perform synchronous isochronous transfer
+ *
+ * @v usbio            USB I/O protocol
+ * @v endpoint         Endpoint address
+ * @v data             Data buffer
+ * @v len              Length of data
+ * @ret status         Transfer status
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_isochronous_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint,
+                              VOID *data, UINTN len, UINT32 *status ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s sync iso %s %p+%zx\n", usbintf->name,
+               ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data,
+               ( ( size_t ) len ) );
+
+       /* Clear status */
+       *status = 0;
+
+       /* Not supported */
+       return EFI_UNSUPPORTED;
+}
+
+/**
+ * Perform asynchronous isochronous transfers
+ *
+ * @v usbio            USB I/O protocol
+ * @v endpoint         Endpoint address
+ * @v data             Data buffer
+ * @v len              Length of data
+ * @v callback         Callback function
+ * @v context          Context for callback function
+ * @ret status         Transfer status
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_async_isochronous_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint,
+                                    VOID *data, UINTN len,
+                                    EFI_ASYNC_USB_TRANSFER_CALLBACK callback,
+                                    VOID *context ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s async iso %s %p+%zx %p/%p\n", usbintf->name,
+               ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data,
+               ( ( size_t ) len ), callback, context );
+
+       /* Not supported */
+       return EFI_UNSUPPORTED;
+}
+
+/**
+ * Get device descriptor
+ *
+ * @v usbio            USB I/O protocol
+ * @ret efidesc                EFI device descriptor
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_device_descriptor ( EFI_USB_IO_PROTOCOL *usbio,
+                               EFI_USB_DEVICE_DESCRIPTOR *efidesc ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s get device descriptor\n", usbintf->name );
+
+       /* Copy cached device descriptor */
+       memcpy ( efidesc, &usbdev->usb->device, sizeof ( *efidesc ) );
+
+       return 0;
+}
+
+/**
+ * Get configuration descriptor
+ *
+ * @v usbio            USB I/O protocol
+ * @ret efidesc                EFI interface descriptor
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_config_descriptor ( EFI_USB_IO_PROTOCOL *usbio,
+                               EFI_USB_CONFIG_DESCRIPTOR *efidesc ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s get configuration descriptor\n",
+               usbintf->name );
+
+       /* Copy cached configuration descriptor */
+       memcpy ( efidesc, usbdev->config, sizeof ( *efidesc ) );
+
+       return 0;
+}
+
+/**
+ * Get interface descriptor
+ *
+ * @v usbio            USB I/O protocol
+ * @ret efidesc                EFI interface descriptor
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_interface_descriptor ( EFI_USB_IO_PROTOCOL *usbio,
+                                  EFI_USB_INTERFACE_DESCRIPTOR *efidesc ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct usb_interface_descriptor *desc;
+
+       DBGC2 ( usbdev, "USBDEV %s get interface descriptor\n", usbintf->name );
+
+       /* Locate cached interface descriptor */
+       desc = usb_interface_descriptor ( usbdev->config, usbintf->interface,
+                                         usbintf->alternate );
+       if ( ! desc ) {
+               DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n",
+                      usbintf->name, usbintf->alternate );
+               return -ENOENT;
+       }
+
+       /* Copy cached interface descriptor */
+       memcpy ( efidesc, desc, sizeof ( *efidesc ) );
+
+       return 0;
+}
+
+/**
+ * Get endpoint descriptor
+ *
+ * @v usbio            USB I/O protocol
+ * @v address          Endpoint index
+ * @ret efidesc                EFI interface descriptor
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_endpoint_descriptor ( EFI_USB_IO_PROTOCOL *usbio, UINT8 index,
+                                 EFI_USB_ENDPOINT_DESCRIPTOR *efidesc ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct usb_interface_descriptor *interface;
+       struct usb_endpoint_descriptor *desc;
+
+       DBGC2 ( usbdev, "USBDEV %s get endpoint %d descriptor\n",
+               usbintf->name, index );
+
+       /* Locate cached interface descriptor */
+       interface = usb_interface_descriptor ( usbdev->config,
+                                              usbintf->interface,
+                                              usbintf->alternate );
+       if ( ! interface ) {
+               DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n",
+                      usbintf->name, usbintf->alternate );
+               return -ENOENT;
+       }
+
+       /* Locate and copy cached endpoint descriptor */
+       for_each_interface_descriptor ( desc, usbdev->config, interface ) {
+               if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) &&
+                    ( index-- == 0 ) ) {
+                       memcpy ( efidesc, desc, sizeof ( *efidesc ) );
+                       return 0;
+               }
+       }
+       return -ENOENT;
+}
+
+/**
+ * Get string descriptor
+ *
+ * @v usbio            USB I/O protocol
+ * @v language         Language ID
+ * @v index            String index
+ * @ret string         String
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_string_descriptor ( EFI_USB_IO_PROTOCOL *usbio, UINT16 language,
+                               UINT8 index, CHAR16 **string ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+       struct usb_descriptor_header header;
+       VOID *buffer;
+       size_t len;
+       EFI_STATUS efirc;
+       int rc;
+
+       DBGC2 ( usbdev, "USBDEV %s get string %d:%d descriptor\n",
+               usbintf->name, language, index );
+
+       /* Read descriptor header */
+       if ( ( rc = usb_get_descriptor ( usbdev->usb, 0, USB_STRING_DESCRIPTOR,
+                                        index, language, &header,
+                                        sizeof ( header ) ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s could not get string %d:%d "
+                      "descriptor header: %s\n", usbintf->name, language,
+                      index, strerror ( rc ) );
+               goto err_get_header;
+       }
+       len = header.len;
+
+       /* Allocate buffer */
+       if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, len,
+                                         &buffer ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               goto err_alloc;
+       }
+
+       /* Read whole descriptor */
+       if ( ( rc = usb_get_descriptor ( usbdev->usb, 0, USB_STRING_DESCRIPTOR,
+                                        index, language, buffer,
+                                        len ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s could not get string %d:%d "
+                      "descriptor: %s\n", usbintf->name, language,
+                      index, strerror ( rc ) );
+               goto err_get_descriptor;
+       }
+
+       /* Shuffle down and terminate string */
+       memmove ( buffer, ( buffer + sizeof ( header ) ),
+                 ( len - sizeof ( header ) ) );
+       memset ( ( buffer + len - sizeof ( header ) ), 0, sizeof ( **string ) );
+
+       /* Return allocated string */
+       *string = buffer;
+       return 0;
+
+ err_get_descriptor:
+       bs->FreePool ( buffer );
+ err_alloc:
+ err_get_header:
+       return EFIRC ( rc );
+}
+
+/**
+ * Get supported languages
+ *
+ * @v usbio            USB I/O protocol
+ * @ret languages      Language ID table
+ * @ret len            Length of language ID table
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_get_supported_languages ( EFI_USB_IO_PROTOCOL *usbio,
+                                 UINT16 **languages, UINT16 *len ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s get supported languages\n", usbintf->name );
+
+       /* Return cached supported languages */
+       *languages = ( ( ( void * ) usbdev->languages ) +
+                      sizeof ( *(usbdev->languages) ) );
+       *len = usbdev->languages->len;
+
+       return 0;
+}
+
+/**
+ * Reset port
+ *
+ * @v usbio            USB I/O protocol
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_usb_port_reset ( EFI_USB_IO_PROTOCOL *usbio ) {
+       struct efi_usb_interface *usbintf =
+               container_of ( usbio, struct efi_usb_interface, usbio );
+       struct efi_usb_device *usbdev = usbintf->usbdev;
+
+       DBGC2 ( usbdev, "USBDEV %s reset port\n", usbintf->name );
+
+       /* This is logically impossible to do, since resetting the
+        * port may destroy state belonging to other
+        * EFI_USB_IO_PROTOCOL instances belonging to the same USB
+        * device.  (This is yet another artifact of the incredibly
+        * poor design of the EFI_USB_IO_PROTOCOL.)
+        */
+       return EFI_INVALID_PARAMETER;
+}
+
+/** USB I/O protocol */
+static EFI_USB_IO_PROTOCOL efi_usb_io_protocol = {
+       .UsbControlTransfer             = efi_usb_control_transfer,
+       .UsbBulkTransfer                = efi_usb_bulk_transfer,
+       .UsbAsyncInterruptTransfer      = efi_usb_async_interrupt_transfer,
+       .UsbSyncInterruptTransfer       = efi_usb_sync_interrupt_transfer,
+       .UsbIsochronousTransfer         = efi_usb_isochronous_transfer,
+       .UsbAsyncIsochronousTransfer    = efi_usb_async_isochronous_transfer,
+       .UsbGetDeviceDescriptor         = efi_usb_get_device_descriptor,
+       .UsbGetConfigDescriptor         = efi_usb_get_config_descriptor,
+       .UsbGetInterfaceDescriptor      = efi_usb_get_interface_descriptor,
+       .UsbGetEndpointDescriptor       = efi_usb_get_endpoint_descriptor,
+       .UsbGetStringDescriptor         = efi_usb_get_string_descriptor,
+       .UsbGetSupportedLanguages       = efi_usb_get_supported_languages,
+       .UsbPortReset                   = efi_usb_port_reset,
+};
+
+/******************************************************************************
+ *
+ * USB driver
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Install interface
+ *
+ * @v usbdev           EFI USB device
+ * @v interface                Interface number
+ * @ret rc             Return status code
+ */
+static int efi_usb_install ( struct efi_usb_device *usbdev,
+                            unsigned int interface ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_device *efidev = usbdev->efidev;
+       struct efi_usb_interface *usbintf;
+       struct usb_device *usb;
+       EFI_DEVICE_PATH_PROTOCOL *path_end;
+       USB_DEVICE_PATH *usbpath;
+       unsigned int path_count;
+       size_t path_prefix_len;
+       size_t path_len;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Calculate device path length */
+       path_count = ( usb_depth ( usbdev->usb ) + 1 );
+       path_prefix_len = efi_devpath_len ( efidev->path );
+       path_len = ( path_prefix_len + ( path_count * sizeof ( *usbpath ) ) +
+                    sizeof ( *path_end ) );
+
+       /* Allocate and initialise structure */
+       usbintf = zalloc ( sizeof ( *usbintf ) + path_len );
+       if ( ! usbintf ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       snprintf ( usbintf->name, sizeof ( usbintf->name ), "%s[%d]",
+                  usbdev->name, interface );
+       usbintf->usbdev = usbdev;
+       usbintf->interface = interface;
+       memcpy ( &usbintf->usbio, &efi_usb_io_protocol,
+                sizeof ( usbintf->usbio ) );
+       usbintf->path = ( ( ( void * ) usbintf ) + sizeof ( *usbintf ) );
+
+       /* Construct device path */
+       memcpy ( usbintf->path, efidev->path, path_prefix_len );
+       path_end = ( ( ( void * ) usbintf->path ) + path_len -
+                    sizeof ( *path_end ) );
+       path_end->Type = END_DEVICE_PATH_TYPE;
+       path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+       path_end->Length[0] = sizeof ( *path_end );
+       usbpath = ( ( ( void * ) path_end ) - sizeof ( *usbpath ) );
+       usbpath->InterfaceNumber = interface;
+       for ( usb = usbdev->usb ; usb ; usbpath--, usb = usb->port->hub->usb ) {
+               usbpath->Header.Type = MESSAGING_DEVICE_PATH;
+               usbpath->Header.SubType = MSG_USB_DP;
+               usbpath->Header.Length[0] = sizeof ( *usbpath );
+               usbpath->ParentPortNumber = usb->port->address;
+       }
+
+       /* Add to list of interfaces */
+       list_add_tail ( &usbintf->list, &usbdev->interfaces );
+
+       /* Install protocols */
+       if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+                       &usbintf->handle,
+                       &efi_usb_io_protocol_guid, &usbintf->usbio,
+                       &efi_device_path_protocol_guid, usbintf->path,
+                       NULL ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( usbdev, "USBDEV %s could not install protocols: %s\n",
+                      usbintf->name, strerror ( rc ) );
+               goto err_install_protocol;
+       }
+
+       DBGC ( usbdev, "USBDEV %s installed as %s\n",
+              usbintf->name, efi_handle_name ( usbintf->handle ) );
+       return 0;
+
+       efi_usb_close_all ( usbintf );
+       bs->UninstallMultipleProtocolInterfaces (
+                       usbintf->handle,
+                       &efi_usb_io_protocol_guid, &usbintf->usbio,
+                       &efi_device_path_protocol_guid, usbintf->path,
+                       NULL );
+ err_install_protocol:
+       list_del ( &usbintf->list );
+       free ( usbintf );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Uninstall interface
+ *
+ * @v usbintf          EFI USB interface
+ */
+static void efi_usb_uninstall ( struct efi_usb_interface *usbintf ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+
+       /* Close all endpoints */
+       efi_usb_close_all ( usbintf );
+
+       /* Uninstall protocols */
+       bs->UninstallMultipleProtocolInterfaces (
+                       usbintf->handle,
+                       &efi_usb_io_protocol_guid, &usbintf->usbio,
+                       &efi_device_path_protocol_guid, usbintf->path,
+                       NULL );
+
+       /* Remove from list of interfaces */
+       list_del ( &usbintf->list );
+
+       /* Free interface */
+       free ( usbintf );
+}
+
+/**
+ * Uninstall all interfaces
+ *
+ * @v usbdev           EFI USB device
+ */
+static void efi_usb_uninstall_all ( struct efi_usb_device *efiusb ) {
+       struct efi_usb_interface *usbintf;
+
+       /* Uninstall all interfaces */
+       while ( ( usbintf = list_first_entry ( &efiusb->interfaces,
+                                              struct efi_usb_interface,
+                                              list ) ) ) {
+               efi_usb_uninstall ( usbintf );
+       }
+}
+
+/**
+ * Probe device
+ *
+ * @v func             USB function
+ * @v config           Configuration descriptor
+ * @ret rc             Return status code
+ */
+static int efi_usb_probe ( struct usb_function *func,
+                          struct usb_configuration_descriptor *config ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct usb_device *usb = func->usb;
+       struct efi_usb_device *usbdev;
+       struct efi_usb_interface *usbintf;
+       struct efi_device *efidev;
+       struct usb_descriptor_header header;
+       size_t config_len;
+       unsigned int i;
+       int rc;
+
+       /* Find parent EFI device */
+       efidev = efidev_parent ( &func->dev );
+       if ( ! efidev ) {
+               rc = -ENOTTY;
+               goto err_no_efidev;
+       }
+
+       /* Get configuration length */
+       config_len = le16_to_cpu ( config->len );
+
+       /* Get supported languages descriptor header */
+       if ( ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, 0, 0,
+                                        &header, sizeof ( header ) ) ) != 0 ) {
+               /* Assume no strings are present */
+               header.len = 0;
+       }
+
+       /* Allocate and initialise structure */
+       usbdev = zalloc ( sizeof ( *usbdev ) + config_len + header.len );
+       if ( ! usbdev ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       usb_func_set_drvdata ( func, usbdev );
+       usbdev->name = func->name;
+       usbdev->usb = usb;
+       usbdev->efidev = efidev;
+       usbdev->config = ( ( ( void * ) usbdev ) + sizeof ( *usbdev ) );
+       memcpy ( usbdev->config, config, config_len );
+       usbdev->languages = ( ( ( void * ) usbdev->config ) + config_len );
+       INIT_LIST_HEAD ( &usbdev->interfaces );
+
+       /* Get supported languages descriptor */
+       if ( header.len &&
+            ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, 0, 0,
+                                        usbdev->languages,
+                                        header.len ) ) != 0 ) {
+               DBGC ( usbdev, "USBDEV %s could not get supported languages: "
+                      "%s\n", usbdev->name, strerror ( rc ) );
+               goto err_get_languages;
+       }
+
+       /* Install interfaces */
+       for ( i = 0 ; i < func->desc.count ; i++ ) {
+               if ( ( rc = efi_usb_install ( usbdev,
+                                             func->interface[i] ) ) != 0 )
+                       goto err_install;
+       }
+
+       /* Connect any external drivers */
+       list_for_each_entry ( usbintf, &usbdev->interfaces, list )
+               bs->ConnectController ( usbintf->handle, NULL, NULL, TRUE );
+
+       return 0;
+
+ err_install:
+       efi_usb_uninstall_all ( usbdev );
+       assert ( list_empty ( &usbdev->interfaces ) );
+ err_get_languages:
+       free ( usbdev );
+ err_alloc:
+ err_no_efidev:
+       return rc;
+}
+
+/**
+ * Remove device
+ *
+ * @v func             USB function
+ */
+static void efi_usb_remove ( struct usb_function *func ) {
+       struct efi_usb_device *usbdev = usb_func_get_drvdata ( func );
+
+       /* Uninstall all interfaces */
+       efi_usb_uninstall_all ( usbdev );
+       assert ( list_empty ( &usbdev->interfaces ) );
+
+       /* Free device */
+       free ( usbdev );
+}
+
+/** USB I/O protocol device IDs */
+static struct usb_device_id efi_usb_ids[] = {
+       {
+               .name = "usbio",
+               .vendor = USB_ANY_ID,
+               .product = USB_ANY_ID,
+       },
+};
+
+/** USB I/O protocol driver */
+struct usb_driver usbio_driver __usb_driver = {
+       .ids = efi_usb_ids,
+       .id_count = ( sizeof ( efi_usb_ids ) / sizeof ( efi_usb_ids[0] ) ),
+       .class = USB_CLASS_ID ( USB_ANY_ID, USB_ANY_ID, USB_ANY_ID ),
+       .score = USB_SCORE_FALLBACK,
+       .probe = efi_usb_probe,
+       .remove = efi_usb_remove,
+};