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