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