/*
 * 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->desc.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 */
	msg = iob_push ( iobuf, sizeof ( *msg ) );
	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 shutdown is in progress */
	if ( efi_shutdown_in_progress )
		return -ECANCELED;

	/* 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 ) {
	struct usb_setup_packet *setup;

	/* Adjust I/O buffer to start of data payload */
	assert ( iob_len ( iobuf ) >= sizeof ( *setup ) );
	iob_pull ( iobuf, sizeof ( *setup ) );

	/* Enqueue transfer */
	return usbio_endpoint_enqueue ( ep, iobuf, USBIO_MESSAGE );
}

/**
 * Enqueue stream transfer
 *
 * @v ep		USB endpoint
 * @v iobuf		I/O buffer
 * @v zlp		Append a zero-length packet
 * @ret rc		Return status code
 */
static int usbio_endpoint_stream ( struct usb_endpoint *ep,
				   struct io_buffer *iobuf, int zlp ) {

	/* Enqueue transfer */
	return usbio_endpoint_enqueue ( ep, iobuf, ( zlp ? USBIO_ZLEN : 0 ) );
}

/**
 * Poll for completions
 *
 * @v endpoint		Endpoint
 */
static void usbio_endpoint_poll ( struct usbio_endpoint *endpoint ) {

	/* Do nothing if shutdown is in progress */
	if ( efi_shutdown_in_progress )
		return;

	/* 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_function_descriptor desc;
	struct usb_driver *driver;
	struct usb_device_id *id;
	union {
		void *interface;
		EFI_USB_IO_PROTOCOL *io;
	} usb;
	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;
	}
	memset ( &desc, 0, sizeof ( desc ) );
	desc.vendor = device.IdVendor;
	desc.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;
	}
	desc.class.class.class = interface.InterfaceClass;
	desc.class.class.subclass = interface.InterfaceSubClass;
	desc.class.class.protocol = interface.InterfaceProtocol;

	/* Look for a driver for this interface */
	driver = usb_find_driver ( &desc, &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 */
	len = efi_devpath_len ( path );
	if ( len < sizeof ( *usbpath ) ) {
		DBGC ( usbio, "USBIO %s underlength device path\n",
		       efi_handle_name ( handle ) );
		rc = -EINVAL;
		goto err_underlength;
	}
	usbpath = ( ( ( void * ) path ) + len - 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,
};
