diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Devices/PC/ipxe/src/interface/efi/efi_usb.c | 1365 |
1 files changed, 1365 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_usb.c b/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_usb.c new file mode 100644 index 00000000..df66df45 --- /dev/null +++ b/src/VBox/Devices/PC/ipxe/src/interface/efi/efi_usb.c @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_path.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_null.h> +#include <ipxe/efi/efi_usb.h> +#include <ipxe/usb.h> + +/** @file + * + * EFI USB I/O PROTOCOL + * + */ + +/** + * Transcribe data direction (for debugging) + * + * @v direction Data direction + * @ret text Transcribed data direction + */ +static const char * efi_usb_direction_name ( EFI_USB_DATA_DIRECTION direction ){ + + switch ( direction ) { + case EfiUsbDataIn: return "in"; + case EfiUsbDataOut: return "out"; + case EfiUsbNoData: return "none"; + default: return "<UNKNOWN>"; + } +} + +/****************************************************************************** + * + * Endpoints + * + ****************************************************************************** + */ + +/** + * Poll USB bus (from endpoint event timer) + * + * @v event EFI event + * @v context EFI USB endpoint + */ +static VOID EFIAPI efi_usb_timer ( EFI_EVENT event __unused, + VOID *context ) { + struct efi_usb_endpoint *usbep = context; + struct usb_function *func = usbep->usbintf->usbdev->func; + + /* Poll bus */ + usb_poll ( func->usb->port->hub->bus ); + + /* Refill endpoint */ + if ( usbep->ep.open ) + usb_refill ( &usbep->ep ); +} + +/** + * Get endpoint MTU + * + * @v usbintf EFI USB interface + * @v endpoint Endpoint address + * @ret mtu Endpoint MTU, or negative error + */ +static int efi_usb_mtu ( struct efi_usb_interface *usbintf, + unsigned int endpoint ) { + struct efi_usb_device *usbdev = usbintf->usbdev; + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *desc; + + /* Locate cached interface descriptor */ + interface = usb_interface_descriptor ( usbdev->config, + usbintf->interface, + usbintf->alternate ); + if ( ! interface ) { + DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n", + usbintf->name, usbintf->alternate ); + return -ENOENT; + } + + /* Locate and copy cached endpoint descriptor */ + for_each_interface_descriptor ( desc, usbdev->config, interface ) { + if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) && + ( desc->endpoint == endpoint ) ) + return USB_ENDPOINT_MTU ( le16_to_cpu ( desc->sizes ) ); + } + + DBGC ( usbdev, "USBDEV %s alt %d ep %02x has no descriptor\n", + usbintf->name, usbintf->alternate, endpoint ); + return -ENOENT; +} + +/** + * Check if endpoint is open + * + * @v usbintf EFI USB interface + * @v endpoint Endpoint address + * @ret is_open Endpoint is open + */ +static int efi_usb_is_open ( struct efi_usb_interface *usbintf, + unsigned int endpoint ) { + unsigned int index = USB_ENDPOINT_IDX ( endpoint ); + struct efi_usb_endpoint *usbep = usbintf->endpoint[index]; + + return ( usbep && usbep->ep.open ); +} + +/** + * Open endpoint + * + * @v usbintf EFI USB interface + * @v endpoint Endpoint address + * @v attributes Endpoint attributes + * @v interval Interval (in milliseconds) + * @v driver Driver operations + * @ret rc Return status code + */ +static int efi_usb_open ( struct efi_usb_interface *usbintf, + unsigned int endpoint, unsigned int attributes, + unsigned int interval, + struct usb_endpoint_driver_operations *driver ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_device *usbdev = usbintf->usbdev; + struct efi_usb_endpoint *usbep; + unsigned int index = USB_ENDPOINT_IDX ( endpoint ); + int mtu; + EFI_STATUS efirc; + int rc; + + /* Allocate structure, if needed. Once allocated, we leave + * the endpoint structure in place until the device is + * removed, to work around external UEFI code that closes the + * endpoint at illegal times. + */ + usbep = usbintf->endpoint[index]; + if ( ! usbep ) { + usbep = zalloc ( sizeof ( *usbep ) ); + if ( ! usbep ) { + rc = -ENOMEM; + goto err_alloc; + } + usbep->usbintf = usbintf; + usbintf->endpoint[index] = usbep; + } + + /* Get endpoint MTU */ + mtu = efi_usb_mtu ( usbintf, endpoint ); + if ( mtu < 0 ) { + rc = mtu; + goto err_mtu; + } + + /* Allocate and initialise structure */ + usb_endpoint_init ( &usbep->ep, usbdev->func->usb, driver ); + usb_endpoint_describe ( &usbep->ep, endpoint, attributes, mtu, 0, + ( interval << 3 /* microframes */ ) ); + + /* Open endpoint */ + if ( ( rc = usb_endpoint_open ( &usbep->ep ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s %s could not open: %s\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ), + strerror ( rc ) ); + goto err_open; + } + DBGC ( usbdev, "USBDEV %s %s opened\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ) ); + + /* Create event */ + if ( ( efirc = bs->CreateEvent ( ( EVT_TIMER | EVT_NOTIFY_SIGNAL ), + TPL_CALLBACK, efi_usb_timer, usbep, + &usbep->event ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( usbdev, "USBDEV %s %s could not create event: %s\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ), + strerror ( rc ) ); + goto err_event; + } + + return 0; + + bs->CloseEvent ( usbep->event ); + err_event: + usb_endpoint_close ( &usbep->ep ); + err_open: + err_mtu: + err_alloc: + return rc; +} + +/** + * Close endpoint + * + * @v usbep EFI USB endpoint + */ +static void efi_usb_close ( struct efi_usb_endpoint *usbep ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_interface *usbintf = usbep->usbintf; + struct efi_usb_device *usbdev = usbintf->usbdev; + unsigned int index = USB_ENDPOINT_IDX ( usbep->ep.address ); + + /* Sanity check */ + assert ( usbintf->endpoint[index] == usbep ); + + /* Cancel timer (if applicable) and close event */ + bs->SetTimer ( usbep->event, TimerCancel, 0 ); + bs->CloseEvent ( usbep->event ); + + /* Close endpoint */ + usb_endpoint_close ( &usbep->ep ); + DBGC ( usbdev, "USBDEV %s %s closed\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ) ); +} + +/** + * Close all endpoints + * + * @v usbintf EFI USB interface + */ +static void efi_usb_close_all ( struct efi_usb_interface *usbintf ) { + struct efi_usb_endpoint *usbep; + unsigned int i; + + for ( i = 0 ; i < ( sizeof ( usbintf->endpoint ) / + sizeof ( usbintf->endpoint[0] ) ) ; i++ ) { + usbep = usbintf->endpoint[i]; + if ( usbep && usbep->ep.open ) + efi_usb_close ( usbep ); + } +} + +/** + * Free all endpoints + * + * @v usbintf EFI USB interface + */ +static void efi_usb_free_all ( struct efi_usb_interface *usbintf ) { + struct efi_usb_endpoint *usbep; + unsigned int i; + + for ( i = 0 ; i < ( sizeof ( usbintf->endpoint ) / + sizeof ( usbintf->endpoint[0] ) ) ; i++ ) { + usbep = usbintf->endpoint[i]; + if ( usbep ) { + assert ( ! usbep->ep.open ); + free ( usbep ); + usbintf->endpoint[i] = NULL; + } + } +} + +/** + * Complete synchronous transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void efi_usb_sync_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf __unused, int rc ) { + struct efi_usb_endpoint *usbep = + container_of ( ep, struct efi_usb_endpoint, ep ); + + /* Record completion status */ + usbep->rc = rc; +} + +/** Synchronous endpoint operations */ +static struct usb_endpoint_driver_operations efi_usb_sync_driver = { + .complete = efi_usb_sync_complete, +}; + +/** + * Perform synchronous transfer + * + * @v usbintf USB endpoint + * @v endpoint Endpoint address + * @v attributes Endpoint attributes + * @v timeout Timeout (in milliseconds) + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int efi_usb_sync_transfer ( struct efi_usb_interface *usbintf, + unsigned int endpoint, + unsigned int attributes, + unsigned int timeout, + void *data, size_t *len ) { + struct efi_usb_device *usbdev = usbintf->usbdev; + struct efi_usb_endpoint *usbep; + struct io_buffer *iobuf; + unsigned int index = USB_ENDPOINT_IDX ( endpoint ); + unsigned int i; + int rc; + + /* Open endpoint, if applicable */ + if ( ( ! efi_usb_is_open ( usbintf, endpoint ) ) && + ( ( rc = efi_usb_open ( usbintf, endpoint, attributes, 0, + &efi_usb_sync_driver ) ) != 0 ) ) { + goto err_open; + } + usbep = usbintf->endpoint[index]; + + /* Allocate and construct I/O buffer */ + iobuf = alloc_iob ( *len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + iob_put ( iobuf, *len ); + if ( ! ( endpoint & USB_ENDPOINT_IN ) ) + memcpy ( iobuf->data, data, *len ); + + /* Initialise completion status */ + usbep->rc = -EINPROGRESS; + + /* Enqueue transfer */ + if ( ( rc = usb_stream ( &usbep->ep, iobuf, 0 ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s %s could not enqueue: %s\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ), + strerror ( rc ) ); + goto err_stream; + } + + /* Wait for completion */ + rc = -ETIMEDOUT; + for ( i = 0 ; ( ( timeout == 0 ) || ( i < timeout ) ) ; i++ ) { + + /* Poll bus */ + usb_poll ( usbdev->func->usb->port->hub->bus ); + + /* Check for completion */ + if ( usbep->rc != -EINPROGRESS ) { + rc = usbep->rc; + break; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* Check for errors */ + if ( rc != 0 ) { + DBGC ( usbdev, "USBDEV %s %s failed: %s\n", usbintf->name, + usb_endpoint_name ( &usbep->ep ), strerror ( rc ) ); + goto err_completion; + } + + /* Copy completion to data buffer, if applicable */ + assert ( iob_len ( iobuf ) <= *len ); + if ( endpoint & USB_ENDPOINT_IN ) + memcpy ( data, iobuf->data, iob_len ( iobuf ) ); + *len = iob_len ( iobuf ); + + /* Free I/O buffer */ + free_iob ( iobuf ); + + /* Leave endpoint open */ + return 0; + + err_completion: + err_stream: + free_iob ( iobuf ); + err_alloc: + efi_usb_close ( usbep ); + err_open: + return EFIRC ( rc ); +} + +/** + * Complete asynchronous transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void efi_usb_async_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct efi_usb_endpoint *usbep = + container_of ( ep, struct efi_usb_endpoint, ep ); + UINT32 status; + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto drop; + + /* Construct status */ + status = ( ( rc == 0 ) ? 0 : EFI_USB_ERR_SYSTEM ); + + /* Report completion */ + usbep->callback ( iobuf->data, iob_len ( iobuf ), usbep->context, + status ); + + drop: + /* Recycle or free I/O buffer */ + if ( usbep->ep.open ) { + usb_recycle ( &usbep->ep, iobuf ); + } else { + free_iob ( iobuf ); + } +} + +/** Asynchronous endpoint operations */ +static struct usb_endpoint_driver_operations efi_usb_async_driver = { + .complete = efi_usb_async_complete, +}; + +/** + * Start asynchronous transfer + * + * @v usbintf EFI USB interface + * @v endpoint Endpoint address + * @v interval Interval (in milliseconds) + * @v len Transfer length + * @v callback Callback function + * @v context Context for callback function + * @ret rc Return status code + */ +static int efi_usb_async_start ( struct efi_usb_interface *usbintf, + unsigned int endpoint, unsigned int interval, + size_t len, + EFI_ASYNC_USB_TRANSFER_CALLBACK callback, + void *context ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_device *usbdev = usbintf->usbdev; + struct efi_usb_endpoint *usbep; + unsigned int index = USB_ENDPOINT_IDX ( endpoint ); + EFI_STATUS efirc; + int rc; + + /* Fail if endpoint is already open */ + if ( efi_usb_is_open ( usbintf, endpoint ) ) { + rc = -EINVAL; + goto err_already_open; + } + + /* Open endpoint */ + if ( ( rc = efi_usb_open ( usbintf, endpoint, + USB_ENDPOINT_ATTR_INTERRUPT, interval, + &efi_usb_async_driver ) ) != 0 ) + goto err_open; + usbep = usbintf->endpoint[index]; + + /* Record callback parameters */ + usbep->callback = callback; + usbep->context = context; + + /* Prefill endpoint */ + usb_refill_init ( &usbep->ep, 0, len, EFI_USB_ASYNC_FILL ); + if ( ( rc = usb_prefill ( &usbep->ep ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s %s could not prefill: %s\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ), + strerror ( rc ) ); + goto err_prefill; + } + + /* Start timer */ + if ( ( efirc = bs->SetTimer ( usbep->event, TimerPeriodic, + ( interval * 10000 ) ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( usbdev, "USBDEV %s %s could not set timer: %s\n", + usbintf->name, usb_endpoint_name ( &usbep->ep ), + strerror ( rc ) ); + goto err_timer; + } + + return 0; + + bs->SetTimer ( usbep->event, TimerCancel, 0 ); + err_timer: + err_prefill: + efi_usb_close ( usbep ); + err_open: + err_already_open: + return rc; +} + +/** + * Stop asynchronous transfer + * + * @v usbintf EFI USB interface + * @v endpoint Endpoint address + */ +static void efi_usb_async_stop ( struct efi_usb_interface *usbintf, + unsigned int endpoint ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_endpoint *usbep; + unsigned int index = USB_ENDPOINT_IDX ( endpoint ); + + /* Do nothing if endpoint is already closed */ + if ( ! efi_usb_is_open ( usbintf, endpoint ) ) + return; + usbep = usbintf->endpoint[index]; + + /* Stop timer */ + bs->SetTimer ( usbep->event, TimerCancel, 0 ); + + /* Close endpoint */ + efi_usb_close ( usbep ); +} + +/****************************************************************************** + * + * USB I/O protocol + * + ****************************************************************************** + */ + +/** + * Perform control transfer + * + * @v usbio USB I/O protocol + * @v packet Setup packet + * @v direction Data direction + * @v timeout Timeout (in milliseconds) + * @v data Data buffer + * @v len Length of data + * @ret status Transfer status + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_control_transfer ( EFI_USB_IO_PROTOCOL *usbio, + EFI_USB_DEVICE_REQUEST *packet, + EFI_USB_DATA_DIRECTION direction, + UINT32 timeout, VOID *data, UINTN len, + UINT32 *status ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + unsigned int request = ( packet->RequestType | + USB_REQUEST_TYPE ( packet->Request ) ); + unsigned int value = le16_to_cpu ( packet->Value ); + unsigned int index = le16_to_cpu ( packet->Index ); + struct efi_saved_tpl tpl; + int rc; + + DBGC2 ( usbdev, "USBDEV %s control %04x:%04x:%04x:%04x %s %dms " + "%p+%zx\n", usbintf->name, request, value, index, + le16_to_cpu ( packet->Length ), + efi_usb_direction_name ( direction ), timeout, data, + ( ( size_t ) len ) ); + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Clear status */ + *status = 0; + + /* Block attempts to change the device configuration, since + * this is logically impossible to do given the constraints of + * the EFI_USB_IO_PROTOCOL design. + */ + if ( ( request == USB_SET_CONFIGURATION ) && + ( value != usbdev->config->config ) ) { + DBGC ( usbdev, "USBDEV %s cannot set configuration %d: not " + "logically possible\n", usbintf->name, index ); + rc = -ENOTSUP; + goto err_change_config; + } + + /* If we are selecting a new alternate setting then close all + * open endpoints. + */ + if ( ( request == USB_SET_INTERFACE ) && + ( value != usbintf->alternate ) ) + efi_usb_close_all ( usbintf ); + + /* Issue control transfer */ + if ( ( rc = usb_control ( usbdev->func->usb, request, value, index, + data, len ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s control %04x:%04x:%04x:%04x %p+%zx " + "failed: %s\n", usbintf->name, request, value, index, + le16_to_cpu ( packet->Length ), data, ( ( size_t ) len ), + strerror ( rc ) ); + *status = EFI_USB_ERR_SYSTEM; + goto err_control; + } + + /* Update alternate setting, if applicable */ + if ( request == USB_SET_INTERFACE ) { + usbintf->alternate = value; + DBGC ( usbdev, "USBDEV %s alt %d selected\n", + usbintf->name, usbintf->alternate ); + } + + err_control: + err_change_config: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Perform bulk transfer + * + * @v usbio USB I/O protocol + * @v endpoint Endpoint address + * @v data Data buffer + * @v len Length of data + * @v timeout Timeout (in milliseconds) + * @ret status Transfer status + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_bulk_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, VOID *data, + UINTN *len, UINTN timeout, UINT32 *status ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + size_t actual = *len; + struct efi_saved_tpl tpl; + int rc; + + DBGC2 ( usbdev, "USBDEV %s bulk %s %p+%zx %dms\n", usbintf->name, + ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data, + ( ( size_t ) *len ), ( ( unsigned int ) timeout ) ); + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Clear status */ + *status = 0; + + /* Perform synchronous transfer */ + if ( ( rc = efi_usb_sync_transfer ( usbintf, endpoint, + USB_ENDPOINT_ATTR_BULK, timeout, + data, &actual ) ) != 0 ) { + /* Assume that any error represents a timeout */ + *status = EFI_USB_ERR_TIMEOUT; + goto err_transfer; + } + + err_transfer: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Perform synchronous interrupt transfer + * + * @v usbio USB I/O protocol + * @v endpoint Endpoint address + * @v data Data buffer + * @v len Length of data + * @v timeout Timeout (in milliseconds) + * @ret status Transfer status + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_sync_interrupt_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, + VOID *data, UINTN *len, UINTN timeout, + UINT32 *status ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + size_t actual = *len; + struct efi_saved_tpl tpl; + int rc; + + DBGC2 ( usbdev, "USBDEV %s sync intr %s %p+%zx %dms\n", usbintf->name, + ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data, + ( ( size_t ) *len ), ( ( unsigned int ) timeout ) ); + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Clear status */ + *status = 0; + + /* Perform synchronous transfer */ + if ( ( rc = efi_usb_sync_transfer ( usbintf, endpoint, + USB_ENDPOINT_ATTR_INTERRUPT, + timeout, data, &actual ) ) != 0 ) { + /* Assume that any error represents a timeout */ + *status = EFI_USB_ERR_TIMEOUT; + goto err_transfer; + } + + err_transfer: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Perform asynchronous interrupt transfer + * + * @v usbio USB I/O protocol + * @v endpoint Endpoint address + * @v start Start (rather than stop) transfer + * @v interval Polling interval (in milliseconds) + * @v len Data length + * @v callback Callback function + * @v context Context for callback function + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_async_interrupt_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, + BOOLEAN start, UINTN interval, UINTN len, + EFI_ASYNC_USB_TRANSFER_CALLBACK callback, + VOID *context ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + struct efi_saved_tpl tpl; + int rc; + + DBGC2 ( usbdev, "USBDEV %s async intr %s len %#zx int %d %p/%p\n", + usbintf->name, + ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), + ( ( size_t ) len ), ( ( unsigned int ) interval ), + callback, context ); + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Start/stop transfer as applicable */ + if ( start ) { + + /* Start new transfer */ + if ( ( rc = efi_usb_async_start ( usbintf, endpoint, interval, + len, callback, + context ) ) != 0 ) + goto err_start; + + } else { + + /* Stop transfer */ + efi_usb_async_stop ( usbintf, endpoint ); + + /* Success */ + rc = 0; + + } + + err_start: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Perform synchronous isochronous transfer + * + * @v usbio USB I/O protocol + * @v endpoint Endpoint address + * @v data Data buffer + * @v len Length of data + * @ret status Transfer status + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_isochronous_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, + VOID *data, UINTN len, UINT32 *status ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s sync iso %s %p+%zx\n", usbintf->name, + ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data, + ( ( size_t ) len ) ); + + /* Clear status */ + *status = 0; + + /* Not supported */ + return EFI_UNSUPPORTED; +} + +/** + * Perform asynchronous isochronous transfers + * + * @v usbio USB I/O protocol + * @v endpoint Endpoint address + * @v data Data buffer + * @v len Length of data + * @v callback Callback function + * @v context Context for callback function + * @ret status Transfer status + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_async_isochronous_transfer ( EFI_USB_IO_PROTOCOL *usbio, UINT8 endpoint, + VOID *data, UINTN len, + EFI_ASYNC_USB_TRANSFER_CALLBACK callback, + VOID *context ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s async iso %s %p+%zx %p/%p\n", usbintf->name, + ( ( endpoint & USB_ENDPOINT_IN ) ? "IN" : "OUT" ), data, + ( ( size_t ) len ), callback, context ); + + /* Not supported */ + return EFI_UNSUPPORTED; +} + +/** + * Get device descriptor + * + * @v usbio USB I/O protocol + * @ret efidesc EFI device descriptor + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_device_descriptor ( EFI_USB_IO_PROTOCOL *usbio, + EFI_USB_DEVICE_DESCRIPTOR *efidesc ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s get device descriptor\n", usbintf->name ); + + /* Copy cached device descriptor */ + memcpy ( efidesc, &usbdev->func->usb->device, sizeof ( *efidesc ) ); + + return 0; +} + +/** + * Get configuration descriptor + * + * @v usbio USB I/O protocol + * @ret efidesc EFI interface descriptor + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_config_descriptor ( EFI_USB_IO_PROTOCOL *usbio, + EFI_USB_CONFIG_DESCRIPTOR *efidesc ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s get configuration descriptor\n", + usbintf->name ); + + /* Copy cached configuration descriptor */ + memcpy ( efidesc, usbdev->config, sizeof ( *efidesc ) ); + + return 0; +} + +/** + * Get interface descriptor + * + * @v usbio USB I/O protocol + * @ret efidesc EFI interface descriptor + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_interface_descriptor ( EFI_USB_IO_PROTOCOL *usbio, + EFI_USB_INTERFACE_DESCRIPTOR *efidesc ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + struct usb_interface_descriptor *desc; + + DBGC2 ( usbdev, "USBDEV %s get interface descriptor\n", usbintf->name ); + + /* Locate cached interface descriptor */ + desc = usb_interface_descriptor ( usbdev->config, usbintf->interface, + usbintf->alternate ); + if ( ! desc ) { + DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n", + usbintf->name, usbintf->alternate ); + return -ENOENT; + } + + /* Copy cached interface descriptor */ + memcpy ( efidesc, desc, sizeof ( *efidesc ) ); + + return 0; +} + +/** + * Get endpoint descriptor + * + * @v usbio USB I/O protocol + * @v address Endpoint index + * @ret efidesc EFI interface descriptor + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_endpoint_descriptor ( EFI_USB_IO_PROTOCOL *usbio, UINT8 index, + EFI_USB_ENDPOINT_DESCRIPTOR *efidesc ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *desc; + + DBGC2 ( usbdev, "USBDEV %s get endpoint %d descriptor\n", + usbintf->name, index ); + + /* Locate cached interface descriptor */ + interface = usb_interface_descriptor ( usbdev->config, + usbintf->interface, + usbintf->alternate ); + if ( ! interface ) { + DBGC ( usbdev, "USBDEV %s alt %d has no interface descriptor\n", + usbintf->name, usbintf->alternate ); + return -ENOENT; + } + + /* Locate and copy cached endpoint descriptor */ + for_each_interface_descriptor ( desc, usbdev->config, interface ) { + if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) && + ( index-- == 0 ) ) { + memcpy ( efidesc, desc, sizeof ( *efidesc ) ); + return 0; + } + } + return -ENOENT; +} + +/** + * Get string descriptor + * + * @v usbio USB I/O protocol + * @v language Language ID + * @v index String index + * @ret string String + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_string_descriptor ( EFI_USB_IO_PROTOCOL *usbio, UINT16 language, + UINT8 index, CHAR16 **string ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + struct usb_descriptor_header header; + struct efi_saved_tpl tpl; + VOID *buffer; + size_t len; + EFI_STATUS efirc; + int rc; + + DBGC2 ( usbdev, "USBDEV %s get string %d:%d descriptor\n", + usbintf->name, language, index ); + + /* Raise TPL */ + efi_raise_tpl ( &tpl ); + + /* Read descriptor header */ + if ( ( rc = usb_get_descriptor ( usbdev->func->usb, 0, + USB_STRING_DESCRIPTOR, index, + language, &header, + sizeof ( header ) ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s could not get string %d:%d " + "descriptor header: %s\n", usbintf->name, language, + index, strerror ( rc ) ); + goto err_get_header; + } + len = header.len; + if ( len < sizeof ( header ) ) { + DBGC ( usbdev, "USBDEV %s underlength string %d:%d\n", + usbintf->name, language, index ); + rc = -EINVAL; + goto err_len; + } + + /* Allocate buffer */ + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, len, + &buffer ) ) != 0 ) { + rc = -EEFI ( efirc ); + goto err_alloc; + } + + /* Read whole descriptor */ + if ( ( rc = usb_get_descriptor ( usbdev->func->usb, 0, + USB_STRING_DESCRIPTOR, index, + language, buffer, len ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s could not get string %d:%d " + "descriptor: %s\n", usbintf->name, language, + index, strerror ( rc ) ); + goto err_get_descriptor; + } + + /* Shuffle down and terminate string */ + memmove ( buffer, ( buffer + sizeof ( header ) ), + ( len - sizeof ( header ) ) ); + memset ( ( buffer + len - sizeof ( header ) ), 0, sizeof ( **string ) ); + + /* Restore TPL */ + efi_restore_tpl ( &tpl ); + + /* Return allocated string */ + *string = buffer; + return 0; + + err_get_descriptor: + bs->FreePool ( buffer ); + err_alloc: + err_len: + err_get_header: + efi_restore_tpl ( &tpl ); + return EFIRC ( rc ); +} + +/** + * Get supported languages + * + * @v usbio USB I/O protocol + * @ret languages Language ID table + * @ret len Length of language ID table + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_get_supported_languages ( EFI_USB_IO_PROTOCOL *usbio, + UINT16 **languages, UINT16 *len ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s get supported languages\n", usbintf->name ); + + /* Return cached supported languages */ + *languages = usbdev->lang; + *len = usbdev->lang_len; + + return 0; +} + +/** + * Reset port + * + * @v usbio USB I/O protocol + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_usb_port_reset ( EFI_USB_IO_PROTOCOL *usbio ) { + struct efi_usb_interface *usbintf = + container_of ( usbio, struct efi_usb_interface, usbio ); + struct efi_usb_device *usbdev = usbintf->usbdev; + + DBGC2 ( usbdev, "USBDEV %s reset port\n", usbintf->name ); + + /* This is logically impossible to do, since resetting the + * port may destroy state belonging to other + * EFI_USB_IO_PROTOCOL instances belonging to the same USB + * device. (This is yet another artifact of the incredibly + * poor design of the EFI_USB_IO_PROTOCOL.) + */ + return EFI_INVALID_PARAMETER; +} + +/** USB I/O protocol */ +static EFI_USB_IO_PROTOCOL efi_usb_io_protocol = { + .UsbControlTransfer = efi_usb_control_transfer, + .UsbBulkTransfer = efi_usb_bulk_transfer, + .UsbAsyncInterruptTransfer = efi_usb_async_interrupt_transfer, + .UsbSyncInterruptTransfer = efi_usb_sync_interrupt_transfer, + .UsbIsochronousTransfer = efi_usb_isochronous_transfer, + .UsbAsyncIsochronousTransfer = efi_usb_async_isochronous_transfer, + .UsbGetDeviceDescriptor = efi_usb_get_device_descriptor, + .UsbGetConfigDescriptor = efi_usb_get_config_descriptor, + .UsbGetInterfaceDescriptor = efi_usb_get_interface_descriptor, + .UsbGetEndpointDescriptor = efi_usb_get_endpoint_descriptor, + .UsbGetStringDescriptor = efi_usb_get_string_descriptor, + .UsbGetSupportedLanguages = efi_usb_get_supported_languages, + .UsbPortReset = efi_usb_port_reset, +}; + +/****************************************************************************** + * + * USB driver + * + ****************************************************************************** + */ + +/** + * Install interface + * + * @v usbdev EFI USB device + * @v interface Interface number + * @ret rc Return status code + */ +static int efi_usb_install ( struct efi_usb_device *usbdev, + unsigned int interface ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct usb_function *func = usbdev->func; + struct efi_usb_interface *usbintf; + int leak = 0; + EFI_STATUS efirc; + int rc; + + /* Allocate and initialise structure */ + usbintf = zalloc ( sizeof ( *usbintf ) ); + if ( ! usbintf ) { + rc = -ENOMEM; + goto err_alloc; + } + snprintf ( usbintf->name, sizeof ( usbintf->name ), "%s[%d]", + usbdev->name, interface ); + usbintf->usbdev = usbdev; + usbintf->interface = interface; + memcpy ( &usbintf->usbio, &efi_usb_io_protocol, + sizeof ( usbintf->usbio ) ); + + /* Construct device path */ + usbintf->path = efi_usb_path ( func ); + if ( ! usbintf->path ) { + rc = -ENODEV; + goto err_path; + } + + /* Add to list of interfaces */ + list_add_tail ( &usbintf->list, &usbdev->interfaces ); + + /* Install protocols */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &usbintf->handle, + &efi_usb_io_protocol_guid, &usbintf->usbio, + &efi_device_path_protocol_guid, usbintf->path, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( usbdev, "USBDEV %s could not install protocols: %s\n", + usbintf->name, strerror ( rc ) ); + goto err_install_protocol; + } + + DBGC ( usbdev, "USBDEV %s installed as %s\n", + usbintf->name, efi_handle_name ( usbintf->handle ) ); + return 0; + + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + usbintf->handle, + &efi_usb_io_protocol_guid, &usbintf->usbio, + &efi_device_path_protocol_guid, usbintf->path, + NULL ) ) != 0 ) { + DBGC ( usbdev, "USBDEV %s could not uninstall: %s\n", + usbintf->name, strerror ( -EEFI ( efirc ) ) ); + leak = 1; + } + efi_nullify_usbio ( &usbintf->usbio ); + err_install_protocol: + efi_usb_close_all ( usbintf ); + efi_usb_free_all ( usbintf ); + list_del ( &usbintf->list ); + if ( ! leak ) + free ( usbintf->path ); + err_path: + if ( ! leak ) + free ( usbintf ); + err_alloc: + if ( leak ) { + DBGC ( usbdev, "USBDEV %s nullified and leaked\n", + usbintf->name ); + } + return rc; +} + +/** + * Uninstall interface + * + * @v usbintf EFI USB interface + */ +static void efi_usb_uninstall ( struct efi_usb_interface *usbintf ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_usb_device *usbdev = usbintf->usbdev; + int leak = efi_shutdown_in_progress; + EFI_STATUS efirc; + + DBGC ( usbdev, "USBDEV %s uninstalling %s\n", + usbintf->name, efi_handle_name ( usbintf->handle ) ); + + /* Disconnect controllers. This should not be necessary, but + * seems to be required on some platforms to avoid failures + * when uninstalling protocols. + */ + if ( ! efi_shutdown_in_progress ) + bs->DisconnectController ( usbintf->handle, NULL, NULL ); + + /* Uninstall protocols */ + if ( ( ! efi_shutdown_in_progress ) && + ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + usbintf->handle, + &efi_usb_io_protocol_guid, &usbintf->usbio, + &efi_device_path_protocol_guid, usbintf->path, + NULL ) ) != 0 ) ) { + DBGC ( usbdev, "USBDEV %s could not uninstall: %s\n", + usbintf->name, strerror ( -EEFI ( efirc ) ) ); + leak = 1; + } + efi_nullify_usbio ( &usbintf->usbio ); + + /* Close and free all endpoints */ + efi_usb_close_all ( usbintf ); + efi_usb_free_all ( usbintf ); + + /* Remove from list of interfaces */ + list_del ( &usbintf->list ); + + /* Free device path */ + if ( ! leak ) + free ( usbintf->path ); + + /* Free interface */ + if ( ! leak ) + free ( usbintf ); + + /* Report leakage, if applicable */ + if ( leak && ( ! efi_shutdown_in_progress ) ) { + DBGC ( usbdev, "USBDEV %s nullified and leaked\n", + usbintf->name ); + } +} + +/** + * Uninstall all interfaces + * + * @v usbdev EFI USB device + */ +static void efi_usb_uninstall_all ( struct efi_usb_device *efiusb ) { + struct efi_usb_interface *usbintf; + + /* Uninstall all interfaces */ + while ( ( usbintf = list_first_entry ( &efiusb->interfaces, + struct efi_usb_interface, + list ) ) ) { + efi_usb_uninstall ( usbintf ); + } +} + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int efi_usb_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct usb_device *usb = func->usb; + struct efi_usb_device *usbdev; + struct efi_usb_interface *usbintf; + struct usb_descriptor_header header; + struct usb_descriptor_header *lang; + size_t config_len; + size_t lang_len; + unsigned int i; + int rc; + + /* Get configuration length */ + config_len = le16_to_cpu ( config->len ); + + /* Get supported languages descriptor header */ + if ( ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, 0, 0, + &header, sizeof ( header ) ) ) != 0 ) { + /* Assume no strings are present */ + header.len = 0; + } + lang_len = ( ( header.len >= sizeof ( header ) ) ? + ( header.len - sizeof ( header ) ) : 0 ); + + /* Allocate and initialise structure */ + usbdev = zalloc ( sizeof ( *usbdev ) + config_len + + sizeof ( *lang ) + lang_len ); + if ( ! usbdev ) { + rc = -ENOMEM; + goto err_alloc; + } + usb_func_set_drvdata ( func, usbdev ); + usbdev->name = func->name; + usbdev->func = func; + usbdev->config = ( ( ( void * ) usbdev ) + sizeof ( *usbdev ) ); + memcpy ( usbdev->config, config, config_len ); + lang = ( ( ( void * ) usbdev->config ) + config_len ); + usbdev->lang = ( ( ( void * ) lang ) + sizeof ( *lang ) ); + usbdev->lang_len = lang_len; + INIT_LIST_HEAD ( &usbdev->interfaces ); + + /* Get supported languages descriptor, if applicable */ + if ( lang_len && + ( ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, + 0, 0, lang, header.len ) ) != 0 ) ) { + DBGC ( usbdev, "USBDEV %s could not get supported languages: " + "%s\n", usbdev->name, strerror ( rc ) ); + goto err_get_languages; + } + + /* Install interfaces */ + for ( i = 0 ; i < func->desc.count ; i++ ) { + if ( ( rc = efi_usb_install ( usbdev, + func->interface[i] ) ) != 0 ) + goto err_install; + } + + /* Connect any external drivers */ + list_for_each_entry ( usbintf, &usbdev->interfaces, list ) + bs->ConnectController ( usbintf->handle, NULL, NULL, TRUE ); + + return 0; + + err_install: + efi_usb_uninstall_all ( usbdev ); + assert ( list_empty ( &usbdev->interfaces ) ); + err_get_languages: + free ( usbdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void efi_usb_remove ( struct usb_function *func ) { + struct efi_usb_device *usbdev = usb_func_get_drvdata ( func ); + + /* Uninstall all interfaces */ + efi_usb_uninstall_all ( usbdev ); + assert ( list_empty ( &usbdev->interfaces ) ); + + /* Free device */ + free ( usbdev ); +} + +/** USB I/O protocol device IDs */ +static struct usb_device_id efi_usb_ids[] = { + { + .name = "usbio", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + }, +}; + +/** USB I/O protocol driver */ +struct usb_driver usbio_driver __usb_fallback_driver = { + .ids = efi_usb_ids, + .id_count = ( sizeof ( efi_usb_ids ) / sizeof ( efi_usb_ids[0] ) ), + .class = USB_CLASS_ID ( USB_ANY_ID, USB_ANY_ID, USB_ANY_ID ), + .score = USB_SCORE_FALLBACK, + .probe = efi_usb_probe, + .remove = efi_usb_remove, +}; |