/* * 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->desc.count ; i++ ) { if ( interface->interface == func->interface[i] ) return interface->interface; } } } } DBGC ( usbio, "USBIO %s cannot find interface for %s", efi_handle_name ( handle ), usb_endpoint_name ( ep ) ); return -ENOENT; } /** * Open USB I/O interface * * @v usbio USB I/O device * @v interface Interface number * @ret rc Return status code */ static int usbio_open ( struct usbio_device *usbio, unsigned int interface ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE handle = usbio->handle; struct usbio_interface *intf = &usbio->interface[interface]; EFI_DEVICE_PATH_PROTOCOL *path; EFI_DEVICE_PATH_PROTOCOL *end; USB_DEVICE_PATH *usbpath; union { void *interface; EFI_USB_IO_PROTOCOL *io; } u; EFI_STATUS efirc; int rc; /* Sanity check */ assert ( interface < usbio->config->interfaces ); /* If interface is already open, just increment the usage count */ if ( intf->count ) { intf->count++; return 0; } /* Construct device path for this interface */ path = usbio->path; usbpath = usbio->usbpath; usbpath->InterfaceNumber = interface; end = efi_devpath_end ( path ); /* Locate handle for this endpoint's interface */ if ( ( efirc = bs->LocateDevicePath ( &efi_usb_io_protocol_guid, &path, &intf->handle ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s could not locate ", efi_handle_name ( handle ) ); DBGC ( usbio, "%s: %s\n", efi_devpath_text ( usbio->path ), strerror ( rc ) ); return rc; } /* Check that expected path was located */ if ( path != end ) { DBGC ( usbio, "USBIO %s located incomplete ", efi_handle_name ( handle ) ); DBGC ( usbio, "%s\n", efi_handle_name ( intf->handle ) ); return -EXDEV; } /* Open USB I/O protocol on this handle */ if ( ( efirc = bs->OpenProtocol ( intf->handle, &efi_usb_io_protocol_guid, &u.interface, efi_image_handle, intf->handle, ( EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s cannot open ", efi_handle_name ( handle ) ); DBGC ( usbio, "%s: %s\n", efi_handle_name ( intf->handle ), strerror ( rc ) ); DBGC_EFI_OPENERS ( usbio, intf->handle, &efi_usb_io_protocol_guid ); return rc; } intf->io = u.io; /* Increment usage count */ intf->count++; return 0; } /** * Close USB I/O interface * * @v usbio USB I/O device * @v interface Interface number */ static void usbio_close ( struct usbio_device *usbio, unsigned int interface ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct usbio_interface *intf = &usbio->interface[interface]; /* Sanity checks */ assert ( interface < usbio->config->interfaces ); assert ( intf->count > 0 ); /* Decrement usage count */ intf->count--; /* Do nothing if interface is still in use */ if ( intf->count ) return; /* Close USB I/O protocol */ bs->CloseProtocol ( intf->handle, &efi_usb_io_protocol_guid, efi_image_handle, intf->handle ); } /****************************************************************************** * * Control endpoints * ****************************************************************************** */ /** * Open control endpoint * * @v endpoint Endpoint * @ret rc Return status code */ static int usbio_control_open ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ return 0; } /** * Close control endpoint * * @v endpoint Endpoint */ static void usbio_control_close ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ } /** * Poll control endpoint * * @v endpoint Endpoint */ static void usbio_control_poll ( struct usbio_endpoint *endpoint ) { struct usbio_device *usbio = endpoint->usbio; struct usb_endpoint *ep = endpoint->ep; EFI_HANDLE handle = usbio->handle; EFI_USB_IO_PROTOCOL *io; union { struct usb_setup_packet setup; EFI_USB_DEVICE_REQUEST efi; } *msg; EFI_USB_DATA_DIRECTION direction; struct io_buffer *iobuf; unsigned int index; unsigned int flags; unsigned int recipient; unsigned int interface; uint16_t request; void *data; size_t len; UINT32 status; EFI_STATUS efirc; int rc; /* Do nothing if ring is empty */ if ( endpoint->cons == endpoint->prod ) return; /* Consume next transfer */ index = ( endpoint->cons++ % USBIO_RING_COUNT ); iobuf = endpoint->iobuf[index]; flags = endpoint->flags[index]; /* Sanity check */ if ( ! ( flags & USBIO_MESSAGE ) ) { DBGC ( usbio, "USBIO %s %s non-message transfer\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ) ); rc = -ENOTSUP; goto err_not_message; } /* Construct transfer */ msg = iob_push ( iobuf, sizeof ( *msg ) ); iob_pull ( iobuf, sizeof ( *msg ) ); request = le16_to_cpu ( msg->setup.request ); len = iob_len ( iobuf ); if ( len ) { data = iobuf->data; direction = ( ( request & USB_DIR_IN ) ? EfiUsbDataIn : EfiUsbDataOut ); } else { data = NULL; direction = EfiUsbNoData; } /* Determine interface for this transfer */ recipient = ( request & USB_RECIP_MASK ); if ( recipient == USB_RECIP_INTERFACE ) { /* Recipient is an interface: use interface number directly */ interface = le16_to_cpu ( msg->setup.index ); } else { /* Route all other requests through the first interface */ interface = 0; } /* Open interface */ if ( ( rc = usbio_open ( usbio, interface ) ) != 0 ) goto err_open; io = usbio->interface[interface].io; /* Due to the design of EFI_USB_IO_PROTOCOL, attempting to set * the configuration to a non-default value is basically a * self-destruct button. */ if ( ( request == USB_SET_CONFIGURATION ) && ( le16_to_cpu ( msg->setup.value ) != usbio->config->config ) ) { rc = -ENOTSUP_MORONIC_SPECIFICATION; DBGC ( usbio, "USBIO %s cannot change configuration: %s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_moronic_specification; } /* Submit transfer */ if ( ( efirc = io->UsbControlTransfer ( io, &msg->efi, direction, 0, data, len, &status ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s %s could not submit control transfer ", efi_handle_name ( handle ), usb_endpoint_name ( ep ) ); DBGC ( usbio, "via %s: %s (status %04x)\n", efi_handle_name ( usbio->interface[interface].handle ), strerror ( rc ), status ); goto err_transfer; } /* Close interface */ usbio_close ( usbio, interface ); /* Complete transfer */ usb_complete ( ep, iobuf ); return; err_transfer: err_moronic_specification: usbio_close ( usbio, interface ); err_open: err_not_message: usb_complete_err ( ep, iobuf, rc ); } /** Control endpoint operations */ static struct usbio_operations usbio_control_operations = { .open = usbio_control_open, .close = usbio_control_close, .poll = usbio_control_poll, }; /****************************************************************************** * * Bulk IN endpoints * ****************************************************************************** */ /** * Open bulk IN endpoint * * @v endpoint Endpoint * @ret rc Return status code */ static int usbio_bulk_in_open ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ return 0; } /** * Close bulk IN endpoint * * @v endpoint Endpoint */ static void usbio_bulk_in_close ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ } /** * Poll bulk IN endpoint * * @v endpoint Endpoint */ static void usbio_bulk_in_poll ( struct usbio_endpoint *endpoint ) { struct usbio_device *usbio = endpoint->usbio; struct usb_endpoint *ep = endpoint->ep; EFI_USB_IO_PROTOCOL *io = endpoint->io; EFI_HANDLE handle = usbio->handle; struct io_buffer *iobuf; unsigned int index; UINTN len; UINT32 status; EFI_STATUS efirc; int rc; /* Do nothing if ring is empty */ if ( endpoint->cons == endpoint->prod ) return; /* Attempt (but do not yet consume) next transfer */ index = ( endpoint->cons % USBIO_RING_COUNT ); iobuf = endpoint->iobuf[index]; /* Construct transfer */ len = iob_len ( iobuf ); /* Upon being turned on, the EFI_USB_IO_PROTOCOL did nothing * for several minutes before firing a small ARP packet a few * millimetres into the ether. */ efirc = io->UsbBulkTransfer ( io, ep->address, iobuf->data, &len, 1, &status ); if ( efirc == EFI_TIMEOUT ) return; /* Consume transfer */ endpoint->cons++; /* Check for failure */ if ( efirc != 0 ) { rc = -EEFI ( efirc ); DBGC2 ( usbio, "USBIO %s %s could not submit bulk IN transfer: " "%s (status %04x)\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ), strerror ( rc ), status ); usb_complete_err ( ep, iobuf, rc ); return; } /* Update length */ iob_put ( iobuf, ( len - iob_len ( iobuf ) ) ); /* Complete transfer */ usb_complete ( ep, iobuf ); } /** Bulk endpoint operations */ static struct usbio_operations usbio_bulk_in_operations = { .open = usbio_bulk_in_open, .close = usbio_bulk_in_close, .poll = usbio_bulk_in_poll, }; /****************************************************************************** * * Bulk OUT endpoints * ****************************************************************************** */ /** * Open bulk OUT endpoint * * @v endpoint Endpoint * @ret rc Return status code */ static int usbio_bulk_out_open ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ return 0; } /** * Close bulk OUT endpoint * * @v endpoint Endpoint */ static void usbio_bulk_out_close ( struct usbio_endpoint *endpoint __unused ) { /* Nothing to do */ } /** * Poll bulk OUT endpoint * * @v endpoint Endpoint */ static void usbio_bulk_out_poll ( struct usbio_endpoint *endpoint ) { struct usbio_device *usbio = endpoint->usbio; struct usb_endpoint *ep = endpoint->ep; EFI_USB_IO_PROTOCOL *io = endpoint->io; EFI_HANDLE handle = usbio->handle; struct io_buffer *iobuf; unsigned int index; unsigned int flags; UINTN len; UINT32 status; EFI_STATUS efirc; int rc; /* Do nothing if ring is empty */ if ( endpoint->cons == endpoint->prod ) return; /* Consume next transfer */ index = ( endpoint->cons++ % USBIO_RING_COUNT ); iobuf = endpoint->iobuf[index]; flags = endpoint->flags[index]; /* Construct transfer */ len = iob_len ( iobuf ); /* Submit transfer */ if ( ( efirc = io->UsbBulkTransfer ( io, ep->address, iobuf->data, &len, 0, &status ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s %s could not submit bulk OUT transfer: " "%s (status %04x)\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ), strerror ( rc ), status ); goto err; } /* Update length */ iob_put ( iobuf, ( len - iob_len ( iobuf ) ) ); /* Submit zero-length transfer if required */ len = 0; if ( ( flags & USBIO_ZLEN ) && ( efirc = io->UsbBulkTransfer ( io, ep->address, NULL, &len, 0, &status ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s %s could not submit zero-length " "transfer: %s (status %04x)\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ), strerror ( rc ), status ); goto err; } /* Complete transfer */ usb_complete ( ep, iobuf ); return; err: usb_complete_err ( ep, iobuf, rc ); } /** Bulk endpoint operations */ static struct usbio_operations usbio_bulk_out_operations = { .open = usbio_bulk_out_open, .close = usbio_bulk_out_close, .poll = usbio_bulk_out_poll, }; /****************************************************************************** * * Interrupt endpoints * ****************************************************************************** * * The EFI_USB_IO_PROTOCOL provides two ways to interact with * interrupt endpoints, neither of which naturally model the hardware * interaction. The UsbSyncInterruptTransfer() method allows imposes * a 1ms overhead for every interrupt transfer (which could result in * up to a 50% decrease in overall throughput for the device). The * UsbAsyncInterruptTransfer() method provides no way for us to * prevent transfers when no I/O buffers are available. * * We work around this design by utilising a small, fixed ring buffer * into which the interrupt callback delivers the data. This aims to * provide buffer space even if no I/O buffers have yet been enqueued. * The scheme is not guaranteed since the fixed ring buffer may also * become full. However: * * - devices which send a constant stream of interrupts (and which * therefore might exhaust the fixed ring buffer) tend to be * responding to every interrupt request, and can tolerate lost * packets, and * * - devices which cannot tolerate lost interrupt packets tend to send * only a few small messages. * * The scheme should therefore work in practice. */ /** * Interrupt endpoint callback * * @v data Received data * @v len Length of received data * @v context Callback context * @v status Transfer status * @ret efirc EFI status code */ static EFI_STATUS EFIAPI usbio_interrupt_callback ( VOID *data, UINTN len, VOID *context, UINT32 status ) { struct usbio_interrupt_ring *intr = context; struct usbio_endpoint *endpoint = intr->endpoint; struct usbio_device *usbio = endpoint->usbio; struct usb_endpoint *ep = endpoint->ep; EFI_HANDLE handle = usbio->handle; unsigned int fill; unsigned int index; /* Sanity check */ assert ( len <= ep->mtu ); /* Do nothing if ring is empty */ fill = ( intr->prod - intr->cons ); if ( fill >= USBIO_INTR_COUNT ) { DBGC ( usbio, "USBIO %s %s dropped interrupt completion\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ) ); return 0; } /* Do nothing if transfer was unsuccessful */ if ( status != 0 ) { DBGC ( usbio, "USBIO %s %s interrupt completion status %04x\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ), status ); return 0; /* Unclear what failure actually means here */ } /* Copy data to buffer and increment producer counter */ index = ( intr->prod % USBIO_INTR_COUNT ); memcpy ( intr->data[index], data, len ); intr->len[index] = len; intr->prod++; return 0; } /** * Open interrupt endpoint * * @v endpoint Endpoint * @ret rc Return status code */ static int usbio_interrupt_open ( struct usbio_endpoint *endpoint ) { struct usbio_device *usbio = endpoint->usbio; struct usbio_interrupt_ring *intr; struct usb_endpoint *ep = endpoint->ep; EFI_USB_IO_PROTOCOL *io = endpoint->io; EFI_HANDLE handle = usbio->handle; unsigned int interval; unsigned int i; void *data; EFI_STATUS efirc; int rc; /* Allocate interrupt ring buffer */ intr = zalloc ( sizeof ( *intr ) + ( USBIO_INTR_COUNT * ep->mtu ) ); if ( ! intr ) { rc = -ENOMEM; goto err_alloc; } endpoint->intr = intr; intr->endpoint = endpoint; data = ( ( ( void * ) intr ) + sizeof ( *intr ) ); for ( i = 0 ; i < USBIO_INTR_COUNT ; i++ ) { intr->data[i] = data; data += ep->mtu; } /* Determine polling interval */ interval = ( ep->interval >> 3 /* microframes -> milliseconds */ ); if ( ! interval ) interval = 1; /* May not be zero */ /* Add to periodic schedule */ if ( ( efirc = io->UsbAsyncInterruptTransfer ( io, ep->address, TRUE, interval, ep->mtu, usbio_interrupt_callback, intr ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s %s could not schedule interrupt " "transfer: %s\n", efi_handle_name ( handle ), usb_endpoint_name ( ep ), strerror ( rc ) ); goto err_schedule; } return 0; io->UsbAsyncInterruptTransfer ( io, ep->address, FALSE, 0, 0, NULL, NULL ); err_schedule: free ( intr ); err_alloc: return rc; } /** * Close interrupt endpoint * * @v endpoint Endpoint */ static void usbio_interrupt_close ( struct usbio_endpoint *endpoint ) { struct usb_endpoint *ep = endpoint->ep; EFI_USB_IO_PROTOCOL *io = endpoint->io; /* Remove from periodic schedule */ io->UsbAsyncInterruptTransfer ( io, ep->address, FALSE, 0, 0, NULL, NULL ); /* Free interrupt ring buffer */ free ( endpoint->intr ); } /** * Poll interrupt endpoint * * @v endpoint Endpoint */ static void usbio_interrupt_poll ( struct usbio_endpoint *endpoint ) { struct usbio_interrupt_ring *intr = endpoint->intr; struct usb_endpoint *ep = endpoint->ep; struct io_buffer *iobuf; unsigned int index; unsigned int intr_index; size_t len; /* Do nothing if ring is empty */ if ( endpoint->cons == endpoint->prod ) return; /* Do nothing if interrupt ring is empty */ if ( intr->cons == intr->prod ) return; /* Consume next transfer */ index = ( endpoint->cons++ % USBIO_RING_COUNT ); iobuf = endpoint->iobuf[index]; /* Populate I/O buffer */ intr_index = ( intr->cons++ % USBIO_INTR_COUNT ); len = intr->len[intr_index]; assert ( len <= iob_len ( iobuf ) ); iob_put ( iobuf, ( len - iob_len ( iobuf ) ) ); memcpy ( iobuf->data, intr->data[intr_index], len ); /* Complete transfer */ usb_complete ( ep, iobuf ); } /** Interrupt endpoint operations */ static struct usbio_operations usbio_interrupt_operations = { .open = usbio_interrupt_open, .close = usbio_interrupt_close, .poll = usbio_interrupt_poll, }; /****************************************************************************** * * Endpoint operations * ****************************************************************************** */ /** * Open endpoint * * @v ep USB endpoint * @ret rc Return status code */ static int usbio_endpoint_open ( struct usb_endpoint *ep ) { struct usb_bus *bus = ep->usb->port->hub->bus; struct usbio_device *usbio = usb_bus_get_hostdata ( bus ); struct usbio_endpoint *endpoint; EFI_HANDLE handle = usbio->handle; unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); int interface; int rc; /* Allocate and initialise structure */ endpoint = zalloc ( sizeof ( *endpoint ) ); if ( ! endpoint ) { rc = -ENOMEM; goto err_alloc; } usb_endpoint_set_hostdata ( ep, endpoint ); endpoint->usbio = usbio; endpoint->ep = ep; /* Identify endpoint operations */ if ( attr == USB_ENDPOINT_ATTR_CONTROL ) { endpoint->op = &usbio_control_operations; } else if ( attr == USB_ENDPOINT_ATTR_BULK ) { endpoint->op = ( ( ep->address & USB_DIR_IN ) ? &usbio_bulk_in_operations : &usbio_bulk_out_operations ); } else if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { endpoint->op = &usbio_interrupt_operations; } else { rc = -ENOTSUP; goto err_operations; } /* Identify interface for this endpoint */ interface = usbio_interface ( usbio, ep ); if ( interface < 0 ) { rc = interface; goto err_interface; } endpoint->interface = interface; /* Open interface */ if ( ( rc = usbio_open ( usbio, interface ) ) != 0 ) goto err_open_interface; endpoint->handle = usbio->interface[interface].handle; endpoint->io = usbio->interface[interface].io; DBGC ( usbio, "USBIO %s %s using ", efi_handle_name ( handle ), usb_endpoint_name ( ep ) ); DBGC ( usbio, "%s\n", efi_handle_name ( endpoint->handle ) ); /* Open endpoint */ if ( ( rc = endpoint->op->open ( endpoint ) ) != 0 ) goto err_open_endpoint; /* Add to list of endpoints */ list_add_tail ( &endpoint->list, &usbio->endpoints ); return 0; list_del ( &endpoint->list ); endpoint->op->close ( endpoint ); err_open_endpoint: usbio_close ( usbio, interface ); err_open_interface: err_interface: err_operations: free ( endpoint ); err_alloc: return rc; } /** * Close endpoint * * @v ep USB endpoint */ static void usbio_endpoint_close ( struct usb_endpoint *ep ) { struct usbio_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); struct usbio_device *usbio = endpoint->usbio; struct io_buffer *iobuf; unsigned int index; /* Remove from list of endpoints */ list_del ( &endpoint->list ); /* Close endpoint */ endpoint->op->close ( endpoint ); /* Close interface */ usbio_close ( usbio, endpoint->interface ); /* Cancel any incomplete transfers */ while ( endpoint->cons != endpoint->prod ) { index = ( endpoint->cons++ % USBIO_RING_COUNT ); iobuf = endpoint->iobuf[index]; usb_complete_err ( ep, iobuf, -ECANCELED ); } /* Free endpoint */ free ( endpoint ); } /** * Reset endpoint * * @v ep USB endpoint * @ret rc Return status code */ static int usbio_endpoint_reset ( struct usb_endpoint *ep __unused ) { /* Nothing to do */ return 0; } /** * Update MTU * * @v ep USB endpoint * @ret rc Return status code */ static int usbio_endpoint_mtu ( struct usb_endpoint *ep __unused ) { /* Nothing to do */ return 0; } /** * Enqueue transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v flags Transfer flags * @ret rc Return status code */ static int usbio_endpoint_enqueue ( struct usb_endpoint *ep, struct io_buffer *iobuf, unsigned int flags ) { struct usbio_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); unsigned int fill; unsigned int index; /* Fail if transfer ring is full */ fill = ( endpoint->prod - endpoint->cons ); if ( fill >= USBIO_RING_COUNT ) return -ENOBUFS; /* Add to ring */ index = ( endpoint->prod++ % USBIO_RING_COUNT ); endpoint->iobuf[index] = iobuf; endpoint->flags[index] = flags; return 0; } /** * Enqueue message transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @ret rc Return status code */ static int usbio_endpoint_message ( struct usb_endpoint *ep, struct io_buffer *iobuf ) { struct usb_setup_packet *setup; /* Adjust I/O buffer to start of data payload */ assert ( iob_len ( iobuf ) >= sizeof ( *setup ) ); iob_pull ( iobuf, sizeof ( *setup ) ); /* Enqueue transfer */ return usbio_endpoint_enqueue ( ep, iobuf, USBIO_MESSAGE ); } /** * Enqueue stream transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v zlp Append a zero-length packet * @ret rc Return status code */ static int usbio_endpoint_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf, int zlp ) { /* Enqueue transfer */ return usbio_endpoint_enqueue ( ep, iobuf, ( zlp ? USBIO_ZLEN : 0 ) ); } /** * Poll for completions * * @v endpoint Endpoint */ static void usbio_endpoint_poll ( struct usbio_endpoint *endpoint ) { /* Poll endpoint */ endpoint->op->poll ( endpoint ); } /****************************************************************************** * * Device operations * ****************************************************************************** */ /** * Open device * * @v usb USB device * @ret rc Return status code */ static int usbio_device_open ( struct usb_device *usb ) { struct usbio_device *usbio = usb_bus_get_hostdata ( usb->port->hub->bus ); usb_set_hostdata ( usb, usbio ); return 0; } /** * Close device * * @v usb USB device */ static void usbio_device_close ( struct usb_device *usb __unused ) { /* Nothing to do */ } /** * Assign device address * * @v usb USB device * @ret rc Return status code */ static int usbio_device_address ( struct usb_device *usb __unused ) { /* Nothing to do */ return 0; } /****************************************************************************** * * Hub operations * ****************************************************************************** */ /** * Open hub * * @v hub USB hub * @ret rc Return status code */ static int usbio_hub_open ( struct usb_hub *hub ) { /* Disallow non-root hubs */ if ( hub->usb ) return -ENOTSUP; /* Nothing to do */ return 0; } /** * Close hub * * @v hub USB hub */ static void usbio_hub_close ( struct usb_hub *hub __unused ) { /* Nothing to do */ } /****************************************************************************** * * Root hub operations * ****************************************************************************** */ /** * Open root hub * * @v hub USB hub * @ret rc Return status code */ static int usbio_root_open ( struct usb_hub *hub __unused ) { /* Nothing to do */ return 0; } /** * Close root hub * * @v hub USB hub */ static void usbio_root_close ( struct usb_hub *hub __unused ) { /* Nothing to do */ } /** * Enable port * * @v hub USB hub * @v port USB port * @ret rc Return status code */ static int usbio_root_enable ( struct usb_hub *hub __unused, struct usb_port *port __unused ) { /* Nothing to do */ return 0; } /** * Disable port * * @v hub USB hub * @v port USB port * @ret rc Return status code */ static int usbio_root_disable ( struct usb_hub *hub __unused, struct usb_port *port __unused ) { /* Nothing to do */ return 0; } /** * Update root hub port speed * * @v hub USB hub * @v port USB port * @ret rc Return status code */ static int usbio_root_speed ( struct usb_hub *hub __unused, struct usb_port *port ) { /* Not actually exposed via EFI_USB_IO_PROTOCOL */ port->speed = USB_SPEED_HIGH; return 0; } /** * Clear transaction translator buffer * * @v hub USB hub * @v port USB port * @v ep USB endpoint * @ret rc Return status code */ static int usbio_root_clear_tt ( struct usb_hub *hub __unused, struct usb_port *port __unused, struct usb_endpoint *ep __unused ) { /* Should never be called; this is a root hub */ return -ENOTSUP; } /****************************************************************************** * * Bus operations * ****************************************************************************** */ /** * Open USB bus * * @v bus USB bus * @ret rc Return status code */ static int usbio_bus_open ( struct usb_bus *bus __unused ) { /* Nothing to do */ return 0; } /** * Close USB bus * * @v bus USB bus */ static void usbio_bus_close ( struct usb_bus *bus __unused ) { /* Nothing to do */ } /** * Poll USB bus * * @v bus USB bus */ static void usbio_bus_poll ( struct usb_bus *bus ) { struct usbio_device *usbio = usb_bus_get_hostdata ( bus ); struct usbio_endpoint *endpoint; /* Poll all endpoints. We trust that completion handlers are * minimal and will not do anything that could plausibly * affect the endpoint list itself. */ list_for_each_entry ( endpoint, &usbio->endpoints, list ) usbio_endpoint_poll ( endpoint ); } /****************************************************************************** * * EFI driver interface * ****************************************************************************** */ /** USB I/O host controller driver operations */ static struct usb_host_operations usbio_operations = { .endpoint = { .open = usbio_endpoint_open, .close = usbio_endpoint_close, .reset = usbio_endpoint_reset, .mtu = usbio_endpoint_mtu, .message = usbio_endpoint_message, .stream = usbio_endpoint_stream, }, .device = { .open = usbio_device_open, .close = usbio_device_close, .address = usbio_device_address, }, .bus = { .open = usbio_bus_open, .close = usbio_bus_close, .poll = usbio_bus_poll, }, .hub = { .open = usbio_hub_open, .close = usbio_hub_close, }, .root = { .open = usbio_root_open, .close = usbio_root_close, .enable = usbio_root_enable, .disable = usbio_root_disable, .speed = usbio_root_speed, .clear_tt = usbio_root_clear_tt, }, }; /** * Check to see if driver supports a device * * @v handle EFI device handle * @ret rc Return status code */ static int usbio_supported ( EFI_HANDLE handle ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_USB_DEVICE_DESCRIPTOR device; EFI_USB_INTERFACE_DESCRIPTOR interface; struct usb_function_descriptor desc; struct usb_driver *driver; struct usb_device_id *id; union { void *interface; EFI_USB_IO_PROTOCOL *io; } usb; EFI_STATUS efirc; int rc; /* Get protocol */ if ( ( efirc = bs->OpenProtocol ( handle, &efi_usb_io_protocol_guid, &usb.interface, efi_image_handle, handle, EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ rc = -EEFI ( efirc ); DBGCP ( handle, "USB %s is not a USB device\n", efi_handle_name ( handle ) ); goto err_open_protocol; } /* Get device descriptor */ if ( ( efirc = usb.io->UsbGetDeviceDescriptor ( usb.io, &device ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( handle, "USB %s could not get device descriptor: " "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_get_device_descriptor; } memset ( &desc, 0, sizeof ( desc ) ); desc.vendor = device.IdVendor; desc.product = device.IdProduct; /* Get interface descriptor */ if ( ( efirc = usb.io->UsbGetInterfaceDescriptor ( usb.io, &interface ) ) !=0){ rc = -EEFI ( efirc ); DBGC ( handle, "USB %s could not get interface descriptor: " "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_get_interface_descriptor; } desc.class.class.class = interface.InterfaceClass; desc.class.class.subclass = interface.InterfaceSubClass; desc.class.class.protocol = interface.InterfaceProtocol; /* Look for a driver for this interface */ driver = usb_find_driver ( &desc, &id ); if ( ! driver ) { rc = -ENOTSUP; goto err_unsupported; } /* Success */ rc = 0; err_unsupported: err_get_interface_descriptor: err_get_device_descriptor: bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid, efi_image_handle, handle ); err_open_protocol: return rc; } /** * Fetch configuration descriptor * * @v usbio USB I/O device * @ret rc Return status code */ static int usbio_config ( struct usbio_device *usbio ) { EFI_HANDLE handle = usbio->handle; EFI_USB_IO_PROTOCOL *io = usbio->io; EFI_USB_DEVICE_DESCRIPTOR device; EFI_USB_CONFIG_DESCRIPTOR partial; union { struct usb_setup_packet setup; EFI_USB_DEVICE_REQUEST efi; } msg; UINT32 status; size_t len; unsigned int count; unsigned int value; unsigned int i; EFI_STATUS efirc; int rc; /* Get device descriptor */ if ( ( efirc = io->UsbGetDeviceDescriptor ( io, &device ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USB %s could not get device descriptor: " "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_get_device_descriptor; } count = device.NumConfigurations; /* Get current partial configuration descriptor */ if ( ( efirc = io->UsbGetConfigDescriptor ( io, &partial ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USB %s could not get partial configuration " "descriptor: %s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_get_configuration_descriptor; } len = le16_to_cpu ( partial.TotalLength ); /* Allocate configuration descriptor */ usbio->config = malloc ( len ); if ( ! usbio->config ) { rc = -ENOMEM; goto err_alloc; } /* There is, naturally, no way to retrieve the entire device * configuration descriptor via EFI_USB_IO_PROTOCOL. Worse, * there is no way to even retrieve the index of the current * configuration descriptor. We have to iterate over all * possible configuration descriptors looking for the * descriptor that matches the current configuration value. */ for ( i = 0 ; i < count ; i++ ) { /* Construct request */ msg.setup.request = cpu_to_le16 ( USB_GET_DESCRIPTOR ); value = ( ( USB_CONFIGURATION_DESCRIPTOR << 8 ) | i ); msg.setup.value = cpu_to_le16 ( value ); msg.setup.index = 0; msg.setup.len = cpu_to_le16 ( len ); /* Get full configuration descriptor */ if ( ( efirc = io->UsbControlTransfer ( io, &msg.efi, EfiUsbDataIn, 0, usbio->config, len, &status ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( usbio, "USB %s could not get configuration %d " "descriptor: %s\n", efi_handle_name ( handle ), i, strerror ( rc ) ); goto err_control_transfer; } /* Ignore unless this is the current configuration */ if ( usbio->config->config != partial.ConfigurationValue ) continue; /* Check length */ if ( le16_to_cpu ( usbio->config->len ) != len ) { DBGC ( usbio, "USB %s configuration descriptor length " "mismatch\n", efi_handle_name ( handle ) ); rc = -EINVAL; goto err_len; } return 0; } /* No match found */ DBGC ( usbio, "USB %s could not find current configuration " "descriptor\n", efi_handle_name ( handle ) ); rc = -ENOENT; err_len: err_control_transfer: free ( usbio->config ); err_alloc: err_get_configuration_descriptor: err_get_device_descriptor: return rc; } /** * Construct device path for opening other interfaces * * @v usbio USB I/O device * @ret rc Return status code */ static int usbio_path ( struct usbio_device *usbio ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE handle = usbio->handle; EFI_DEVICE_PATH_PROTOCOL *path; EFI_DEVICE_PATH_PROTOCOL *end; USB_DEVICE_PATH *usbpath; union { void *interface; EFI_DEVICE_PATH_PROTOCOL *path; } u; size_t len; EFI_STATUS efirc; int rc; /* Open device path protocol */ if ( ( efirc = bs->OpenProtocol ( handle, &efi_device_path_protocol_guid, &u.interface, efi_image_handle, handle, EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s cannot open device path protocol: " "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_open_protocol; } path = u.interface; /* Locate end of device path and sanity check */ len = efi_devpath_len ( path ); if ( len < sizeof ( *usbpath ) ) { DBGC ( usbio, "USBIO %s underlength device path\n", efi_handle_name ( handle ) ); rc = -EINVAL; goto err_underlength; } usbpath = ( ( ( void * ) path ) + len - sizeof ( *usbpath ) ); if ( ! ( ( usbpath->Header.Type == MESSAGING_DEVICE_PATH ) && ( usbpath->Header.SubType == MSG_USB_DP ) ) ) { DBGC ( usbio, "USBIO %s not a USB device path: ", efi_handle_name ( handle ) ); DBGC ( usbio, "%s\n", efi_devpath_text ( path ) ); rc = -EINVAL; goto err_non_usb; } /* Allocate copy of device path */ usbio->path = malloc ( len + sizeof ( *end ) ); if ( ! usbio->path ) { rc = -ENOMEM; goto err_alloc; } memcpy ( usbio->path, path, ( len + sizeof ( *end ) ) ); usbio->usbpath = ( ( ( void * ) usbio->path ) + len - sizeof ( *usbpath ) ); /* Close protocol */ bs->CloseProtocol ( handle, &efi_device_path_protocol_guid, efi_image_handle, handle ); return 0; free ( usbio->path ); err_alloc: err_non_usb: err_underlength: bs->CloseProtocol ( handle, &efi_device_path_protocol_guid, efi_image_handle, handle ); err_open_protocol: return rc; } /** * Construct interface list * * @v usbio USB I/O device * @ret rc Return status code */ static int usbio_interfaces ( struct usbio_device *usbio ) { EFI_HANDLE handle = usbio->handle; EFI_USB_IO_PROTOCOL *io = usbio->io; EFI_USB_INTERFACE_DESCRIPTOR interface; unsigned int first; unsigned int count; EFI_STATUS efirc; int rc; /* Get interface descriptor */ if ( ( efirc = io->UsbGetInterfaceDescriptor ( io, &interface ) ) != 0){ rc = -EEFI ( efirc ); DBGC ( usbio, "USB %s could not get interface descriptor: " "%s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_get_interface_descriptor; } /* Record first interface number */ first = interface.InterfaceNumber; count = usbio->config->interfaces; assert ( first < count ); usbio->first = first; /* Allocate interface list */ usbio->interface = zalloc ( count * sizeof ( usbio->interface[0] ) ); if ( ! usbio->interface ) { rc = -ENOMEM; goto err_alloc; } /* Use already-opened protocol for control transfers and for * the first interface. */ usbio->interface[0].handle = handle; usbio->interface[0].io = io; usbio->interface[0].count = 1; usbio->interface[first].handle = handle; usbio->interface[first].io = io; usbio->interface[first].count = 1; return 0; free ( usbio->interface ); err_alloc: err_get_interface_descriptor: return rc; } /** * Attach driver to device * * @v efidev EFI device * @ret rc Return status code */ static int usbio_start ( struct efi_device *efidev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE handle = efidev->device; struct usbio_device *usbio; struct usb_port *port; union { void *interface; EFI_USB_IO_PROTOCOL *io; } u; EFI_STATUS efirc; int rc; /* Allocate and initialise structure */ usbio = zalloc ( sizeof ( *usbio ) ); if ( ! usbio ) { rc = -ENOMEM; goto err_alloc; } efidev_set_drvdata ( efidev, usbio ); usbio->handle = handle; INIT_LIST_HEAD ( &usbio->endpoints ); /* Open USB I/O protocol */ if ( ( efirc = bs->OpenProtocol ( handle, &efi_usb_io_protocol_guid, &u.interface, efi_image_handle, handle, ( EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ rc = -EEFI ( efirc ); DBGC ( usbio, "USBIO %s cannot open USB I/O protocol: %s\n", efi_handle_name ( handle ), strerror ( rc ) ); DBGC_EFI_OPENERS ( usbio, handle, &efi_usb_io_protocol_guid ); goto err_open_usbio; } usbio->io = u.io; /* Describe generic device */ efi_device_info ( handle, "USB", &usbio->dev ); usbio->dev.parent = &efidev->dev; list_add ( &usbio->dev.siblings, &efidev->dev.children ); INIT_LIST_HEAD ( &usbio->dev.children ); /* Fetch configuration descriptor */ if ( ( rc = usbio_config ( usbio ) ) != 0 ) goto err_config; /* Construct device path */ if ( ( rc = usbio_path ( usbio ) ) != 0 ) goto err_path; /* Construct interface list */ if ( ( rc = usbio_interfaces ( usbio ) ) != 0 ) goto err_interfaces; /* Allocate USB bus */ usbio->bus = alloc_usb_bus ( &usbio->dev, 1 /* single "port" */, USBIO_MTU, &usbio_operations ); if ( ! usbio->bus ) { rc = -ENOMEM; goto err_alloc_bus; } usb_bus_set_hostdata ( usbio->bus, usbio ); usb_hub_set_drvdata ( usbio->bus->hub, usbio ); /* Set port protocol */ port = usb_port ( usbio->bus->hub, 1 ); port->protocol = USB_PROTO_2_0; /* Register USB bus */ if ( ( rc = register_usb_bus ( usbio->bus ) ) != 0 ) goto err_register; return 0; unregister_usb_bus ( usbio->bus ); err_register: free_usb_bus ( usbio->bus ); err_alloc_bus: free ( usbio->interface ); err_interfaces: free ( usbio->path ); err_path: free ( usbio->config ); err_config: list_del ( &usbio->dev.siblings ); bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid, efi_image_handle, handle ); err_open_usbio: free ( usbio ); err_alloc: return rc; } /** * Detach driver from device * * @v efidev EFI device */ static void usbio_stop ( struct efi_device *efidev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE handle = efidev->device; struct usbio_device *usbio = efidev_get_drvdata ( efidev ); unregister_usb_bus ( usbio->bus ); free_usb_bus ( usbio->bus ); free ( usbio->interface ); free ( usbio->path ); free ( usbio->config ); list_del ( &usbio->dev.siblings ); bs->CloseProtocol ( handle, &efi_usb_io_protocol_guid, efi_image_handle, handle ); free ( usbio ); } /** EFI USB I/O driver */ struct efi_driver usbio_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { .name = "USBIO", .supported = usbio_supported, .start = usbio_start, .stop = usbio_stop, };