diff options
Diffstat (limited to 'qemu/roms/ipxe/src/drivers')
102 files changed, 19755 insertions, 276 deletions
diff --git a/qemu/roms/ipxe/src/drivers/bitbash/bitbash.c b/qemu/roms/ipxe/src/drivers/bitbash/bitbash.c index 23ca30356..9b24f716c 100644 --- a/qemu/roms/ipxe/src/drivers/bitbash/bitbash.c +++ b/qemu/roms/ipxe/src/drivers/bitbash/bitbash.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/bitbash.h> diff --git a/qemu/roms/ipxe/src/drivers/bitbash/i2c_bit.c b/qemu/roms/ipxe/src/drivers/bitbash/i2c_bit.c index decc8d80e..707d9447d 100644 --- a/qemu/roms/ipxe/src/drivers/bitbash/i2c_bit.c +++ b/qemu/roms/ipxe/src/drivers/bitbash/i2c_bit.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdint.h> diff --git a/qemu/roms/ipxe/src/drivers/bitbash/spi_bit.c b/qemu/roms/ipxe/src/drivers/bitbash/spi_bit.c index 1b39d72fa..04fddc20b 100644 --- a/qemu/roms/ipxe/src/drivers/bitbash/spi_bit.c +++ b/qemu/roms/ipxe/src/drivers/bitbash/spi_bit.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdint.h> diff --git a/qemu/roms/ipxe/src/drivers/block/ata.c b/qemu/roms/ipxe/src/drivers/block/ata.c index c9b87c20c..b1c6855a0 100644 --- a/qemu/roms/ipxe/src/drivers/block/ata.c +++ b/qemu/roms/ipxe/src/drivers/block/ata.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/block/scsi.c b/qemu/roms/ipxe/src/drivers/block/scsi.c index 64d692986..fd5f82b9f 100644 --- a/qemu/roms/ipxe/src/drivers/block/scsi.c +++ b/qemu/roms/ipxe/src/drivers/block/scsi.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/bus/cdc.c b/qemu/roms/ipxe/src/drivers/bus/cdc.c new file mode 100644 index 000000000..373a03072 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/cdc.c @@ -0,0 +1,54 @@ +/* + * 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 <stddef.h> +#include <ipxe/usb.h> +#include <ipxe/cdc.h> + +/** @file + * + * USB Communications Device Class (CDC) + * + */ + +/** + * Locate CDC union functional descriptor + * + * @v config Configuration descriptor + * @v interface Interface descriptor + * @ret desc Union functional descriptor, or NULL if not found + */ +struct cdc_union_descriptor * +cdc_union_descriptor ( struct usb_configuration_descriptor *config, + struct usb_interface_descriptor *interface ) { + struct cdc_union_descriptor *desc; + + for_each_interface_descriptor ( desc, config, interface ) { + if ( ( desc->header.type == USB_CS_INTERFACE_DESCRIPTOR ) && + ( desc->subtype == CDC_SUBTYPE_UNION ) ) + return desc; + } + return NULL; +} diff --git a/qemu/roms/ipxe/src/drivers/bus/pci.c b/qemu/roms/ipxe/src/drivers/bus/pci.c index 4a8d00b54..6fbedd940 100644 --- a/qemu/roms/ipxe/src/drivers/bus/pci.c +++ b/qemu/roms/ipxe/src/drivers/bus/pci.c @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -58,8 +62,8 @@ static unsigned long pci_bar ( struct pci_device *pci, unsigned int reg ) { uint32_t high; pci_read_config_dword ( pci, reg, &low ); - if ( ( low & (PCI_BASE_ADDRESS_SPACE|PCI_BASE_ADDRESS_MEM_TYPE_MASK) ) - == (PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64) ){ + if ( ( low & (PCI_BASE_ADDRESS_SPACE_IO|PCI_BASE_ADDRESS_MEM_TYPE_MASK)) + == PCI_BASE_ADDRESS_MEM_TYPE_64 ) { pci_read_config_dword ( pci, reg + 4, &high ); if ( high ) { if ( sizeof ( unsigned long ) > sizeof ( uint32_t ) ) { @@ -93,10 +97,10 @@ unsigned long pci_bar_start ( struct pci_device *pci, unsigned int reg ) { unsigned long bar; bar = pci_bar ( pci, reg ); - if ( (bar & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY ){ - return ( bar & PCI_BASE_ADDRESS_MEM_MASK ); + if ( bar & PCI_BASE_ADDRESS_SPACE_IO ) { + return ( bar & ~PCI_BASE_ADDRESS_IO_MASK ); } else { - return ( bar & PCI_BASE_ADDRESS_IO_MASK ); + return ( bar & ~PCI_BASE_ADDRESS_MEM_MASK ); } } @@ -122,11 +126,11 @@ static void pci_read_bases ( struct pci_device *pci ) { if ( bar & PCI_BASE_ADDRESS_SPACE_IO ) { if ( ! pci->ioaddr ) pci->ioaddr = - ( bar & PCI_BASE_ADDRESS_IO_MASK ); + ( bar & ~PCI_BASE_ADDRESS_IO_MASK ); } else { if ( ! pci->membase ) pci->membase = - ( bar & PCI_BASE_ADDRESS_MEM_MASK ); + ( bar & ~PCI_BASE_ADDRESS_MEM_MASK ); /* Skip next BAR if 64-bit */ if ( bar & PCI_BASE_ADDRESS_MEM_TYPE_64 ) reg += 4; @@ -181,7 +185,7 @@ int pci_read_config ( struct pci_device *pci ) { pci->busdevfn = PCI_FIRST_FUNC ( pci->busdevfn ); pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdrtype ); pci->busdevfn = busdevfn; - if ( ! ( hdrtype & 0x80 ) ) + if ( ! ( hdrtype & PCI_HEADER_TYPE_MULTI ) ) return -ENODEV; } @@ -253,6 +257,8 @@ int pci_find_driver ( struct pci_device *pci ) { unsigned int i; for_each_table_entry ( driver, PCI_DRIVERS ) { + if ( ( driver->class.class ^ pci->class ) & driver->class.mask ) + continue; for ( i = 0 ; i < driver->id_count ; i++ ) { id = &driver->ids[i]; if ( ( id->vendor != PCI_ANY_ID ) && @@ -334,14 +340,15 @@ static int pcibus_probe ( struct root_device *rootdev ) { /* Look for a driver */ if ( ( rc = pci_find_driver ( pci ) ) != 0 ) { - DBGC ( pci, PCI_FMT " (%04x:%04x) has no driver\n", - PCI_ARGS ( pci ), pci->vendor, pci->device ); + DBGC ( pci, PCI_FMT " (%04x:%04x class %06x) has no " + "driver\n", PCI_ARGS ( pci ), pci->vendor, + pci->device, pci->class ); continue; } /* Add to device hierarchy */ pci->dev.parent = &rootdev->dev; - list_add ( &pci->dev.siblings, &rootdev->dev.children); + list_add ( &pci->dev.siblings, &rootdev->dev.children ); /* Look for a driver */ if ( ( rc = pci_probe ( pci ) ) == 0 ) { diff --git a/qemu/roms/ipxe/src/drivers/bus/pci_settings.c b/qemu/roms/ipxe/src/drivers/bus/pci_settings.c index db20452e0..1cb9fa5a3 100644 --- a/qemu/roms/ipxe/src/drivers/bus/pci_settings.c +++ b/qemu/roms/ipxe/src/drivers/bus/pci_settings.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdio.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/drivers/bus/pcibackup.c b/qemu/roms/ipxe/src/drivers/bus/pcibackup.c index 6b592e893..fecad8192 100644 --- a/qemu/roms/ipxe/src/drivers/bus/pcibackup.c +++ b/qemu/roms/ipxe/src/drivers/bus/pcibackup.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/pci.h> diff --git a/qemu/roms/ipxe/src/drivers/bus/pciextra.c b/qemu/roms/ipxe/src/drivers/bus/pciextra.c index c4417e0cb..82287fb86 100644 --- a/qemu/roms/ipxe/src/drivers/bus/pciextra.c +++ b/qemu/roms/ipxe/src/drivers/bus/pciextra.c @@ -1,4 +1,4 @@ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/pci.h> @@ -26,7 +26,7 @@ int pci_find_capability ( struct pci_device *pci, int cap ) { return 0; pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdr_type ); - switch ( hdr_type & 0x7F ) { + switch ( hdr_type & PCI_HEADER_TYPE_MASK ) { case PCI_HEADER_TYPE_NORMAL: case PCI_HEADER_TYPE_BRIDGE: default: @@ -38,13 +38,13 @@ int pci_find_capability ( struct pci_device *pci, int cap ) { } while ( ttl-- && pos >= 0x40 ) { pos &= ~3; - pci_read_config_byte ( pci, pos + PCI_CAP_LIST_ID, &id ); + pci_read_config_byte ( pci, pos + PCI_CAP_ID, &id ); DBG ( "PCI Capability: %d\n", id ); if ( id == 0xff ) break; if ( id == cap ) return pos; - pci_read_config_byte ( pci, pos + PCI_CAP_LIST_NEXT, &pos ); + pci_read_config_byte ( pci, pos + PCI_CAP_NEXT, &pos ); } return 0; } @@ -76,9 +76,9 @@ unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) { /* Restore the original command register. This reenables decoding. */ pci_write_config_word ( pci, PCI_COMMAND, cmd ); if ( start & PCI_BASE_ADDRESS_SPACE_IO ) { - size &= PCI_BASE_ADDRESS_IO_MASK; + size &= ~PCI_BASE_ADDRESS_IO_MASK; } else { - size &= PCI_BASE_ADDRESS_MEM_MASK; + size &= ~PCI_BASE_ADDRESS_MEM_MASK; } /* Find the lowest bit set */ size = size & ~( size - 1 ); diff --git a/qemu/roms/ipxe/src/drivers/bus/pcivpd.c b/qemu/roms/ipxe/src/drivers/bus/pcivpd.c index 0b7a879fe..243b1f779 100644 --- a/qemu/roms/ipxe/src/drivers/bus/pcivpd.c +++ b/qemu/roms/ipxe/src/drivers/bus/pcivpd.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/bus/usb.c b/qemu/roms/ipxe/src/drivers/bus/usb.c new file mode 100644 index 000000000..2019e3341 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/bus/usb.c @@ -0,0 +1,2128 @@ +/* + * Copyright (C) 2014 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 <strings.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/usb.h> +#include <ipxe/cdc.h> + +/** @file + * + * Universal Serial Bus (USB) + * + */ + +/** List of USB buses */ +struct list_head usb_buses = LIST_HEAD_INIT ( usb_buses ); + +/** List of changed ports */ +static struct list_head usb_changed = LIST_HEAD_INIT ( usb_changed ); + +/** List of halted endpoints */ +static struct list_head usb_halted = LIST_HEAD_INIT ( usb_halted ); + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Get USB speed name (for debugging) + * + * @v speed Speed + * @ret name Speed name + */ +static inline const char * usb_speed_name ( unsigned int speed ) { + static const char *exponents[4] = { "", "k", "M", "G" }; + static char buf[ 10 /* "xxxxxXbps" + NUL */ ]; + unsigned int mantissa; + unsigned int exponent; + + /* Extract mantissa and exponent */ + mantissa = USB_SPEED_MANTISSA ( speed ); + exponent = USB_SPEED_EXPONENT ( speed ); + + /* Name speed */ + switch ( speed ) { + case USB_SPEED_NONE: return "DETACHED"; + case USB_SPEED_LOW: return "low"; + case USB_SPEED_FULL: return "full"; + case USB_SPEED_HIGH: return "high"; + case USB_SPEED_SUPER: return "super"; + default: + snprintf ( buf, sizeof ( buf ), "%d%sbps", + mantissa, exponents[exponent] ); + return buf; + } +} + +/** + * Transcribe USB BCD-coded value (for debugging) + * + * @v bcd BCD-coded value + * @ret string Transcribed value + */ +static inline const char * usb_bcd ( uint16_t bcd ) { + static char buf[ 6 /* "xx.xx" + NUL */ ]; + uint8_t high = ( bcd >> 8 ); + uint8_t low = ( bcd >> 0 ); + + snprintf ( buf, sizeof ( buf ), "%x.%02x", high, low ); + return buf; +} + +/****************************************************************************** + * + * USB descriptors + * + ****************************************************************************** + */ + +/** + * Locate USB interface association descriptor + * + * @v config Configuraton descriptor + * @v first First interface number + * @ret desc Interface association descriptor, or NULL if not found + */ +static struct usb_interface_association_descriptor * +usb_interface_association_descriptor ( struct usb_configuration_descriptor + *config, + unsigned int first ) { + struct usb_interface_association_descriptor *desc; + + /* Find a matching interface association descriptor */ + for_each_config_descriptor ( desc, config ) { + if ( ( desc->header.type == + USB_INTERFACE_ASSOCIATION_DESCRIPTOR ) && + ( desc->first == first ) ) + return desc; + } + return NULL; +} + +/** + * Locate USB interface descriptor + * + * @v config Configuraton descriptor + * @v interface Interface number + * @v alternate Alternate setting + * @ret desc Interface descriptor, or NULL if not found + */ +struct usb_interface_descriptor * +usb_interface_descriptor ( struct usb_configuration_descriptor *config, + unsigned int interface, unsigned int alternate ) { + struct usb_interface_descriptor *desc; + + /* Find a matching interface descriptor */ + for_each_config_descriptor ( desc, config ) { + if ( ( desc->header.type == USB_INTERFACE_DESCRIPTOR ) && + ( desc->interface == interface ) && + ( desc->alternate == alternate ) ) + return desc; + } + return NULL; +} + +/** + * Locate USB endpoint descriptor + * + * @v config Configuration descriptor + * @v interface Interface descriptor + * @v type Endpoint (internal) type + * @v index Endpoint index + * @ret desc Descriptor, or NULL if not found + */ +struct usb_endpoint_descriptor * +usb_endpoint_descriptor ( struct usb_configuration_descriptor *config, + struct usb_interface_descriptor *interface, + unsigned int type, unsigned int index ) { + struct usb_endpoint_descriptor *desc; + unsigned int attributes = ( type & USB_ENDPOINT_ATTR_TYPE_MASK ); + unsigned int direction = ( type & USB_DIR_IN ); + + /* Find a matching endpoint descriptor */ + for_each_interface_descriptor ( desc, config, interface ) { + if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) && + ( ( desc->attributes & + USB_ENDPOINT_ATTR_TYPE_MASK ) == attributes ) && + ( ( desc->endpoint & USB_DIR_IN ) == direction ) && + ( index-- == 0 ) ) + return desc; + } + return NULL; +} + +/** + * Locate USB endpoint companion descriptor + * + * @v config Configuration descriptor + * @v desc Endpoint descriptor + * @ret descx Companion descriptor, or NULL if not found + */ +struct usb_endpoint_companion_descriptor * +usb_endpoint_companion_descriptor ( struct usb_configuration_descriptor *config, + struct usb_endpoint_descriptor *desc ) { + struct usb_endpoint_companion_descriptor *descx; + + /* Get companion descriptor, if present */ + descx = container_of ( usb_next_descriptor ( &desc->header ), + struct usb_endpoint_companion_descriptor, + header ); + return ( ( usb_is_within_config ( config, &descx->header ) && + descx->header.type == USB_ENDPOINT_COMPANION_DESCRIPTOR ) + ? descx : NULL ); +} + +/****************************************************************************** + * + * USB endpoint + * + ****************************************************************************** + */ + +/** + * Get USB endpoint name (for debugging) + * + * @v ep USB endpoint + * @ret name Endpoint name + */ +const char * usb_endpoint_name ( struct usb_endpoint *ep ) { + static char buf[ 9 /* "EPxx OUT" + NUL */ ]; + unsigned int address = ep->address; + + snprintf ( buf, sizeof ( buf ), "EP%d%s", + ( address & USB_ENDPOINT_MAX ), + ( address ? + ( ( address & USB_ENDPOINT_IN ) ? " IN" : " OUT" ) : "" )); + return buf; +} + +/** + * Describe USB endpoint from device configuration + * + * @v ep USB endpoint + * @v config Configuration descriptor + * @v interface Interface descriptor + * @v type Endpoint (internal) type + * @v index Endpoint index + * @ret rc Return status code + */ +int usb_endpoint_described ( struct usb_endpoint *ep, + struct usb_configuration_descriptor *config, + struct usb_interface_descriptor *interface, + unsigned int type, unsigned int index ) { + struct usb_device *usb = ep->usb; + struct usb_port *port = usb->port; + struct usb_endpoint_descriptor *desc; + struct usb_endpoint_companion_descriptor *descx; + unsigned int sizes; + unsigned int burst; + unsigned int interval; + size_t mtu; + + /* Locate endpoint descriptor */ + desc = usb_endpoint_descriptor ( config, interface, type, index ); + if ( ! desc ) + return -ENOENT; + + /* Locate companion descriptor, if any */ + descx = usb_endpoint_companion_descriptor ( config, desc ); + + /* Calculate MTU and burst size */ + sizes = le16_to_cpu ( desc->sizes ); + mtu = USB_ENDPOINT_MTU ( sizes ); + burst = ( descx ? descx->burst : USB_ENDPOINT_BURST ( sizes ) ); + + /* Calculate interval */ + if ( ( type & USB_ENDPOINT_ATTR_TYPE_MASK ) == + USB_ENDPOINT_ATTR_INTERRUPT ) { + if ( port->speed >= USB_SPEED_HIGH ) { + /* 2^(desc->interval-1) is a microframe count */ + interval = ( 1 << ( desc->interval - 1 ) ); + } else { + /* desc->interval is a (whole) frame count */ + interval = ( desc->interval << 3 ); + } + } else { + /* desc->interval is a microframe count */ + interval = desc->interval; + } + + /* Describe endpoint */ + usb_endpoint_describe ( ep, desc->endpoint, desc->attributes, + mtu, burst, interval ); + return 0; +} + +/** + * Open USB endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +int usb_endpoint_open ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + unsigned int idx = USB_ENDPOINT_IDX ( ep->address ); + int rc; + + /* Populate host controller operations */ + ep->host = &usb->port->hub->bus->op->endpoint; + + /* Add to endpoint list */ + if ( usb->ep[idx] != NULL ) { + DBGC ( usb, "USB %s %s is already open\n", + usb->name, usb_endpoint_name ( ep ) ); + rc = -EALREADY; + goto err_already; + } + usb->ep[idx] = ep; + INIT_LIST_HEAD ( &ep->halted ); + + /* Open endpoint */ + if ( ( rc = ep->host->open ( ep ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not open: %s\n", usb->name, + usb_endpoint_name ( ep ), strerror ( rc ) ); + goto err_open; + } + ep->open = 1; + + DBGC2 ( usb, "USB %s %s opened with MTU %zd, burst %d, interval %d\n", + usb->name, usb_endpoint_name ( ep ), ep->mtu, ep->burst, + ep->interval ); + return 0; + + ep->open = 0; + ep->host->close ( ep ); + err_open: + usb->ep[idx] = NULL; + err_already: + if ( ep->max ) + usb_flush ( ep ); + return rc; +} + +/** + * Clear transaction translator (if applicable) + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int usb_endpoint_clear_tt ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + struct usb_port *tt; + int rc; + + /* Do nothing if this is a periodic endpoint */ + if ( ep->attributes & USB_ENDPOINT_ATTR_PERIODIC ) + return 0; + + /* Do nothing if this endpoint is not behind a transaction translator */ + tt = usb_transaction_translator ( usb ); + if ( ! tt ) + return 0; + + /* Clear transaction translator buffer */ + if ( ( rc = tt->hub->driver->clear_tt ( tt->hub, tt, ep ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not clear transaction translator: " + "%s\n", usb->name, usb_endpoint_name ( ep ), + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Close USB endpoint + * + * @v ep USB endpoint + */ +void usb_endpoint_close ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + unsigned int idx = USB_ENDPOINT_IDX ( ep->address ); + + /* Sanity checks */ + assert ( usb->ep[idx] == ep ); + + /* Close endpoint */ + ep->open = 0; + ep->host->close ( ep ); + assert ( ep->fill == 0 ); + + /* Remove from endpoint list */ + usb->ep[idx] = NULL; + list_del ( &ep->halted ); + + /* Discard any recycled buffers, if applicable */ + if ( ep->max ) + usb_flush ( ep ); + + /* Clear transaction translator, if applicable */ + usb_endpoint_clear_tt ( ep ); +} + +/** + * Reset USB endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int usb_endpoint_reset ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + unsigned int type; + int rc; + + /* Sanity check */ + assert ( ! list_empty ( &ep->halted ) ); + + /* Reset endpoint */ + if ( ( rc = ep->host->reset ( ep ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not reset: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + return rc; + } + + /* Clear transaction translator, if applicable */ + if ( ( rc = usb_endpoint_clear_tt ( ep ) ) != 0 ) + return rc; + + /* Clear endpoint halt, if applicable */ + type = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + if ( ( type != USB_ENDPOINT_ATTR_CONTROL ) && + ( ( rc = usb_clear_feature ( usb, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, + ep->address ) ) != 0 ) ) { + DBGC ( usb, "USB %s %s could not clear endpoint halt: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + return rc; + } + + /* Remove from list of halted endpoints */ + list_del ( &ep->halted ); + INIT_LIST_HEAD ( &ep->halted ); + + DBGC ( usb, "USB %s %s reset\n", + usb->name, usb_endpoint_name ( ep ) ); + return 0; +} + +/** + * Update endpoint MTU + * + * @v ep USB endpoint + * @v mtu New MTU + * @ret rc Return status code + */ +static int usb_endpoint_mtu ( struct usb_endpoint *ep, size_t mtu ) { + struct usb_device *usb = ep->usb; + int rc; + + /* Update MTU */ + ep->mtu = mtu; + if ( ( rc = ep->host->mtu ( ep ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not update MTU: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Enqueue USB message transfer + * + * @v ep USB endpoint + * @v request Request + * @v value Value parameter + * @v index Index parameter + * @v iobuf I/O buffer + * @ret rc Return status code + * + * The I/O buffer must have sufficient headroom to contain a setup + * packet. + */ +int usb_message ( struct usb_endpoint *ep, unsigned int request, + unsigned int value, unsigned int index, + struct io_buffer *iobuf ) { + struct usb_device *usb = ep->usb; + struct usb_port *port = usb->port; + struct usb_setup_packet *packet; + size_t len = iob_len ( iobuf ); + int rc; + + /* Sanity check */ + assert ( iob_headroom ( iobuf ) >= sizeof ( *packet ) ); + + /* Fail immediately if device has been unplugged */ + if ( port->speed == USB_SPEED_NONE ) + return -ENODEV; + + /* Reset endpoint if required */ + if ( ( ! list_empty ( &ep->halted ) ) && + ( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) ) + return rc; + + /* Zero input data buffer (if applicable) */ + if ( request & USB_DIR_IN ) + memset ( iobuf->data, 0, len ); + + /* Construct setup packet */ + packet = iob_push ( iobuf, sizeof ( *packet ) ); + packet->request = cpu_to_le16 ( request ); + packet->value = cpu_to_le16 ( value ); + packet->index = cpu_to_le16 ( index ); + packet->len = cpu_to_le16 ( len ); + + /* Enqueue message transfer */ + if ( ( rc = ep->host->message ( ep, iobuf ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not enqueue message transfer: " + "%s\n", usb->name, usb_endpoint_name ( ep ), + strerror ( rc ) ); + return rc; + } + + /* Increment fill level */ + ep->fill++; + + return 0; +} + +/** + * Enqueue USB stream transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v terminate Terminate using a short packet + * @ret rc Return status code + */ +int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int terminate ) { + struct usb_device *usb = ep->usb; + struct usb_port *port = usb->port; + int rc; + + /* Fail immediately if device has been unplugged */ + if ( port->speed == USB_SPEED_NONE ) + return -ENODEV; + + /* Reset endpoint if required */ + if ( ( ! list_empty ( &ep->halted ) ) && + ( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) ) + return rc; + + /* Enqueue stream transfer */ + if ( ( rc = ep->host->stream ( ep, iobuf, terminate ) ) != 0 ) { + DBGC ( usb, "USB %s %s could not enqueue stream transfer: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + return rc; + } + + /* Increment fill level */ + ep->fill++; + + return 0; +} + +/** + * Complete transfer (possibly with error) + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +void usb_complete_err ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int rc ) { + struct usb_device *usb = ep->usb; + + /* Decrement fill level */ + assert ( ep->fill > 0 ); + ep->fill--; + + /* Schedule reset, if applicable */ + if ( ( rc != 0 ) && ep->open ) { + DBGC ( usb, "USB %s %s completion failed: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + list_del ( &ep->halted ); + list_add_tail ( &ep->halted, &usb_halted ); + } + + /* Report completion */ + ep->driver->complete ( ep, iobuf, rc ); +} + +/****************************************************************************** + * + * Endpoint refilling + * + ****************************************************************************** + */ + +/** + * Prefill endpoint recycled buffer list + * + * @v ep USB endpoint + * @ret rc Return status code + */ +int usb_prefill ( struct usb_endpoint *ep ) { + struct io_buffer *iobuf; + size_t len = ( ep->len ? ep->len : ep->mtu ); + unsigned int fill; + int rc; + + /* Sanity checks */ + assert ( ep->fill == 0 ); + assert ( ep->max > 0 ); + assert ( list_empty ( &ep->recycled ) ); + + /* Fill recycled buffer list */ + for ( fill = 0 ; fill < ep->max ; fill++ ) { + + /* Allocate I/O buffer */ + iobuf = alloc_iob ( len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Add to recycled buffer list */ + list_add_tail ( &iobuf->list, &ep->recycled ); + } + + return 0; + + err_alloc: + usb_flush ( ep ); + return rc; +} + +/** + * Refill endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +int usb_refill ( struct usb_endpoint *ep ) { + struct io_buffer *iobuf; + size_t len = ( ep->len ? ep->len : ep->mtu ); + int rc; + + /* Sanity checks */ + assert ( ep->open ); + assert ( ep->max > 0 ); + + /* Refill endpoint */ + while ( ep->fill < ep->max ) { + + /* Get or allocate buffer */ + if ( list_empty ( &ep->recycled ) ) { + /* Recycled buffer list is empty; allocate new buffer */ + iobuf = alloc_iob ( len ); + if ( ! iobuf ) + return -ENOMEM; + } else { + /* Get buffer from recycled buffer list */ + iobuf = list_first_entry ( &ep->recycled, + struct io_buffer, list ); + assert ( iobuf != NULL ); + list_del ( &iobuf->list ); + } + + /* Reset buffer to maximum size */ + assert ( iob_len ( iobuf ) <= len ); + iob_put ( iobuf, ( len - iob_len ( iobuf ) ) ); + + /* Enqueue buffer */ + if ( ( rc = usb_stream ( ep, iobuf, 0 ) ) != 0 ) { + list_add ( &iobuf->list, &ep->recycled ); + return rc; + } + } + + return 0; +} + +/** + * Discard endpoint recycled buffer list + * + * @v ep USB endpoint + */ +void usb_flush ( struct usb_endpoint *ep ) { + struct io_buffer *iobuf; + struct io_buffer *tmp; + + /* Sanity checks */ + assert ( ! ep->open ); + assert ( ep->max > 0 ); + + /* Free all I/O buffers */ + list_for_each_entry_safe ( iobuf, tmp, &ep->recycled, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } +} + +/****************************************************************************** + * + * Control endpoint + * + ****************************************************************************** + */ + +/** USB control transfer pseudo-header */ +struct usb_control_pseudo_header { + /** Completion status */ + int rc; +}; + +/** + * Complete USB control transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void usb_control_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct usb_device *usb = ep->usb; + struct usb_control_pseudo_header *pshdr; + + /* Record completion status in buffer */ + pshdr = iob_push ( iobuf, sizeof ( *pshdr ) ); + pshdr->rc = rc; + + /* Add to list of completed I/O buffers */ + list_add_tail ( &iobuf->list, &usb->complete ); +} + +/** USB control endpoint driver operations */ +static struct usb_endpoint_driver_operations usb_control_operations = { + .complete = usb_control_complete, +}; + +/** + * Issue USB control transaction + * + * @v usb USB device + * @v request Request + * @v value Value parameter + * @v index Index parameter + * @v data Data buffer (if any) + * @v len Length of data + * @ret rc Return status code + */ +int usb_control ( struct usb_device *usb, unsigned int request, + unsigned int value, unsigned int index, void *data, + size_t len ) { + struct usb_bus *bus = usb->port->hub->bus; + struct usb_endpoint *ep = &usb->control; + struct io_buffer *iobuf; + struct io_buffer *cmplt; + union { + struct usb_setup_packet setup; + struct usb_control_pseudo_header pshdr; + } *headroom; + struct usb_control_pseudo_header *pshdr; + unsigned int i; + int rc; + + /* Allocate I/O buffer */ + iobuf = alloc_iob ( sizeof ( *headroom ) + len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + iob_reserve ( iobuf, sizeof ( *headroom ) ); + iob_put ( iobuf, len ); + if ( request & USB_DIR_IN ) { + memset ( data, 0, len ); + } else { + memcpy ( iobuf->data, data, len ); + } + + /* Enqueue message */ + if ( ( rc = usb_message ( ep, request, value, index, iobuf ) ) != 0 ) + goto err_message; + + /* Wait for completion */ + for ( i = 0 ; i < USB_CONTROL_MAX_WAIT_MS ; i++ ) { + + /* Poll bus */ + usb_poll ( bus ); + + /* Check for completion */ + while ( ( cmplt = list_first_entry ( &usb->complete, + struct io_buffer, + list ) ) ) { + + /* Remove from completion list */ + list_del ( &cmplt->list ); + + /* Extract and strip completion status */ + pshdr = cmplt->data; + iob_pull ( cmplt, sizeof ( *pshdr ) ); + rc = pshdr->rc; + + /* Discard stale completions */ + if ( cmplt != iobuf ) { + DBGC ( usb, "USB %s stale control completion: " + "%s\n", usb->name, strerror ( rc ) ); + DBGC_HDA ( usb, 0, cmplt->data, + iob_len ( cmplt ) ); + free_iob ( cmplt ); + continue; + } + + /* Fail immediately if completion was in error */ + if ( rc != 0 ) { + DBGC ( usb, "USB %s control %04x:%04x:%04x " + "failed: %s\n", usb->name, request, + value, index, strerror ( rc ) ); + free_iob ( cmplt ); + return rc; + } + + /* Copy completion to data buffer, if applicable */ + assert ( iob_len ( cmplt ) <= len ); + if ( request & USB_DIR_IN ) + memcpy ( data, cmplt->data, iob_len ( cmplt ) ); + free_iob ( cmplt ); + return 0; + } + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( usb, "USB %s timed out waiting for control %04x:%04x:%04x\n", + usb->name, request, value, index ); + return -ETIMEDOUT; + + err_message: + free_iob ( iobuf ); + err_alloc: + return rc; +} + +/** + * Get USB string descriptor + * + * @v usb USB device + * @v index String index + * @v language Language ID + * @v buf Data buffer + * @v len Length of buffer + * @ret len String length (excluding NUL), or negative error + */ +int usb_get_string_descriptor ( struct usb_device *usb, unsigned int index, + unsigned int language, char *buf, size_t len ) { + size_t max = ( len ? ( len - 1 /* NUL */ ) : 0 ); + struct { + struct usb_descriptor_header header; + uint16_t character[max]; + } __attribute__ (( packed )) *desc; + unsigned int actual; + unsigned int i; + int rc; + + /* Allocate buffer for string */ + desc = malloc ( sizeof ( *desc ) ); + if ( ! desc ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Get descriptor */ + if ( ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, index, + language, &desc->header, + sizeof ( *desc ) ) ) != 0 ) + goto err_get_descriptor; + + /* Copy to buffer */ + actual = ( ( desc->header.len - sizeof ( desc->header ) ) / + sizeof ( desc->character[0] ) ); + for ( i = 0 ; ( ( i < actual ) && ( i < max ) ) ; i++ ) + buf[i] = le16_to_cpu ( desc->character[i] ); + if ( len ) + buf[i] = '\0'; + + /* Free buffer */ + free ( desc ); + + return actual; + + err_get_descriptor: + free ( desc ); + err_alloc: + return rc; +} + +/****************************************************************************** + * + * USB device driver + * + ****************************************************************************** + */ + +/** + * Describe USB function + * + * @v func USB function + * @v config Configuration descriptor + * @v first First interface number + * @ret rc Return status code + */ +static int usb_function ( struct usb_function *func, + struct usb_configuration_descriptor *config, + unsigned int first ) { + struct usb_device *usb = func->usb; + struct usb_interface_association_descriptor *association; + struct usb_interface_descriptor *interface; + struct cdc_union_descriptor *cdc_union; + unsigned int i; + + /* First, look for an interface association descriptor */ + association = usb_interface_association_descriptor ( config, first ); + if ( association ) { + + /* Sanity check */ + if ( association->count > config->interfaces ) { + DBGC ( usb, "USB %s has invalid association [%d-%d)\n", + func->name, association->first, + ( association->first + association->count ) ); + return -ERANGE; + } + + /* Describe function */ + memcpy ( &func->class, &association->class, + sizeof ( func->class ) ); + func->count = association->count; + for ( i = 0 ; i < association->count ; i++ ) + func->interface[i] = ( association->first + i ); + return 0; + } + + /* Next, look for an interface descriptor */ + interface = usb_interface_descriptor ( config, first, 0 ); + if ( ! interface ) { + DBGC ( usb, "USB %s has no interface descriptor\n", + func->name ); + return -ENOENT; + } + + /* Describe function */ + memcpy ( &func->class, &interface->class, sizeof ( func->class ) ); + func->count = 1; + func->interface[0] = first; + + /* Look for a CDC union descriptor, if applicable */ + if ( ( func->class.class == USB_CLASS_CDC ) && + ( cdc_union = cdc_union_descriptor ( config, interface ) ) ) { + + /* Determine interface count */ + func->count = ( ( cdc_union->header.len - + offsetof ( typeof ( *cdc_union ), + interface[0] ) ) / + sizeof ( cdc_union->interface[0] ) ); + if ( func->count > config->interfaces ) { + DBGC ( usb, "USB %s has invalid union functional " + "descriptor with %d interfaces\n", + func->name, func->count ); + return -ERANGE; + } + + /* Describe function */ + for ( i = 0 ; i < func->count ; i++ ) + func->interface[i] = cdc_union->interface[i]; + + return 0; + } + + return 0; +} + +/** + * Check for a USB device ID match + * + * @v func USB function + * @v id Device ID + * @ret matches Device ID matches + */ +static int +usb_device_id_matches ( struct usb_function *func, struct usb_device_id *id ) { + + return ( ( ( id->vendor == func->dev.desc.vendor ) || + ( id->vendor == USB_ANY_ID ) ) && + ( ( id->product == func->dev.desc.device ) || + ( id->product == USB_ANY_ID ) ) && + ( id->class.class == func->class.class ) && + ( id->class.subclass == func->class.subclass ) && + ( id->class.protocol == func->class.protocol ) ); +} + +/** + * Probe USB device driver + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int usb_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct usb_driver *driver; + struct usb_device_id *id; + unsigned int i; + int rc; + + /* Look for a matching driver */ + for_each_table_entry ( driver, USB_DRIVERS ) { + for ( i = 0 ; i < driver->id_count ; i++ ) { + + /* Check for a matching ID */ + id = &driver->ids[i]; + if ( ! usb_device_id_matches ( func, id ) ) + continue; + + /* Probe driver */ + if ( ( rc = driver->probe ( func, config ) ) != 0 ) { + DBGC ( usb, "USB %s failed to probe driver %s: " + "%s\n", func->name, id->name, + strerror ( rc ) ); + /* Continue trying other drivers */ + continue; + } + + /* Record driver */ + func->driver = driver; + func->dev.driver_name = id->name; + return 0; + } + } + + /* No driver found */ + DBGC ( usb, "USB %s %04x:%04x class %d:%d:%d has no driver\n", + func->name, func->dev.desc.vendor, func->dev.desc.device, + func->class.class, func->class.subclass, func->class.protocol ); + return -ENOENT; +} + +/** + * Remove USB device driver + * + * @v func USB function + */ +static void usb_remove ( struct usb_function *func ) { + + /* Remove driver */ + func->driver->remove ( func ); +} + +/** + * Probe all USB device drivers + * + * @v usb USB device + * @v config Configuration descriptor + */ +static void +usb_probe_all ( struct usb_device *usb, + struct usb_configuration_descriptor *config ) { + struct usb_bus *bus = usb->port->hub->bus; + struct usb_function *func; + uint8_t used[config->interfaces]; + unsigned int first; + unsigned int i; + int rc; + + /* Identify each function in turn */ + memset ( used, 0, sizeof ( used ) ); + for ( first = 0 ; first < config->interfaces ; first++ ) { + + /* Skip interfaces already used */ + if ( used[first] ) + continue; + + /* Allocate and initialise structure */ + func = zalloc ( sizeof ( *func ) + + ( config->interfaces * + sizeof ( func->interface[0] ) ) ); + if ( ! func ) + goto err_alloc; + func->name = func->dev.name; + func->usb = usb; + func->dev.desc.bus_type = BUS_TYPE_USB; + func->dev.desc.location = usb->address; + func->dev.desc.vendor = le16_to_cpu ( usb->device.vendor ); + func->dev.desc.device = le16_to_cpu ( usb->device.product ); + snprintf ( func->dev.name, sizeof ( func->dev.name ), + "%s-%d.%d", usb->name, config->config, first ); + INIT_LIST_HEAD ( &func->dev.children ); + func->dev.parent = bus->dev; + + /* Identify function */ + if ( ( rc = usb_function ( func, config, first ) ) != 0 ) + goto err_function; + assert ( func->count <= config->interfaces ); + + /* Mark interfaces as used */ + for ( i = 0 ; i < func->count ; i++ ) { + if ( func->interface[i] >= config->interfaces ) { + DBGC ( usb, "USB %s has invalid interface %d\n", + func->name, func->interface[i] ); + goto err_interface; + } + used[ func->interface[i] ] = 1; + } + + /* Probe device driver */ + if ( ( rc = usb_probe ( func, config ) ) != 0 ) + goto err_probe; + DBGC ( usb, "USB %s %04x:%04x class %d:%d:%d interfaces ", + func->name, func->dev.desc.vendor, func->dev.desc.device, + func->class.class, func->class.subclass, + func->class.protocol ); + for ( i = 0 ; i < func->count ; i++ ) + DBGC ( usb, "%s%d", ( i ? "," : "" ), + func->interface[i] ); + DBGC ( usb, " using driver %s\n", func->dev.driver_name ); + + /* Add to list of functions */ + list_add ( &func->list, &usb->functions ); + + /* Add to device hierarchy */ + list_add_tail ( &func->dev.siblings, &bus->dev->children ); + + continue; + + list_del ( &func->dev.siblings ); + list_del ( &func->list ); + usb_remove ( func ); + err_probe: + free ( func ); + err_alloc: + err_interface: + err_function: + /* Continue registering other functions */ + continue; + } +} + +/** + * Remove all device drivers + * + * @v usb USB device + */ +static void usb_remove_all ( struct usb_device *usb ) { + struct usb_function *func; + struct usb_function *tmp; + + /* Remove all functions */ + list_for_each_entry_safe ( func, tmp, &usb->functions, list ) { + + /* Remove device driver */ + usb_remove ( func ); + + /* Remove from device hierarchy */ + assert ( list_empty ( &func->dev.children ) ); + list_del ( &func->dev.siblings ); + + /* Remove from list of functions */ + list_del ( &func->list ); + + /* Free function */ + free ( func ); + } +} + +/** + * Select USB device configuration + * + * @v usb USB device + * @v index Configuration index + * @ret rc Return status code + */ +static int usb_configure ( struct usb_device *usb, unsigned int index ) { + struct usb_configuration_descriptor partial; + struct usb_configuration_descriptor *config; + size_t len; + int rc; + + /* Read first part of configuration descriptor to get size */ + if ( ( rc = usb_get_config_descriptor ( usb, index, &partial, + sizeof ( partial ) ) ) != 0 ) { + DBGC ( usb, "USB %s could not get configuration descriptor %d: " + "%s\n", usb->name, index, strerror ( rc ) ); + goto err_get_partial; + } + len = le16_to_cpu ( partial.len ); + if ( len < sizeof ( partial ) ) { + DBGC ( usb, "USB %s underlength configuraton descriptor %d\n", + usb->name, index ); + rc = -EINVAL; + goto err_partial_len; + } + + /* Allocate buffer for whole configuration descriptor */ + config = malloc ( len ); + if ( ! config ) { + rc = -ENOMEM; + goto err_alloc_config; + } + + /* Read whole configuration descriptor */ + if ( ( rc = usb_get_config_descriptor ( usb, index, config, + len ) ) != 0 ) { + DBGC ( usb, "USB %s could not get configuration descriptor %d: " + "%s\n", usb->name, index, strerror ( rc ) ); + goto err_get_config_descriptor; + } + if ( config->len != partial.len ) { + DBGC ( usb, "USB %s bad configuration descriptor %d length\n", + usb->name, index ); + rc = -EINVAL; + goto err_config_len; + } + + /* Set configuration */ + if ( ( rc = usb_set_configuration ( usb, config->config ) ) != 0){ + DBGC ( usb, "USB %s could not set configuration %d: %s\n", + usb->name, config->config, strerror ( rc ) ); + goto err_set_configuration; + } + + /* Probe USB device drivers */ + usb_probe_all ( usb, config ); + + /* Free configuration descriptor */ + free ( config ); + + return 0; + + usb_remove_all ( usb ); + usb_set_configuration ( usb, 0 ); + err_set_configuration: + err_config_len: + err_get_config_descriptor: + free ( config ); + err_alloc_config: + err_partial_len: + err_get_partial: + return rc; +} + +/** + * Clear USB device configuration + * + * @v usb USB device + */ +static void usb_deconfigure ( struct usb_device *usb ) { + unsigned int i; + + /* Remove device drivers */ + usb_remove_all ( usb ); + + /* Sanity checks */ + for ( i = 0 ; i < ( sizeof ( usb->ep ) / sizeof ( usb->ep[0] ) ) ; i++){ + if ( i != USB_ENDPOINT_IDX ( USB_EP0_ADDRESS ) ) + assert ( usb->ep[i] == NULL ); + } + + /* Clear device configuration */ + usb_set_configuration ( usb, 0 ); +} + +/** + * Find and select a supported USB device configuration + * + * @v usb USB device + * @ret rc Return status code + */ +static int usb_configure_any ( struct usb_device *usb ) { + unsigned int index; + int rc = -ENOENT; + + /* Attempt all configuration indexes */ + for ( index = 0 ; index < usb->device.configurations ; index++ ) { + + /* Attempt this configuration index */ + if ( ( rc = usb_configure ( usb, index ) ) != 0 ) + continue; + + /* If we have no drivers, then try the next configuration */ + if ( list_empty ( &usb->functions ) ) { + rc = -ENOTSUP; + usb_deconfigure ( usb ); + continue; + } + + return 0; + } + + return rc; +} + +/****************************************************************************** + * + * USB device + * + ****************************************************************************** + */ + +/** + * Allocate USB device + * + * @v port USB port + * @ret usb USB device, or NULL on allocation failure + */ +static struct usb_device * alloc_usb ( struct usb_port *port ) { + struct usb_hub *hub = port->hub; + struct usb_bus *bus = hub->bus; + struct usb_device *usb; + + /* Allocate and initialise structure */ + usb = zalloc ( sizeof ( *usb ) ); + if ( ! usb ) + return NULL; + snprintf ( usb->name, sizeof ( usb->name ), "%s%c%d", hub->name, + ( hub->usb ? '.' : '-' ), port->address ); + usb->port = port; + INIT_LIST_HEAD ( &usb->functions ); + usb->host = &bus->op->device; + usb_endpoint_init ( &usb->control, usb, &usb_control_operations ); + INIT_LIST_HEAD ( &usb->complete ); + + return usb; +} + +/** + * Register USB device + * + * @v usb USB device + * @ret rc Return status code + */ +static int register_usb ( struct usb_device *usb ) { + struct usb_port *port = usb->port; + struct usb_hub *hub = port->hub; + struct usb_bus *bus = hub->bus; + unsigned int protocol; + size_t mtu; + int rc; + + /* Add to port */ + if ( port->usb != NULL ) { + DBGC ( hub, "USB hub %s port %d is already registered to %s\n", + hub->name, port->address, port->usb->name ); + rc = -EALREADY; + goto err_already; + } + port->usb = usb; + + /* Add to bus device list */ + list_add_tail ( &usb->list, &bus->devices ); + + /* Enable device */ + if ( ( rc = hub->driver->enable ( hub, port ) ) != 0 ) { + DBGC ( hub, "USB hub %s port %d could not enable: %s\n", + hub->name, port->address, strerror ( rc ) ); + goto err_enable; + } + + /* Allow recovery interval since port may have been reset */ + mdelay ( USB_RESET_RECOVER_DELAY_MS ); + + /* Get device speed */ + if ( ( rc = hub->driver->speed ( hub, port ) ) != 0 ) { + DBGC ( hub, "USB hub %s port %d could not get speed: %s\n", + hub->name, port->address, strerror ( rc ) ); + goto err_speed; + } + DBGC2 ( usb, "USB %s attached as %s-speed device\n", + usb->name, usb_speed_name ( port->speed ) ); + + /* Open device */ + if ( ( rc = usb->host->open ( usb ) ) != 0 ) { + DBGC ( usb, "USB %s could not open: %s\n", + usb->name, strerror ( rc ) ); + goto err_open; + } + + /* Describe control endpoint */ + mtu = USB_EP0_DEFAULT_MTU ( port->speed ); + usb_endpoint_describe ( &usb->control, USB_EP0_ADDRESS, + USB_EP0_ATTRIBUTES, mtu, USB_EP0_BURST, + USB_EP0_INTERVAL ); + + /* Open control endpoint */ + if ( ( rc = usb_endpoint_open ( &usb->control ) ) != 0 ) + goto err_open_control; + assert ( usb_endpoint ( usb, USB_EP0_ADDRESS ) == &usb->control ); + + /* Assign device address */ + if ( ( rc = usb->host->address ( usb ) ) != 0 ) { + DBGC ( usb, "USB %s could not set address: %s\n", + usb->name, strerror ( rc ) ); + goto err_address; + } + DBGC2 ( usb, "USB %s assigned address %d\n", usb->name, usb->address ); + + /* Allow recovery interval after Set Address command */ + mdelay ( USB_SET_ADDRESS_RECOVER_DELAY_MS ); + + /* Read first part of device descriptor to get EP0 MTU */ + if ( ( rc = usb_get_mtu ( usb, &usb->device ) ) != 0 ) { + DBGC ( usb, "USB %s could not get MTU: %s\n", + usb->name, strerror ( rc ) ); + goto err_get_mtu; + } + + /* Calculate EP0 MTU */ + protocol = le16_to_cpu ( usb->device.protocol ); + mtu = ( ( protocol < USB_PROTO_3_0 ) ? + usb->device.mtu : ( 1 << usb->device.mtu ) ); + DBGC2 ( usb, "USB %s has control MTU %zd (guessed %zd)\n", + usb->name, mtu, usb->control.mtu ); + + /* Update MTU */ + if ( ( rc = usb_endpoint_mtu ( &usb->control, mtu ) ) != 0 ) + goto err_mtu; + + /* Read whole device descriptor */ + if ( ( rc = usb_get_device_descriptor ( usb, &usb->device ) ) != 0 ) { + DBGC ( usb, "USB %s could not get device descriptor: %s\n", + usb->name, strerror ( rc ) ); + goto err_get_device_descriptor; + } + DBGC ( usb, "USB %s addr %d %04x:%04x class %d:%d:%d (v%s, %s-speed, " + "MTU %zd)\n", usb->name, usb->address, + le16_to_cpu ( usb->device.vendor ), + le16_to_cpu ( usb->device.product ), usb->device.class.class, + usb->device.class.subclass, usb->device.class.protocol, + usb_bcd ( le16_to_cpu ( usb->device.protocol ) ), + usb_speed_name ( port->speed ), usb->control.mtu ); + + /* Configure device */ + if ( ( rc = usb_configure_any ( usb ) ) != 0 ) + goto err_configure_any; + + return 0; + + usb_deconfigure ( usb ); + err_configure_any: + err_get_device_descriptor: + err_mtu: + err_get_mtu: + err_address: + usb_endpoint_close ( &usb->control ); + err_open_control: + usb->host->close ( usb ); + err_open: + err_speed: + hub->driver->disable ( hub, port ); + err_enable: + list_del ( &usb->list ); + port->usb = NULL; + err_already: + return rc; +} + +/** + * Unregister USB device + * + * @v usb USB device + */ +static void unregister_usb ( struct usb_device *usb ) { + struct usb_port *port = usb->port; + struct usb_hub *hub = port->hub; + struct io_buffer *iobuf; + struct io_buffer *tmp; + + /* Sanity checks */ + assert ( port->usb == usb ); + + /* Clear device configuration */ + usb_deconfigure ( usb ); + + /* Close control endpoint */ + usb_endpoint_close ( &usb->control ); + + /* Discard any stale control completions */ + list_for_each_entry_safe ( iobuf, tmp, &usb->complete, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } + + /* Close device */ + usb->host->close ( usb ); + + /* Disable port */ + hub->driver->disable ( hub, port ); + + /* Remove from bus device list */ + list_del ( &usb->list ); + + /* Remove from port */ + port->usb = NULL; +} + +/** + * Free USB device + * + * @v usb USB device + */ +static void free_usb ( struct usb_device *usb ) { + unsigned int i; + + /* Sanity checks */ + for ( i = 0 ; i < ( sizeof ( usb->ep ) / sizeof ( usb->ep[0] ) ) ; i++ ) + assert ( usb->ep[i] == NULL ); + assert ( list_empty ( &usb->functions ) ); + assert ( list_empty ( &usb->complete ) ); + + /* Free device */ + free ( usb ); +} + +/****************************************************************************** + * + * USB device hotplug event handling + * + ****************************************************************************** + */ + +/** + * Handle newly attached USB device + * + * @v port USB port + * @ret rc Return status code + */ +static int usb_attached ( struct usb_port *port ) { + struct usb_device *usb; + int rc; + + /* Mark port as attached */ + port->attached = 1; + + /* Sanity checks */ + assert ( port->usb == NULL ); + + /* Allocate USB device */ + usb = alloc_usb ( port ); + if ( ! usb ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Register USB device */ + if ( ( rc = register_usb ( usb ) ) != 0 ) + goto err_register; + + return 0; + + unregister_usb ( usb ); + err_register: + free_usb ( usb ); + err_alloc: + return rc; +} + +/** + * Handle newly detached USB device + * + * @v port USB port + */ +static void usb_detached ( struct usb_port *port ) { + struct usb_device *usb = port->usb; + + /* Mark port as detached */ + port->attached = 0; + + /* Do nothing if we have no USB device */ + if ( ! usb ) + return; + + /* Unregister USB device */ + unregister_usb ( usb ); + + /* Free USB device */ + free_usb ( usb ); +} + +/** + * Handle newly attached or detached USB device + * + * @v port USB port + * @ret rc Return status code + */ +static int usb_hotplugged ( struct usb_port *port ) { + struct usb_hub *hub = port->hub; + int rc; + + /* Get current port speed */ + if ( ( rc = hub->driver->speed ( hub, port ) ) != 0 ) { + DBGC ( hub, "USB hub %s port %d could not get speed: %s\n", + hub->name, port->address, strerror ( rc ) ); + goto err_speed; + } + + /* Detach device, if applicable */ + if ( port->attached && ( port->disconnected || ! port->speed ) ) + usb_detached ( port ); + + /* Attach device, if applicable */ + if ( port->speed && ( ! port->attached ) && + ( ( rc = usb_attached ( port ) ) != 0 ) ) + goto err_attached; + + err_attached: + err_speed: + /* Clear any recorded disconnections */ + port->disconnected = 0; + return rc; +} + +/****************************************************************************** + * + * USB process + * + ****************************************************************************** + */ + +/** + * Report port status change + * + * @v port USB port + */ +void usb_port_changed ( struct usb_port *port ) { + + /* Record hub port status change */ + list_del ( &port->changed ); + list_add_tail ( &port->changed, &usb_changed ); +} + +/** + * Handle newly attached or detached USB device + * + */ +static void usb_hotplug ( void ) { + struct usb_port *port; + + /* Handle any changed ports, allowing for the fact that the + * port list may change as we perform hotplug actions. + */ + while ( ! list_empty ( &usb_changed ) ) { + + /* Get first changed port */ + port = list_first_entry ( &usb_changed, struct usb_port, + changed ); + assert ( port != NULL ); + + /* Remove from list of changed ports */ + list_del ( &port->changed ); + INIT_LIST_HEAD ( &port->changed ); + + /* Perform appropriate hotplug action */ + usb_hotplugged ( port ); + } +} + +/** + * USB process + * + * @v process USB process + */ +static void usb_step ( struct process *process __unused ) { + struct usb_bus *bus; + struct usb_endpoint *ep; + + /* Poll all buses */ + for_each_usb_bus ( bus ) + usb_poll ( bus ); + + /* Attempt to reset first halted endpoint in list, if any. We + * do not attempt to process the complete list, since this + * would require extra code to allow for the facts that the + * halted endpoint list may change as we do so, and that + * resetting an endpoint may fail. + */ + if ( ( ep = list_first_entry ( &usb_halted, struct usb_endpoint, + halted ) ) != NULL ) + usb_endpoint_reset ( ep ); + + /* Handle any changed ports */ + usb_hotplug(); +} + +/** USB process */ +PERMANENT_PROCESS ( usb_process, usb_step ); + +/****************************************************************************** + * + * USB hub + * + ****************************************************************************** + */ + +/** + * Allocate USB hub + * + * @v bus USB bus + * @v usb Underlying USB device, if any + * @v ports Number of ports + * @v driver Hub driver operations + * @ret hub USB hub, or NULL on allocation failure + */ +struct usb_hub * alloc_usb_hub ( struct usb_bus *bus, struct usb_device *usb, + unsigned int ports, + struct usb_hub_driver_operations *driver ) { + struct usb_hub *hub; + struct usb_port *port; + unsigned int i; + + /* Allocate and initialise structure */ + hub = zalloc ( sizeof ( *hub ) + ( ports * sizeof ( hub->port[0] ) ) ); + if ( ! hub ) + return NULL; + hub->name = ( usb ? usb->name : bus->name ); + hub->bus = bus; + hub->usb = usb; + if ( usb ) + hub->protocol = usb->port->protocol; + hub->ports = ports; + hub->driver = driver; + hub->host = &bus->op->hub; + + /* Initialise port list */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + port->hub = hub; + port->address = i; + if ( usb ) + port->protocol = usb->port->protocol; + INIT_LIST_HEAD ( &port->changed ); + } + + return hub; +} + +/** + * Register USB hub + * + * @v hub USB hub + * @ret rc Return status code + */ +int register_usb_hub ( struct usb_hub *hub ) { + struct usb_bus *bus = hub->bus; + struct usb_port *port; + unsigned int i; + int rc; + + /* Add to hub list */ + list_add_tail ( &hub->list, &bus->hubs ); + + /* Open hub (host controller) */ + if ( ( rc = hub->host->open ( hub ) ) != 0 ) { + DBGC ( hub, "USB hub %s could not open: %s\n", + hub->name, strerror ( rc ) ); + goto err_host_open; + } + + /* Open hub (driver) */ + if ( ( rc = hub->driver->open ( hub ) ) != 0 ) { + DBGC ( hub, "USB hub %s could not open: %s\n", + hub->name, strerror ( rc ) ); + goto err_driver_open; + } + + /* Delay to allow ports to stabilise */ + mdelay ( USB_PORT_DELAY_MS ); + + /* Mark all ports as changed */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + usb_port_changed ( port ); + } + + /* Some hubs seem to defer reporting device connections until + * their interrupt endpoint is polled for the first time. + * Poll the bus once now in order to pick up any such + * connections. + */ + usb_poll ( bus ); + + return 0; + + hub->driver->close ( hub ); + err_driver_open: + hub->host->close ( hub ); + err_host_open: + list_del ( &hub->list ); + return rc; +} + +/** + * Unregister USB hub + * + * @v hub USB hub + */ +void unregister_usb_hub ( struct usb_hub *hub ) { + struct usb_port *port; + unsigned int i; + + /* Detach all devices */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + if ( port->attached ) + usb_detached ( port ); + } + + /* Close hub (driver) */ + hub->driver->close ( hub ); + + /* Close hub (host controller) */ + hub->host->close ( hub ); + + /* Cancel any pending port status changes */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + list_del ( &port->changed ); + INIT_LIST_HEAD ( &port->changed ); + } + + /* Remove from hub list */ + list_del ( &hub->list ); +} + +/** + * Free USB hub + * + * @v hub USB hub + */ +void free_usb_hub ( struct usb_hub *hub ) { + struct usb_port *port; + unsigned int i; + + /* Sanity checks */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + assert ( ! port->attached ); + assert ( port->usb == NULL ); + assert ( list_empty ( &port->changed ) ); + } + + /* Free hub */ + free ( hub ); +} + +/****************************************************************************** + * + * USB bus + * + ****************************************************************************** + */ + +/** + * Allocate USB bus + * + * @v dev Underlying hardware device + * @v ports Number of root hub ports + * @v mtu Largest transfer allowed on the bus + * @v op Host controller operations + * @ret bus USB bus, or NULL on allocation failure + */ +struct usb_bus * alloc_usb_bus ( struct device *dev, unsigned int ports, + size_t mtu, struct usb_host_operations *op ) { + struct usb_bus *bus; + + /* Allocate and initialise structure */ + bus = zalloc ( sizeof ( *bus ) ); + if ( ! bus ) + goto err_alloc_bus; + bus->name = dev->name; + bus->dev = dev; + bus->mtu = mtu; + bus->op = op; + INIT_LIST_HEAD ( &bus->devices ); + INIT_LIST_HEAD ( &bus->hubs ); + bus->host = &bus->op->bus; + + /* Allocate root hub */ + bus->hub = alloc_usb_hub ( bus, NULL, ports, &op->root ); + if ( ! bus->hub ) + goto err_alloc_hub; + + return bus; + + free_usb_hub ( bus->hub ); + err_alloc_hub: + free ( bus ); + err_alloc_bus: + return NULL; +} + +/** + * Register USB bus + * + * @v bus USB bus + * @ret rc Return status code + */ +int register_usb_bus ( struct usb_bus *bus ) { + int rc; + + /* Sanity checks */ + assert ( bus->hub != NULL ); + + /* Open bus */ + if ( ( rc = bus->host->open ( bus ) ) != 0 ) + goto err_open; + + /* Add to list of USB buses */ + list_add_tail ( &bus->list, &usb_buses ); + + /* Register root hub */ + if ( ( rc = register_usb_hub ( bus->hub ) ) != 0 ) + goto err_register_hub; + + /* Attach any devices already present */ + usb_hotplug(); + + return 0; + + unregister_usb_hub ( bus->hub ); + err_register_hub: + list_del ( &bus->list ); + bus->host->close ( bus ); + err_open: + return rc; +} + +/** + * Unregister USB bus + * + * @v bus USB bus + */ +void unregister_usb_bus ( struct usb_bus *bus ) { + + /* Sanity checks */ + assert ( bus->hub != NULL ); + + /* Unregister root hub */ + unregister_usb_hub ( bus->hub ); + + /* Remove from list of USB buses */ + list_del ( &bus->list ); + + /* Close bus */ + bus->host->close ( bus ); + + /* Sanity checks */ + assert ( list_empty ( &bus->devices ) ); + assert ( list_empty ( &bus->hubs ) ); +} + +/** + * Free USB bus + * + * @v bus USB bus + */ +void free_usb_bus ( struct usb_bus *bus ) { + struct usb_endpoint *ep; + struct usb_port *port; + + /* Sanity checks */ + assert ( list_empty ( &bus->devices ) ); + assert ( list_empty ( &bus->hubs ) ); + list_for_each_entry ( ep, &usb_halted, halted ) + assert ( ep->usb->port->hub->bus != bus ); + list_for_each_entry ( port, &usb_changed, changed ) + assert ( port->hub->bus != bus ); + + /* Free root hub */ + free_usb_hub ( bus->hub ); + + /* Free bus */ + free ( bus ); +} + +/** + * Find USB bus by device location + * + * @v bus_type Bus type + * @v location Bus location + * @ret bus USB bus, or NULL + */ +struct usb_bus * find_usb_bus_by_location ( unsigned int bus_type, + unsigned int location ) { + struct usb_bus *bus; + + for_each_usb_bus ( bus ) { + if ( ( bus->dev->desc.bus_type == bus_type ) && + ( bus->dev->desc.location == location ) ) + return bus; + } + + return NULL; +} + +/****************************************************************************** + * + * USB address assignment + * + ****************************************************************************** + */ + +/** + * Allocate device address + * + * @v bus USB bus + * @ret address Device address, or negative error + */ +int usb_alloc_address ( struct usb_bus *bus ) { + unsigned int address; + + /* Find first free device address */ + address = ffsll ( ~bus->addresses ); + if ( ! address ) + return -ENOENT; + + /* Mark address as used */ + bus->addresses |= ( 1ULL << ( address - 1 ) ); + + return address; +} + +/** + * Free device address + * + * @v bus USB bus + * @v address Device address + */ +void usb_free_address ( struct usb_bus *bus, unsigned int address ) { + + /* Sanity check */ + assert ( address > 0 ); + assert ( bus->addresses & ( 1ULL << ( address - 1 ) ) ); + + /* Mark address as free */ + bus->addresses &= ~( 1ULL << ( address - 1 ) ); +} + +/****************************************************************************** + * + * USB bus topology + * + ****************************************************************************** + */ + +/** + * Get USB route string + * + * @v usb USB device + * @ret route USB route string + */ +unsigned int usb_route_string ( struct usb_device *usb ) { + struct usb_device *parent; + unsigned int route; + + /* Navigate up to root hub, constructing route string as we go */ + for ( route = 0 ; ( parent = usb->port->hub->usb ) ; usb = parent ) { + route <<= 4; + route |= ( ( usb->port->address > 0xf ) ? + 0xf : usb->port->address ); + } + + return route; +} + +/** + * Get USB depth + * + * @v usb USB device + * @ret depth Hub depth + */ +unsigned int usb_depth ( struct usb_device *usb ) { + struct usb_device *parent; + unsigned int depth; + + /* Navigate up to root hub, constructing depth as we go */ + for ( depth = 0 ; ( parent = usb->port->hub->usb ) ; usb = parent ) + depth++; + + return depth; +} + +/** + * Get USB root hub port + * + * @v usb USB device + * @ret port Root hub port + */ +struct usb_port * usb_root_hub_port ( struct usb_device *usb ) { + struct usb_device *parent; + + /* Navigate up to root hub */ + while ( ( parent = usb->port->hub->usb ) ) + usb = parent; + + return usb->port; +} + +/** + * Get USB transaction translator + * + * @v usb USB device + * @ret port Transaction translator port, or NULL + */ +struct usb_port * usb_transaction_translator ( struct usb_device *usb ) { + struct usb_device *parent; + + /* Navigate up to root hub. If we find a low-speed or + * full-speed port with a higher-speed parent device, then + * that port is the transaction translator. + */ + for ( ; ( parent = usb->port->hub->usb ) ; usb = parent ) { + if ( ( usb->port->speed <= USB_SPEED_FULL ) && + ( parent->port->speed > USB_SPEED_FULL ) ) + return usb->port; + } + + return NULL; +} + +/* Drag in objects via register_usb_bus() */ +REQUIRING_SYMBOL ( register_usb_bus ); + +/* Drag in USB configuration */ +REQUIRE_OBJECT ( config_usb ); + +/* Drag in hub driver */ +REQUIRE_OBJECT ( usbhub ); diff --git a/qemu/roms/ipxe/src/drivers/infiniband/arbel.c b/qemu/roms/ipxe/src/drivers/infiniband/arbel.c index 1a56ff9af..2a6c32dec 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/arbel.c +++ b/qemu/roms/ipxe/src/drivers/infiniband/arbel.c @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/infiniband/arbel.h b/qemu/roms/ipxe/src/drivers/infiniband/arbel.h index c0303f1bc..73394cd9a 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/arbel.h +++ b/qemu/roms/ipxe/src/drivers/infiniband/arbel.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/uaccess.h> diff --git a/qemu/roms/ipxe/src/drivers/infiniband/linda.c b/qemu/roms/ipxe/src/drivers/infiniband/linda.c index 4afda1208..a6ae9f529 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/linda.c +++ b/qemu/roms/ipxe/src/drivers/infiniband/linda.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/infiniband/linda.h b/qemu/roms/ipxe/src/drivers/infiniband/linda.h index 72ce70868..46a920a17 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/linda.h +++ b/qemu/roms/ipxe/src/drivers/infiniband/linda.h @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/infiniband/qib7322.c b/qemu/roms/ipxe/src/drivers/infiniband/qib7322.c index 9979b346e..e22f2349a 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/qib7322.c +++ b/qemu/roms/ipxe/src/drivers/infiniband/qib7322.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/infiniband/qib7322.h b/qemu/roms/ipxe/src/drivers/infiniband/qib7322.h index 63abe221b..72797b240 100644 --- a/qemu/roms/ipxe/src/drivers/infiniband/qib7322.h +++ b/qemu/roms/ipxe/src/drivers/infiniband/qib7322.h @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/net/amd8111e.h b/qemu/roms/ipxe/src/drivers/net/amd8111e.h index 2000df158..8ecd159af 100644 --- a/qemu/roms/ipxe/src/drivers/net/amd8111e.h +++ b/qemu/roms/ipxe/src/drivers/net/amd8111e.h @@ -16,6 +16,10 @@ * 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. * USA Module Name: @@ -36,7 +40,7 @@ Revision History: 3.0.1 */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #ifndef _AMD811E_H #define _AMD811E_H diff --git a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ani.h b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ani.h index dbd4d4d5b..ba87ba0fd 100644 --- a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ani.h +++ b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ani.h @@ -125,7 +125,7 @@ struct ar5416AniState { u8 mrcCCKOff; u8 spurImmunityLevel; u8 firstepLevel; - u8 ofdmWeakSigDetectOff; + u8 ofdmWeakSigDetect; u8 cckWeakSigThreshold; u32 listenTime; int32_t rssiThrLow; diff --git a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ani.c b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ani.c index ff7df497f..76ca79cba 100644 --- a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ani.c +++ b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ani.c @@ -177,7 +177,7 @@ static void ath9k_hw_ani_ofdm_err_trigger_old(struct ath_hw *ah) rssi = BEACON_RSSI(ah); if (rssi > aniState->rssiThrHigh) { - if (!aniState->ofdmWeakSigDetectOff) { + if (aniState->ofdmWeakSigDetect) { if (ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, 0)) { @@ -192,7 +192,7 @@ static void ath9k_hw_ani_ofdm_err_trigger_old(struct ath_hw *ah) return; } } else if (rssi > aniState->rssiThrLow) { - if (aniState->ofdmWeakSigDetectOff) + if (!aniState->ofdmWeakSigDetect) ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, 1); @@ -202,7 +202,7 @@ static void ath9k_hw_ani_ofdm_err_trigger_old(struct ath_hw *ah) return; } else { if ((ah->dev->channels + ah->dev->channel)->band == NET80211_BAND_2GHZ) { - if (!aniState->ofdmWeakSigDetectOff) + if (aniState->ofdmWeakSigDetect) ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, 0); @@ -360,7 +360,7 @@ static void ath9k_hw_ani_lower_immunity_old(struct ath_hw *ah) if (rssi > aniState->rssiThrHigh) { /* XXX: Handle me */ } else if (rssi > aniState->rssiThrLow) { - if (aniState->ofdmWeakSigDetectOff) { + if (!aniState->ofdmWeakSigDetect) { if (ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, 1) == 1) @@ -436,9 +436,9 @@ static void ath9k_ani_reset_old(struct ath_hw *ah) if (aniState->spurImmunityLevel != 0) ath9k_hw_ani_control(ah, ATH9K_ANI_SPUR_IMMUNITY_LEVEL, aniState->spurImmunityLevel); - if (aniState->ofdmWeakSigDetectOff) + if (!aniState->ofdmWeakSigDetect) ath9k_hw_ani_control(ah, ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, - !aniState->ofdmWeakSigDetectOff); + aniState->ofdmWeakSigDetect); if (aniState->cckWeakSigThreshold) ath9k_hw_ani_control(ah, ATH9K_ANI_CCK_WEAK_SIGNAL_THR, aniState->cckWeakSigThreshold); @@ -709,8 +709,8 @@ void ath9k_hw_ani_init(struct ath_hw *ah) ani->rssiThrHigh = ATH9K_ANI_RSSI_THR_HIGH; ani->rssiThrLow = ATH9K_ANI_RSSI_THR_LOW; - ani->ofdmWeakSigDetectOff = - !ATH9K_ANI_USE_OFDM_WEAK_SIG; + ani->ofdmWeakSigDetect = + ATH9K_ANI_USE_OFDM_WEAK_SIG; ani->cckNoiseImmunityLevel = ATH9K_ANI_CCK_DEF_LEVEL; } diff --git a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar5008_phy.c b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar5008_phy.c index 60e87e9e2..2b6c133cb 100644 --- a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar5008_phy.c +++ b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar5008_phy.c @@ -1141,12 +1141,12 @@ static int ar5008_hw_ani_control_old(struct ath_hw *ah, REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); - if (!on != aniState->ofdmWeakSigDetectOff) { + if (on != aniState->ofdmWeakSigDetect) { if (on) ah->stats.ast_ani_ofdmon++; else ah->stats.ast_ani_ofdmoff++; - aniState->ofdmWeakSigDetectOff = !on; + aniState->ofdmWeakSigDetect = on; } break; } @@ -1215,10 +1215,10 @@ static int ar5008_hw_ani_control_old(struct ath_hw *ah, DBG2("ath9k: ANI parameters:\n"); DBG2( - "noiseImmunityLevel=%d, spurImmunityLevel=%d, ofdmWeakSigDetectOff=%d\n", + "noiseImmunityLevel=%d, spurImmunityLevel=%d, ofdmWeakSigDetect=%d\n", aniState->noiseImmunityLevel, aniState->spurImmunityLevel, - !aniState->ofdmWeakSigDetectOff); + aniState->ofdmWeakSigDetect); DBG2( "cckWeakSigThreshold=%d, firstepLevel=%d, listenTime=%d\n", aniState->cckWeakSigThreshold, @@ -1307,18 +1307,18 @@ static int ar5008_hw_ani_control_new(struct ath_hw *ah, REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); - if (!on != aniState->ofdmWeakSigDetectOff) { + if (on != aniState->ofdmWeakSigDetect) { DBG2("ath9k: " "** ch %d: ofdm weak signal: %s=>%s\n", chan->channel, - !aniState->ofdmWeakSigDetectOff ? + aniState->ofdmWeakSigDetect ? "on" : "off", on ? "on" : "off"); if (on) ah->stats.ast_ani_ofdmon++; else ah->stats.ast_ani_ofdmoff++; - aniState->ofdmWeakSigDetectOff = !on; + aniState->ofdmWeakSigDetect = on; } break; } @@ -1467,7 +1467,7 @@ static int ar5008_hw_ani_control_new(struct ath_hw *ah, DBG2("ath9k: " "ANI parameters: SI=%d, ofdmWS=%s FS=%d MRCcck=%s listenTime=%d ofdmErrs=%d cckErrs=%d\n", aniState->spurImmunityLevel, - !aniState->ofdmWeakSigDetectOff ? "on" : "off", + aniState->ofdmWeakSigDetect ? "on" : "off", aniState->firstepLevel, !aniState->mrcCCKOff ? "on" : "off", aniState->listenTime, @@ -1554,7 +1554,7 @@ static void ar5008_hw_ani_cache_ini_regs(struct ath_hw *ah) /* these levels just got reset to defaults by the INI */ aniState->spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL_NEW; aniState->firstepLevel = ATH9K_ANI_FIRSTEP_LVL_NEW; - aniState->ofdmWeakSigDetectOff = !ATH9K_ANI_USE_OFDM_WEAK_SIG; + aniState->ofdmWeakSigDetect = ATH9K_ANI_USE_OFDM_WEAK_SIG; aniState->mrcCCKOff = 1; /* not available on pre AR9003 */ } diff --git a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar9003_phy.c b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar9003_phy.c index 6103040ab..2244b775a 100644 --- a/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar9003_phy.c +++ b/qemu/roms/ipxe/src/drivers/net/ath/ath9k/ath9k_ar9003_phy.c @@ -859,18 +859,18 @@ static int ar9003_hw_ani_control(struct ath_hw *ah, REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); - if (!on != aniState->ofdmWeakSigDetectOff) { + if (on != aniState->ofdmWeakSigDetect) { DBG2("ath9k: " "** ch %d: ofdm weak signal: %s=>%s\n", chan->channel, - !aniState->ofdmWeakSigDetectOff ? + aniState->ofdmWeakSigDetect ? "on" : "off", on ? "on" : "off"); if (on) ah->stats.ast_ani_ofdmon++; else ah->stats.ast_ani_ofdmoff++; - aniState->ofdmWeakSigDetectOff = !on; + aniState->ofdmWeakSigDetect = on; } break; } @@ -1013,7 +1013,7 @@ static int ar9003_hw_ani_control(struct ath_hw *ah, AR_PHY_MRC_CCK_ENABLE, is_on); REG_RMW_FIELD(ah, AR_PHY_MRC_CCK_CTRL, AR_PHY_MRC_CCK_MUX_REG, is_on); - if (!is_on != aniState->mrcCCKOff) { + if (!(is_on != aniState->mrcCCKOff)) { DBG2("ath9k: " "** ch %d: MRC CCK: %s=>%s\n", chan->channel, @@ -1037,7 +1037,7 @@ static int ar9003_hw_ani_control(struct ath_hw *ah, DBG2("ath9k: " "ANI parameters: SI=%d, ofdmWS=%s FS=%d MRCcck=%s listenTime=%d ofdmErrs=%d cckErrs=%d\n", aniState->spurImmunityLevel, - !aniState->ofdmWeakSigDetectOff ? "on" : "off", + aniState->ofdmWeakSigDetect ? "on" : "off", aniState->firstepLevel, !aniState->mrcCCKOff ? "on" : "off", aniState->listenTime, @@ -1137,7 +1137,7 @@ static void ar9003_hw_ani_cache_ini_regs(struct ath_hw *ah) /* these levels just got reset to defaults by the INI */ aniState->spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL_NEW; aniState->firstepLevel = ATH9K_ANI_FIRSTEP_LVL_NEW; - aniState->ofdmWeakSigDetectOff = !ATH9K_ANI_USE_OFDM_WEAK_SIG; + aniState->ofdmWeakSigDetect = ATH9K_ANI_USE_OFDM_WEAK_SIG; aniState->mrcCCKOff = !ATH9K_ANI_ENABLE_MRC_CCK; } diff --git a/qemu/roms/ipxe/src/drivers/net/atl1e.c b/qemu/roms/ipxe/src/drivers/net/atl1e.c index 1ff0f0d10..d010d8c4a 100644 --- a/qemu/roms/ipxe/src/drivers/net/atl1e.c +++ b/qemu/roms/ipxe/src/drivers/net/atl1e.c @@ -224,7 +224,7 @@ static int atl1e_sw_init(struct atl1e_adapter *adapter) adapter->link_duplex = FULL_DUPLEX; /* PCI config space info */ - pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id); + pci_read_config_byte(pdev, PCI_REVISION, &rev_id); phy_status_data = AT_READ_REG(hw, REG_PHY_STATUS); /* nic type */ diff --git a/qemu/roms/ipxe/src/drivers/net/davicom.c b/qemu/roms/ipxe/src/drivers/net/davicom.c index a4870a729..9d3d8b915 100644 --- a/qemu/roms/ipxe/src/drivers/net/davicom.c +++ b/qemu/roms/ipxe/src/drivers/net/davicom.c @@ -340,6 +340,7 @@ static void davicom_media_chk(struct nic * nic __unused) csr6 = 0x00200000; /* SF */ outl(csr6, ioaddr + CSR6); +#define PCI_VENDOR_ID_DAVICOM 0x1282 #define PCI_DEVICE_ID_DM9009 0x9009 if (vendor == PCI_VENDOR_ID_DAVICOM && dev_id == PCI_DEVICE_ID_DM9009) { /* Set to 10BaseT mode for DM9009 */ diff --git a/qemu/roms/ipxe/src/drivers/net/dm96xx.c b/qemu/roms/ipxe/src/drivers/net/dm96xx.c new file mode 100644 index 000000000..58d8dd964 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/dm96xx.c @@ -0,0 +1,671 @@ +/* + * 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 <string.h> +#include <unistd.h> +#include <errno.h> +#include <ipxe/ethernet.h> +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include "dm96xx.h" + +/** @file + * + * Davicom DM96xx USB Ethernet driver + * + */ + +/****************************************************************************** + * + * Register operations + * + ****************************************************************************** + */ + +/** + * Reset device + * + * @v dm96xx DM96xx device + * @ret rc Return status code + */ +static int dm96xx_reset ( struct dm96xx_device *dm96xx ) { + int ncr; + int rc; + + /* Reset device */ + if ( ( rc = dm96xx_write_register ( dm96xx, DM96XX_NCR, + DM96XX_NCR_RST ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not reset: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + /* Wait for reset to complete */ + udelay ( DM96XX_RESET_DELAY_US ); + + /* Check that reset has completed */ + ncr = dm96xx_read_register ( dm96xx, DM96XX_NCR ); + if ( ncr < 0 ) { + rc = ncr; + DBGC ( dm96xx, "DM96XX %p failed to reset: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + if ( ncr & DM96XX_NCR_RST ) { + DBGC ( dm96xx, "DM96XX %p failed to reset (NCR=%#02x)\n", + dm96xx, ncr ); + return -EIO; + } + + return 0; +} + +/** + * Read MAC address + * + * @v dm96xx DM96xx device + * @v mac MAC address to fill in + * @ret rc Return status code + */ +static int dm96xx_read_mac ( struct dm96xx_device *dm96xx, uint8_t *mac ) { + int rc; + + /* Read MAC address */ + if ( ( rc = dm96xx_read_registers ( dm96xx, DM96XX_PAR, mac, + ETH_ALEN ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not read MAC address: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Write MAC address + * + * @v dm96xx DM96xx device + * @v mac MAC address + * @ret rc Return status code + */ +static int dm96xx_write_mac ( struct dm96xx_device *dm96xx, uint8_t *mac ) { + int rc; + + /* Write MAC address */ + if ( ( rc = dm96xx_write_registers ( dm96xx, DM96XX_PAR, mac, + ETH_ALEN ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not write MAC address: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Update link status based on network status register + * + * @v dm96xx DM96xx device + * @v nsr Network status register + */ +static void dm96xx_link_nsr ( struct dm96xx_device *dm96xx, unsigned int nsr ) { + struct net_device *netdev = dm96xx->netdev; + + if ( nsr & DM96XX_NSR_LINKST ) { + if ( ! netdev_link_ok ( netdev ) ) + netdev_link_up ( netdev ); + } else { + if ( netdev_link_ok ( netdev ) ) + netdev_link_down ( netdev ); + } +} + +/** + * Get link status + * + * @v dm96xx DM96xx device + * @ret rc Return status code + */ +static int dm96xx_check_link ( struct dm96xx_device *dm96xx ) { + int nsr; + int rc; + + /* Read network status register */ + nsr = dm96xx_read_register ( dm96xx, DM96XX_NSR ); + if ( nsr < 0 ) { + rc = nsr; + DBGC ( dm96xx, "DM96XX %p could not read network status: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + /* Update link status */ + dm96xx_link_nsr ( dm96xx, nsr ); + + return 0; +} + +/** + * Set DM9601-compatible RX header mode + * + * @v dm96xx DM96xx device + * @ret rc Return status code + */ +static int dm96xx_rx_mode ( struct dm96xx_device *dm96xx ) { + int chipr; + int mode_ctl; + int rc; + + /* Get chip revision */ + chipr = dm96xx_read_register ( dm96xx, DM96XX_CHIPR ); + if ( chipr < 0 ) { + rc = chipr; + DBGC ( dm96xx, "DM96XX %p could not read chip revision: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + /* Do nothing if device is a DM9601 anyway */ + if ( chipr == DM96XX_CHIPR_9601 ) + return 0; + + /* Read current mode control */ + mode_ctl = dm96xx_read_register ( dm96xx, DM96XX_MODE_CTL ); + if ( mode_ctl < 0 ) { + rc = mode_ctl; + DBGC ( dm96xx, "DM96XX %p could not read mode control: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + /* Write mode control */ + mode_ctl &= ~DM96XX_MODE_CTL_MODE; + if ( ( rc = dm96xx_write_register ( dm96xx, DM96XX_MODE_CTL, + mode_ctl ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not write mode control: %s\n", + dm96xx, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/****************************************************************************** + * + * Endpoint operations + * + ****************************************************************************** + */ + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void dm96xx_intr_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct dm96xx_device *dm96xx = container_of ( ep, struct dm96xx_device, + usbnet.intr ); + struct net_device *netdev = dm96xx->netdev; + struct dm96xx_interrupt *intr; + size_t len = iob_len ( iobuf ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto done; + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( dm96xx, "DM96XX %p interrupt failed: %s\n", + dm96xx, strerror ( rc ) ); + DBGC_HDA ( dm96xx, 0, iobuf->data, iob_len ( iobuf ) ); + netdev_rx_err ( netdev, NULL, rc ); + goto done; + } + + /* Extract message header */ + if ( len < sizeof ( *intr ) ) { + DBGC ( dm96xx, "DM96XX %p underlength interrupt:\n", dm96xx ); + DBGC_HDA ( dm96xx, 0, iobuf->data, iob_len ( iobuf ) ); + netdev_rx_err ( netdev, NULL, -EINVAL ); + goto done; + } + intr = iobuf->data; + + /* Update link status */ + dm96xx_link_nsr ( dm96xx, intr->nsr ); + + done: + /* Free I/O buffer */ + free_iob ( iobuf ); +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations dm96xx_intr_operations = { + .complete = dm96xx_intr_complete, +}; + +/** + * Complete bulk IN transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void dm96xx_in_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct dm96xx_device *dm96xx = container_of ( ep, struct dm96xx_device, + usbnet.in ); + struct net_device *netdev = dm96xx->netdev; + struct dm96xx_rx_header *header; + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) { + free_iob ( iobuf ); + return; + } + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( dm96xx, "DM96XX %p bulk IN failed: %s\n", + dm96xx, strerror ( rc ) ); + goto err; + } + + /* Sanity check */ + if ( iob_len ( iobuf ) < ( sizeof ( *header ) + 4 /* CRC */ ) ) { + DBGC ( dm96xx, "DM96XX %p underlength bulk IN\n", dm96xx ); + DBGC_HDA ( dm96xx, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto err; + } + + /* Strip header and CRC */ + header = iobuf->data; + iob_pull ( iobuf, sizeof ( *header ) ); + iob_unput ( iobuf, 4 /* CRC */ ); + + /* Check status */ + if ( header->rsr & ~DM96XX_RSR_MF ) { + DBGC ( dm96xx, "DM96XX %p receive error %02x:\n", + dm96xx, header->rsr ); + DBGC_HDA ( dm96xx, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EIO; + goto err; + } + + /* Hand off to network stack */ + netdev_rx ( netdev, iob_disown ( iobuf ) ); + return; + + err: + /* Hand off to network stack */ + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); +} + +/** Bulk IN endpoint operations */ +static struct usb_endpoint_driver_operations dm96xx_in_operations = { + .complete = dm96xx_in_complete, +}; + +/** + * Transmit packet + * + * @v dm96xx DM96xx device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int dm96xx_out_transmit ( struct dm96xx_device *dm96xx, + struct io_buffer *iobuf ) { + struct dm96xx_tx_header *header; + size_t len = iob_len ( iobuf ); + int rc; + + /* Prepend header */ + if ( ( rc = iob_ensure_headroom ( iobuf, sizeof ( *header ) ) ) != 0 ) + return rc; + header = iob_push ( iobuf, sizeof ( *header ) ); + header->len = cpu_to_le16 ( len ); + + /* Enqueue I/O buffer */ + if ( ( rc = usb_stream ( &dm96xx->usbnet.out, iobuf, 0 ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Complete bulk OUT transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void dm96xx_out_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct dm96xx_device *dm96xx = container_of ( ep, struct dm96xx_device, + usbnet.out ); + struct net_device *netdev = dm96xx->netdev; + + /* Report TX completion */ + netdev_tx_complete_err ( netdev, iobuf, rc ); +} + +/** Bulk OUT endpoint operations */ +static struct usb_endpoint_driver_operations dm96xx_out_operations = { + .complete = dm96xx_out_complete, +}; + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int dm96xx_open ( struct net_device *netdev ) { + struct dm96xx_device *dm96xx = netdev->priv; + unsigned int rcr; + int rc; + + /* Set DM9601-compatible RX header mode */ + if ( ( rc = dm96xx_rx_mode ( dm96xx ) ) != 0 ) + goto err_rx_mode; + + /* Write MAC address */ + if ( ( rc = dm96xx_write_mac ( dm96xx, netdev->ll_addr ) ) != 0 ) + goto err_write_mac; + + /* Open USB network device */ + if ( ( rc = usbnet_open ( &dm96xx->usbnet ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not open: %s\n", + dm96xx, strerror ( rc ) ); + goto err_open; + } + + /* Set receive filters */ + rcr = ( DM96XX_RCR_ALL | DM96XX_RCR_RUNT | DM96XX_RCR_PRMSC | + DM96XX_RCR_RXEN ); + if ( ( rc = dm96xx_write_register ( dm96xx, DM96XX_RCR, rcr ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not write receive filters: " + "%s\n", dm96xx, strerror ( rc ) ); + goto err_write_rcr; + } + + /* Update link status */ + if ( ( rc = dm96xx_check_link ( dm96xx ) ) != 0 ) + goto err_check_link; + + return 0; + + err_check_link: + err_write_rcr: + usbnet_close ( &dm96xx->usbnet ); + err_open: + err_write_mac: + err_rx_mode: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void dm96xx_close ( struct net_device *netdev ) { + struct dm96xx_device *dm96xx = netdev->priv; + + /* Close USB network device */ + usbnet_close ( &dm96xx->usbnet ); + + /* Reset device */ + dm96xx_reset ( dm96xx ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int dm96xx_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct dm96xx_device *dm96xx = netdev->priv; + int rc; + + /* Transmit packet */ + if ( ( rc = dm96xx_out_transmit ( dm96xx, iobuf ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void dm96xx_poll ( struct net_device *netdev ) { + struct dm96xx_device *dm96xx = netdev->priv; + int rc; + + /* Poll USB bus */ + usb_poll ( dm96xx->bus ); + + /* Refill endpoints */ + if ( ( rc = usbnet_refill ( &dm96xx->usbnet ) ) != 0 ) + netdev_rx_err ( netdev, NULL, rc ); +} + +/** DM96xx network device operations */ +static struct net_device_operations dm96xx_operations = { + .open = dm96xx_open, + .close = dm96xx_close, + .transmit = dm96xx_transmit, + .poll = dm96xx_poll, +}; + +/****************************************************************************** + * + * USB interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int dm96xx_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct net_device *netdev; + struct dm96xx_device *dm96xx; + int rc; + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *dm96xx ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &dm96xx_operations ); + netdev->dev = &func->dev; + dm96xx = netdev->priv; + memset ( dm96xx, 0, sizeof ( *dm96xx ) ); + dm96xx->usb = usb; + dm96xx->bus = usb->port->hub->bus; + dm96xx->netdev = netdev; + usbnet_init ( &dm96xx->usbnet, func, &dm96xx_intr_operations, + &dm96xx_in_operations, &dm96xx_out_operations ); + usb_refill_init ( &dm96xx->usbnet.intr, 0, DM96XX_INTR_MAX_FILL ); + usb_refill_init ( &dm96xx->usbnet.in, DM96XX_IN_MTU, + DM96XX_IN_MAX_FILL ); + DBGC ( dm96xx, "DM96XX %p on %s\n", dm96xx, func->name ); + + /* Describe USB network device */ + if ( ( rc = usbnet_describe ( &dm96xx->usbnet, config ) ) != 0 ) { + DBGC ( dm96xx, "DM96XX %p could not describe: %s\n", + dm96xx, strerror ( rc ) ); + goto err_describe; + } + + /* Reset device */ + if ( ( rc = dm96xx_reset ( dm96xx ) ) != 0 ) + goto err_reset; + + /* Read MAC address */ + if ( ( rc = dm96xx_read_mac ( dm96xx, netdev->hw_addr ) ) != 0 ) + goto err_read_mac; + + /* Get initial link status */ + if ( ( rc = dm96xx_check_link ( dm96xx ) ) != 0 ) + goto err_check_link; + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + usb_func_set_drvdata ( func, netdev ); + return 0; + + unregister_netdev ( netdev ); + err_register: + err_check_link: + err_read_mac: + err_reset: + err_describe: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void dm96xx_remove ( struct usb_function *func ) { + struct net_device *netdev = usb_func_get_drvdata ( func ); + + unregister_netdev ( netdev ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** DM96xx device IDs */ +static struct usb_device_id dm96xx_ids[] = { + { + .name = "dm9601-corega", + .vendor = 0x07aa, + .product = 0x9601, + }, + { + .name = "dm9601", + .vendor = 0x0a46, + .product = 0x9601, + }, + { + .name = "zt6688", + .vendor = 0x0a46, + .product = 0x6688, + }, + { + .name = "st268", + .vendor = 0x0a46, + .product = 0x0268, + }, + { + .name = "adm8515", + .vendor = 0x0a46, + .product = 0x8515, + }, + { + .name = "dm9601-hirose", + .vendor = 0x0a47, + .product = 0x9601, + }, + { + .name = "dm9601-8101", + .vendor = 0x0fe6, + .product = 0x8101, + }, + { + .name = "dm9601-9700", + .vendor = 0x0fe6, + .product = 0x9700, + }, + { + .name = "dm9000e", + .vendor = 0x0a46, + .product = 0x9000, + }, + { + .name = "dm9620", + .vendor = 0x0a46, + .product = 0x9620, + }, + { + .name = "dm9621A", + .vendor = 0x0a46, + .product = 0x9621, + }, + { + .name = "dm9622", + .vendor = 0x0a46, + .product = 0x9622, + }, + { + .name = "dm962Oa", + .vendor = 0x0a46, + .product = 0x0269, + }, + { + .name = "dm9621a", + .vendor = 0x0a46, + .product = 0x1269, + }, +}; + +/** Davicom DM96xx driver */ +struct usb_driver dm96xx_driver __usb_driver = { + .ids = dm96xx_ids, + .id_count = ( sizeof ( dm96xx_ids ) / sizeof ( dm96xx_ids[0] ) ), + .probe = dm96xx_probe, + .remove = dm96xx_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/dm96xx.h b/qemu/roms/ipxe/src/drivers/net/dm96xx.h new file mode 100644 index 000000000..43a1a4e30 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/dm96xx.h @@ -0,0 +1,194 @@ +#ifndef _DM96XX_H +#define _DM96XX_H + +/** @file + * + * Davicom DM96xx USB Ethernet driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include <ipxe/if_ether.h> + +/** Read register(s) */ +#define DM96XX_READ_REGISTER \ + ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0x00 ) ) + +/** Write register(s) */ +#define DM96XX_WRITE_REGISTER \ + ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0x01 ) ) + +/** Write single register */ +#define DM96XX_WRITE1_REGISTER \ + ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0x03 ) ) + +/** Network control register */ +#define DM96XX_NCR 0x00 +#define DM96XX_NCR_RST 0x01 /**< Software reset */ + +/** Network status register */ +#define DM96XX_NSR 0x01 +#define DM96XX_NSR_LINKST 0x40 /**< Link status */ + +/** Receive control register */ +#define DM96XX_RCR 0x05 +#define DM96XX_RCR_ALL 0x08 /**< Pass all multicast */ +#define DM96XX_RCR_RUNT 0x04 /**< Pass runt packet */ +#define DM96XX_RCR_PRMSC 0x02 /**< Promiscuous mode */ +#define DM96XX_RCR_RXEN 0x01 /**< RX enable */ + +/** Receive status register */ +#define DM96XX_RSR 0x06 +#define DM96XX_RSR_MF 0x40 /**< Multicast frame */ + +/** PHY address registers */ +#define DM96XX_PAR 0x10 + +/** Chip revision register */ +#define DM96XX_CHIPR 0x2c +#define DM96XX_CHIPR_9601 0x00 /**< DM9601 */ +#define DM96XX_CHIPR_9620 0x01 /**< DM9620 */ + +/** RX header control/status register (DM9620+ only) */ +#define DM96XX_MODE_CTL 0x91 +#define DM96XX_MODE_CTL_MODE 0x80 /**< 4-byte header mode */ + +/** DM96xx interrupt data */ +struct dm96xx_interrupt { + /** Network status register */ + uint8_t nsr; + /** Transmit status registers */ + uint8_t tsr[2]; + /** Receive status register */ + uint8_t rsr; + /** Receive overflow counter register */ + uint8_t rocr; + /** Receive packet counter */ + uint8_t rxc; + /** Transmit packet counter */ + uint8_t txc; + /** General purpose register */ + uint8_t gpr; +} __attribute__ (( packed )); + +/** DM96xx receive header */ +struct dm96xx_rx_header { + /** Packet status */ + uint8_t rsr; + /** Packet length (excluding this header, including CRC) */ + uint16_t len; +} __attribute__ (( packed )); + +/** DM96xx transmit header */ +struct dm96xx_tx_header { + /** Packet length (excluding this header) */ + uint16_t len; +} __attribute__ (( packed )); + +/** A DM96xx network device */ +struct dm96xx_device { + /** USB device */ + struct usb_device *usb; + /** USB bus */ + struct usb_bus *bus; + /** Network device */ + struct net_device *netdev; + /** USB network device */ + struct usbnet_device usbnet; +}; + +/** + * Read registers + * + * @v dm96xx DM96xx device + * @v offset Register offset + * @v data Data buffer + * @v len Length of data + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +dm96xx_read_registers ( struct dm96xx_device *dm96xx, unsigned int offset, + void *data, size_t len ) { + + return usb_control ( dm96xx->usb, DM96XX_READ_REGISTER, 0, offset, + data, len ); +} + +/** + * Read register + * + * @v dm96xx DM96xx device + * @v offset Register offset + * @ret value Register value, or negative error + */ +static inline __attribute__ (( always_inline )) int +dm96xx_read_register ( struct dm96xx_device *dm96xx, unsigned int offset ) { + uint8_t value; + int rc; + + if ( ( rc = dm96xx_read_registers ( dm96xx, offset, &value, + sizeof ( value ) ) ) != 0 ) + return rc; + return value; +} + +/** + * Write registers + * + * @v dm96xx DM96xx device + * @v offset Register offset + * @v data Data buffer + * @v len Length of data + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +dm96xx_write_registers ( struct dm96xx_device *dm96xx, unsigned int offset, + void *data, size_t len ) { + + return usb_control ( dm96xx->usb, DM96XX_WRITE_REGISTER, 0, offset, + data, len ); +} + +/** + * Write register + * + * @v dm96xx DM96xx device + * @v offset Register offset + * @v value Register value + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +dm96xx_write_register ( struct dm96xx_device *dm96xx, unsigned int offset, + uint8_t value ) { + + return usb_control ( dm96xx->usb, DM96XX_WRITE1_REGISTER, value, + offset, NULL, 0 ); +} + +/** Reset delay (in microseconds) */ +#define DM96XX_RESET_DELAY_US 10 + +/** Interrupt maximum fill level + * + * This is a policy decision. + */ +#define DM96XX_INTR_MAX_FILL 2 + +/** Bulk IN maximum fill level + * + * This is a policy decision. + */ +#define DM96XX_IN_MAX_FILL 8 + +/** Bulk IN buffer size */ +#define DM96XX_IN_MTU \ + ( 4 /* DM96xx header */ + ETH_FRAME_LEN + \ + 4 /* possible VLAN header */ + 4 /* CRC */ ) + +#endif /* _DM96XX_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/dmfe.c b/qemu/roms/ipxe/src/drivers/net/dmfe.c index aae40fce7..2ea0d2b2b 100644 --- a/qemu/roms/ipxe/src/drivers/net/dmfe.c +++ b/qemu/roms/ipxe/src/drivers/net/dmfe.c @@ -462,7 +462,7 @@ static int dmfe_probe ( struct nic *nic, struct pci_device *pci ) { pci->id->name, pci->vendor, pci->device); /* Read Chip revision */ - pci_read_config_dword(pci, PCI_REVISION_ID, &dev_rev); + pci_read_config_dword(pci, PCI_REVISION, &dev_rev); dprintf(("Revision %lX\n", dev_rev)); /* point to private storage */ diff --git a/qemu/roms/ipxe/src/drivers/net/ecm.c b/qemu/roms/ipxe/src/drivers/net/ecm.c new file mode 100644 index 000000000..8c84ea9e9 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/ecm.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2014 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 <stdint.h> +#include <errno.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/if_ether.h> +#include <ipxe/base16.h> +#include <ipxe/profile.h> +#include <ipxe/usb.h> +#include "ecm.h" + +/** @file + * + * CDC-ECM USB Ethernet driver + * + */ + +/** Interrupt completion profiler */ +static struct profiler ecm_intr_profiler __profiler = + { .name = "ecm.intr" }; + +/** Bulk IN completion profiler */ +static struct profiler ecm_in_profiler __profiler = + { .name = "ecm.in" }; + +/** Bulk OUT profiler */ +static struct profiler ecm_out_profiler __profiler = + { .name = "ecm.out" }; + +/****************************************************************************** + * + * Ethernet functional descriptor + * + ****************************************************************************** + */ + +/** + * Locate Ethernet functional descriptor + * + * @v config Configuration descriptor + * @v interface Interface descriptor + * @ret desc Descriptor, or NULL if not found + */ +struct ecm_ethernet_descriptor * +ecm_ethernet_descriptor ( struct usb_configuration_descriptor *config, + struct usb_interface_descriptor *interface ) { + struct ecm_ethernet_descriptor *desc; + + for_each_interface_descriptor ( desc, config, interface ) { + if ( ( desc->header.type == USB_CS_INTERFACE_DESCRIPTOR ) && + ( desc->subtype == CDC_SUBTYPE_ETHERNET ) ) + return desc; + } + return NULL; +} + +/** + * Get hardware MAC address + * + * @v usb USB device + * @v desc Ethernet functional descriptor + * @v hw_addr Hardware address to fill in + * @ret rc Return status code + */ +int ecm_fetch_mac ( struct usb_device *usb, + struct ecm_ethernet_descriptor *desc, uint8_t *hw_addr ) { + char buf[ base16_encoded_len ( ETH_ALEN ) + 1 /* NUL */ ]; + int len; + int rc; + + /* Fetch MAC address string */ + len = usb_get_string_descriptor ( usb, desc->mac, 0, buf, + sizeof ( buf ) ); + if ( len < 0 ) { + rc = len; + return rc; + } + + /* Sanity check */ + if ( len != ( ( int ) ( sizeof ( buf ) - 1 /* NUL */ ) ) ) + return -EINVAL; + + /* Decode MAC address */ + len = base16_decode ( buf, hw_addr, ETH_ALEN ); + if ( len < 0 ) { + rc = len; + return rc; + } + + return 0; +} + +/****************************************************************************** + * + * CDC-ECM communications interface + * + ****************************************************************************** + */ + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ecm_intr_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct ecm_device *ecm = container_of ( ep, struct ecm_device, + usbnet.intr ); + struct net_device *netdev = ecm->netdev; + struct usb_setup_packet *message; + size_t len = iob_len ( iobuf ); + + /* Profile completions */ + profile_start ( &ecm_intr_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto ignore; + + /* Drop packets with errors */ + if ( rc != 0 ) { + DBGC ( ecm, "ECM %p interrupt failed: %s\n", + ecm, strerror ( rc ) ); + DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) ); + goto error; + } + + /* Extract message header */ + if ( len < sizeof ( *message ) ) { + DBGC ( ecm, "ECM %p underlength interrupt:\n", ecm ); + DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto error; + } + message = iobuf->data; + + /* Parse message header */ + switch ( message->request ) { + + case cpu_to_le16 ( CDC_NETWORK_CONNECTION ) : + if ( message->value && ! netdev_link_ok ( netdev ) ) { + DBGC ( ecm, "ECM %p link up\n", ecm ); + netdev_link_up ( netdev ); + } else if ( netdev_link_ok ( netdev ) && ! message->value ) { + DBGC ( ecm, "ECM %p link down\n", ecm ); + netdev_link_down ( netdev ); + } + break; + + case cpu_to_le16 ( CDC_CONNECTION_SPEED_CHANGE ) : + /* Ignore */ + break; + + default: + DBGC ( ecm, "ECM %p unrecognised interrupt:\n", ecm ); + DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -ENOTSUP; + goto error; + } + + /* Free I/O buffer */ + free_iob ( iobuf ); + profile_stop ( &ecm_intr_profiler ); + + return; + + error: + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); + ignore: + free_iob ( iobuf ); + return; +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations ecm_intr_operations = { + .complete = ecm_intr_complete, +}; + +/****************************************************************************** + * + * CDC-ECM data interface + * + ****************************************************************************** + */ + +/** + * Complete bulk IN transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ecm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int rc ) { + struct ecm_device *ecm = container_of ( ep, struct ecm_device, + usbnet.in ); + struct net_device *netdev = ecm->netdev; + + /* Profile receive completions */ + profile_start ( &ecm_in_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto ignore; + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( ecm, "ECM %p bulk IN failed: %s\n", + ecm, strerror ( rc ) ); + goto error; + } + + /* Hand off to network stack */ + netdev_rx ( netdev, iob_disown ( iobuf ) ); + + profile_stop ( &ecm_in_profiler ); + return; + + error: + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); + ignore: + free_iob ( iobuf ); +} + +/** Bulk IN endpoint operations */ +static struct usb_endpoint_driver_operations ecm_in_operations = { + .complete = ecm_in_complete, +}; + +/** + * Transmit packet + * + * @v ecm CDC-ECM device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ecm_out_transmit ( struct ecm_device *ecm, + struct io_buffer *iobuf ) { + int rc; + + /* Profile transmissions */ + profile_start ( &ecm_out_profiler ); + + /* Enqueue I/O buffer */ + if ( ( rc = usb_stream ( &ecm->usbnet.out, iobuf, 1 ) ) != 0 ) + return rc; + + profile_stop ( &ecm_out_profiler ); + return 0; +} + +/** + * Complete bulk OUT transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ecm_out_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int rc ) { + struct ecm_device *ecm = container_of ( ep, struct ecm_device, + usbnet.out ); + struct net_device *netdev = ecm->netdev; + + /* Report TX completion */ + netdev_tx_complete_err ( netdev, iobuf, rc ); +} + +/** Bulk OUT endpoint operations */ +static struct usb_endpoint_driver_operations ecm_out_operations = { + .complete = ecm_out_complete, +}; + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int ecm_open ( struct net_device *netdev ) { + struct ecm_device *ecm = netdev->priv; + struct usb_device *usb = ecm->usb; + unsigned int filter; + int rc; + + /* Open USB network device */ + if ( ( rc = usbnet_open ( &ecm->usbnet ) ) != 0 ) { + DBGC ( ecm, "ECM %p could not open: %s\n", + ecm, strerror ( rc ) ); + goto err_open; + } + + /* Set packet filter */ + filter = ( ECM_PACKET_TYPE_PROMISCUOUS | + ECM_PACKET_TYPE_ALL_MULTICAST | + ECM_PACKET_TYPE_DIRECTED | + ECM_PACKET_TYPE_BROADCAST ); + if ( ( rc = usb_control ( usb, ECM_SET_ETHERNET_PACKET_FILTER, + filter, ecm->usbnet.comms, NULL, 0 ) ) != 0 ){ + DBGC ( ecm, "ECM %p could not set packet filter: %s\n", + ecm, strerror ( rc ) ); + goto err_set_filter; + } + + return 0; + + err_set_filter: + usbnet_close ( &ecm->usbnet ); + err_open: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void ecm_close ( struct net_device *netdev ) { + struct ecm_device *ecm = netdev->priv; + + /* Close USB network device */ + usbnet_close ( &ecm->usbnet ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ecm_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct ecm_device *ecm = netdev->priv; + int rc; + + /* Transmit packet */ + if ( ( rc = ecm_out_transmit ( ecm, iobuf ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void ecm_poll ( struct net_device *netdev ) { + struct ecm_device *ecm = netdev->priv; + int rc; + + /* Poll USB bus */ + usb_poll ( ecm->bus ); + + /* Refill endpoints */ + if ( ( rc = usbnet_refill ( &ecm->usbnet ) ) != 0 ) + netdev_rx_err ( netdev, NULL, rc ); +} + +/** CDC-ECM network device operations */ +static struct net_device_operations ecm_operations = { + .open = ecm_open, + .close = ecm_close, + .transmit = ecm_transmit, + .poll = ecm_poll, +}; + +/****************************************************************************** + * + * USB interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int ecm_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct net_device *netdev; + struct ecm_device *ecm; + struct usb_interface_descriptor *comms; + struct ecm_ethernet_descriptor *ethernet; + int rc; + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *ecm ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &ecm_operations ); + netdev->dev = &func->dev; + ecm = netdev->priv; + memset ( ecm, 0, sizeof ( *ecm ) ); + ecm->usb = usb; + ecm->bus = usb->port->hub->bus; + ecm->netdev = netdev; + usbnet_init ( &ecm->usbnet, func, &ecm_intr_operations, + &ecm_in_operations, &ecm_out_operations ); + usb_refill_init ( &ecm->usbnet.intr, 0, ECM_INTR_MAX_FILL ); + usb_refill_init ( &ecm->usbnet.in, ECM_IN_MTU, ECM_IN_MAX_FILL ); + DBGC ( ecm, "ECM %p on %s\n", ecm, func->name ); + + /* Describe USB network device */ + if ( ( rc = usbnet_describe ( &ecm->usbnet, config ) ) != 0 ) { + DBGC ( ecm, "ECM %p could not describe: %s\n", + ecm, strerror ( rc ) ); + goto err_describe; + } + + /* Locate Ethernet descriptor */ + comms = usb_interface_descriptor ( config, ecm->usbnet.comms, 0 ); + assert ( comms != NULL ); + ethernet = ecm_ethernet_descriptor ( config, comms ); + if ( ! ethernet ) { + DBGC ( ecm, "ECM %p has no Ethernet descriptor\n", ecm ); + rc = -EINVAL; + goto err_ethernet; + } + + /* Fetch MAC address */ + if ( ( rc = ecm_fetch_mac ( usb, ethernet, netdev->hw_addr ) ) != 0 ) { + DBGC ( ecm, "ECM %p could not fetch MAC address: %s\n", + ecm, strerror ( rc ) ); + goto err_fetch_mac; + } + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + usb_func_set_drvdata ( func, ecm ); + return 0; + + unregister_netdev ( netdev ); + err_register: + err_fetch_mac: + err_ethernet: + err_describe: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void ecm_remove ( struct usb_function *func ) { + struct ecm_device *ecm = usb_func_get_drvdata ( func ); + struct net_device *netdev = ecm->netdev; + + unregister_netdev ( netdev ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** CDC-ECM device IDs */ +static struct usb_device_id ecm_ids[] = { + { + .name = "cdc-ecm", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + .class = { + .class = USB_CLASS_CDC, + .subclass = USB_SUBCLASS_CDC_ECM, + .protocol = 0, + }, + }, +}; + +/** CDC-ECM driver */ +struct usb_driver ecm_driver __usb_driver = { + .ids = ecm_ids, + .id_count = ( sizeof ( ecm_ids ) / sizeof ( ecm_ids[0] ) ), + .probe = ecm_probe, + .remove = ecm_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/ecm.h b/qemu/roms/ipxe/src/drivers/net/ecm.h new file mode 100644 index 000000000..83d324bdc --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/ecm.h @@ -0,0 +1,93 @@ +#ifndef _ECM_H +#define _ECM_H + +/** @file + * + * CDC-ECM USB Ethernet driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include <ipxe/cdc.h> + +/** CDC-ECM subclass */ +#define USB_SUBCLASS_CDC_ECM 0x06 + +/** Set Ethernet packet filter */ +#define ECM_SET_ETHERNET_PACKET_FILTER \ + ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE | \ + USB_REQUEST_TYPE ( 0x43 ) ) + +/** Ethernet packet types */ +enum ecm_ethernet_packet_filter { + /** Promiscuous mode */ + ECM_PACKET_TYPE_PROMISCUOUS = 0x0001, + /** All multicast packets */ + ECM_PACKET_TYPE_ALL_MULTICAST = 0x0002, + /** Unicast packets */ + ECM_PACKET_TYPE_DIRECTED = 0x0004, + /** Broadcast packets */ + ECM_PACKET_TYPE_BROADCAST = 0x0008, + /** Specified multicast packets */ + ECM_PACKET_TYPE_MULTICAST = 0x0010, +}; + +/** An Ethernet Functional Descriptor */ +struct ecm_ethernet_descriptor { + /** Descriptor header */ + struct usb_descriptor_header header; + /** Descriptor subtype */ + uint8_t subtype; + /** MAC address string */ + uint8_t mac; + /** Ethernet statistics bitmap */ + uint32_t statistics; + /** Maximum segment size */ + uint16_t mtu; + /** Multicast filter configuration */ + uint16_t mcast; + /** Number of wake-on-LAN filters */ + uint8_t wol; +} __attribute__ (( packed )); + +/** A CDC-ECM network device */ +struct ecm_device { + /** USB device */ + struct usb_device *usb; + /** USB bus */ + struct usb_bus *bus; + /** Network device */ + struct net_device *netdev; + /** USB network device */ + struct usbnet_device usbnet; +}; + +/** Interrupt maximum fill level + * + * This is a policy decision. + */ +#define ECM_INTR_MAX_FILL 2 + +/** Bulk IN maximum fill level + * + * This is a policy decision. + */ +#define ECM_IN_MAX_FILL 8 + +/** Bulk IN buffer size + * + * This is a policy decision. + */ +#define ECM_IN_MTU ( ETH_FRAME_LEN + 4 /* possible VLAN header */ ) + +extern struct ecm_ethernet_descriptor * +ecm_ethernet_descriptor ( struct usb_configuration_descriptor *config, + struct usb_interface_descriptor *interface ); +extern int ecm_fetch_mac ( struct usb_device *usb, + struct ecm_ethernet_descriptor *desc, + uint8_t *hw_addr ); + +#endif /* _ECM_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/eepro.c b/qemu/roms/ipxe/src/drivers/net/eepro.c index 909482bcc..97b4c4061 100644 --- a/qemu/roms/ipxe/src/drivers/net/eepro.c +++ b/qemu/roms/ipxe/src/drivers/net/eepro.c @@ -27,8 +27,18 @@ has 34 pins, the top row of 2 are not used. /* * 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, or (at - * your option) any later version. + * 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. */ FILE_LICENCE ( GPL2_OR_LATER ); @@ -591,9 +601,9 @@ static int eepro_probe ( struct nic *nic, struct isa_device *isa ) { l_eepro = 0; name = "Intel 82595-based LAN card"; } - station_addr.saddr[0] = swap16(station_addr.saddr[0]); - station_addr.saddr[1] = swap16(station_addr.saddr[1]); - station_addr.saddr[2] = swap16(station_addr.saddr[2]); + station_addr.saddr[0] = bswap_16(station_addr.saddr[0]); + station_addr.saddr[1] = bswap_16(station_addr.saddr[1]); + station_addr.saddr[2] = bswap_16(station_addr.saddr[2]); for (i = 0; i < ETH_ALEN; i++) { nic->node_addr[i] = station_addr.caddr[i]; } diff --git a/qemu/roms/ipxe/src/drivers/net/eepro100.c b/qemu/roms/ipxe/src/drivers/net/eepro100.c index ede0a1a4b..1046cda39 100644 --- a/qemu/roms/ipxe/src/drivers/net/eepro100.c +++ b/qemu/roms/ipxe/src/drivers/net/eepro100.c @@ -1136,7 +1136,6 @@ PCI_ROM(0x8086, 0x2449, "82562em", "Intel EtherExpressPro100 82562EM", 0), PCI_ROM(0x8086, 0x2459, "82562-1", "Intel 82562 based Fast Ethernet Connection", 0), PCI_ROM(0x8086, 0x245d, "82562-2", "Intel 82562 based Fast Ethernet Connection", 0), PCI_ROM(0x8086, 0x1050, "82562ez", "Intel 82562EZ Network Connection", 0), -PCI_ROM(0x8086, 0x1051, "eepro100-1051", "Intel 82801EB/ER (ICH5/ICH5R) Chipset Ethernet Controller", 0), PCI_ROM(0x8086, 0x1065, "82562-3", "Intel 82562 based Fast Ethernet Connection", 0), PCI_ROM(0x8086, 0x5200, "eepro100-5200", "Intel EtherExpress PRO/100 Intelligent Server", 0), PCI_ROM(0x8086, 0x5201, "eepro100-5201", "Intel EtherExpress PRO/100 Intelligent Server", 0), diff --git a/qemu/roms/ipxe/src/drivers/net/efi/nii.c b/qemu/roms/ipxe/src/drivers/net/efi/nii.c index d0d7da95a..b91848f5c 100644 --- a/qemu/roms/ipxe/src/drivers/net/efi/nii.c +++ b/qemu/roms/ipxe/src/drivers/net/efi/nii.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <strings.h> @@ -168,6 +172,9 @@ struct nii_nic { /** Saved task priority level */ EFI_TPL saved_tpl; + /** Media status is supported */ + int media; + /** Current transmit buffer */ struct io_buffer *txbuf; /** Current receive buffer */ @@ -408,6 +415,13 @@ static int nii_issue_cpb_db ( struct nii_nic *nii, unsigned int op, void *cpb, cdb.IFnum = nii->nii->IfNum; /* Issue command */ + DBGC2 ( nii, "NII %s issuing %02x:%04x ifnum %d%s%s\n", + nii->dev.name, cdb.OpCode, cdb.OpFlags, cdb.IFnum, + ( cpb ? " cpb" : "" ), ( db ? " db" : "" ) ); + if ( cpb ) + DBGC2_HD ( nii, cpb, cpb_len ); + if ( db ) + DBGC2_HD ( nii, db, db_len ); nii->issue ( ( intptr_t ) &cdb ); /* Check completion status */ @@ -552,6 +566,7 @@ static int nii_get_init_info ( struct nii_nic *nii, nii->buffer_len = db.MemoryRequired; nii->mtu = ( db.FrameDataLen + db.MediaHeaderLen ); netdev->max_pkt_len = nii->mtu; + nii->media = ( stat & PXE_STATFLAGS_GET_STATUS_NO_MEDIA_SUPPORTED ); return 0; } @@ -560,10 +575,12 @@ static int nii_get_init_info ( struct nii_nic *nii, * Initialise UNDI * * @v nii NII NIC + * @v flags Flags * @ret rc Return status code */ -static int nii_initialise ( struct nii_nic *nii ) { +static int nii_initialise_flags ( struct nii_nic *nii, unsigned int flags ) { PXE_CPB_INITIALIZE cpb; + PXE_DB_INITIALIZE db; unsigned int op; int stat; int rc; @@ -580,10 +597,13 @@ static int nii_initialise ( struct nii_nic *nii ) { cpb.MemoryAddr = ( ( intptr_t ) nii->buffer ); cpb.MemoryLength = nii->buffer_len; + /* Construct data block */ + memset ( &db, 0, sizeof ( db ) ); + /* Issue command */ - op = NII_OP ( PXE_OPCODE_INITIALIZE, - PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE ); - if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_INITIALIZE, flags ); + if ( ( stat = nii_issue_cpb_db ( nii, op, &cpb, sizeof ( cpb ), + &db, sizeof ( db ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not initialise: %s\n", nii->dev.name, strerror ( rc ) ); @@ -599,6 +619,36 @@ static int nii_initialise ( struct nii_nic *nii ) { } /** + * Initialise UNDI + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_initialise ( struct nii_nic *nii ) { + unsigned int flags; + + /* Initialise UNDI */ + flags = PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE; + return nii_initialise_flags ( nii, flags ); +} + +/** + * Initialise UNDI and detect cable + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_initialise_and_detect ( struct nii_nic *nii ) { + unsigned int flags; + + /* Initialise UNDI and detect cable. This is required to work + * around bugs in some Emulex NII drivers. + */ + flags = PXE_OPFLAGS_INITIALIZE_DETECT_CABLE; + return nii_initialise_flags ( nii, flags ); +} + +/** * Shut down UNDI * * @v nii NII NIC @@ -630,6 +680,7 @@ static void nii_shutdown ( struct nii_nic *nii ) { static int nii_get_station_address ( struct nii_nic *nii, struct net_device *netdev ) { PXE_DB_STATION_ADDRESS db; + unsigned int op; int stat; int rc; @@ -638,8 +689,9 @@ static int nii_get_station_address ( struct nii_nic *nii, goto err_initialise; /* Issue command */ - if ( ( stat = nii_issue_db ( nii, PXE_OPCODE_STATION_ADDRESS, &db, - sizeof ( db ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_STATION_ADDRESS, + PXE_OPFLAGS_STATION_ADDRESS_READ ); + if ( ( stat = nii_issue_db ( nii, op, &db, sizeof ( db ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not get station address: %s\n", nii->dev.name, strerror ( rc ) ); @@ -669,18 +721,25 @@ static int nii_get_station_address ( struct nii_nic *nii, */ static int nii_set_station_address ( struct nii_nic *nii, struct net_device *netdev ) { + uint32_t implementation = nii->undi->Implementation; PXE_CPB_STATION_ADDRESS cpb; + unsigned int op; int stat; int rc; + /* Fail if setting station address is unsupported */ + if ( ! ( implementation & PXE_ROMID_IMP_STATION_ADDR_SETTABLE ) ) + return -ENOTSUP; + /* Construct parameter block */ memset ( &cpb, 0, sizeof ( cpb ) ); memcpy ( cpb.StationAddr, netdev->ll_addr, netdev->ll_protocol->ll_addr_len ); /* Issue command */ - if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_STATION_ADDRESS, - &cpb, sizeof ( cpb ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_STATION_ADDRESS, + PXE_OPFLAGS_STATION_ADDRESS_WRITE ); + if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not set station address: %s\n", nii->dev.name, strerror ( rc ) ); @@ -697,21 +756,28 @@ static int nii_set_station_address ( struct nii_nic *nii, * @ret rc Return status code */ static int nii_set_rx_filters ( struct nii_nic *nii ) { + uint32_t implementation = nii->undi->Implementation; + unsigned int flags; unsigned int op; int stat; int rc; + /* Construct receive filter set */ + flags = ( PXE_OPFLAGS_RECEIVE_FILTER_ENABLE | + PXE_OPFLAGS_RECEIVE_FILTER_UNICAST ); + if ( implementation & PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST; + if ( implementation & PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS; + if ( implementation & PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST; + /* Issue command */ - op = NII_OP ( PXE_OPCODE_RECEIVE_FILTERS, - ( PXE_OPFLAGS_RECEIVE_FILTER_ENABLE | - PXE_OPFLAGS_RECEIVE_FILTER_UNICAST | - PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST | - PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS | - PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST ) ); + op = NII_OP ( PXE_OPCODE_RECEIVE_FILTERS, flags ); if ( ( stat = nii_issue ( nii, op ) ) < 0 ) { rc = -EIO_STAT ( stat ); - DBGC ( nii, "NII %s could not set receive filters: %s\n", - nii->dev.name, strerror ( rc ) ); + DBGC ( nii, "NII %s could not set receive filters %#04x: %s\n", + nii->dev.name, flags, strerror ( rc ) ); return rc; } @@ -729,6 +795,7 @@ static int nii_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { struct nii_nic *nii = netdev->priv; PXE_CPB_TRANSMIT cpb; + unsigned int op; int stat; int rc; @@ -745,8 +812,10 @@ static int nii_transmit ( struct net_device *netdev, cpb.MediaheaderLen = netdev->ll_protocol->ll_header_len; /* Transmit packet */ - if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_TRANSMIT, &cpb, - sizeof ( cpb ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_TRANSMIT, + ( PXE_OPFLAGS_TRANSMIT_WHOLE | + PXE_OPFLAGS_TRANSMIT_DONT_BLOCK ) ); + if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not transmit: %s\n", nii->dev.name, strerror ( rc ) ); @@ -772,12 +841,7 @@ static void nii_poll_tx ( struct net_device *netdev, unsigned int stat ) { return; /* Sanity check */ - if ( ! nii->txbuf ) { - DBGC ( nii, "NII %s reported spurious TX completion\n", - nii->dev.name ); - netdev_tx_err ( netdev, NULL, -EPIPE ); - return; - } + assert ( nii->txbuf != NULL ); /* Complete transmission */ iobuf = nii->txbuf; @@ -869,11 +933,14 @@ static void nii_poll ( struct net_device *netdev ) { int stat; int rc; + /* Construct data block */ + memset ( &db, 0, sizeof ( db ) ); + /* Get status */ op = NII_OP ( PXE_OPCODE_GET_STATUS, ( PXE_OPFLAGS_GET_INTERRUPT_STATUS | - PXE_OPFLAGS_GET_TRANSMITTED_BUFFERS | - PXE_OPFLAGS_GET_MEDIA_STATUS ) ); + ( nii->txbuf ? PXE_OPFLAGS_GET_TRANSMITTED_BUFFERS : 0)| + ( nii->media ? PXE_OPFLAGS_GET_MEDIA_STATUS : 0 ) ) ); if ( ( stat = nii_issue_db ( nii, op, &db, sizeof ( db ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not get status: %s\n", @@ -882,13 +949,15 @@ static void nii_poll ( struct net_device *netdev ) { } /* Process any TX completions */ - nii_poll_tx ( netdev, stat ); + if ( nii->txbuf ) + nii_poll_tx ( netdev, stat ); /* Process any RX completions */ nii_poll_rx ( netdev ); /* Check for link state changes */ - nii_poll_link ( netdev, stat ); + if ( nii->media ) + nii_poll_link ( netdev, stat ); } /** @@ -901,8 +970,18 @@ static int nii_open ( struct net_device *netdev ) { struct nii_nic *nii = netdev->priv; int rc; - /* Initialise NIC */ - if ( ( rc = nii_initialise ( nii ) ) != 0 ) + /* Initialise NIC + * + * Some Emulex NII drivers have a bug which prevents packets + * from being sent or received unless we specifically ask it + * to detect cable presence during initialisation. Work + * around these buggy drivers by requesting cable detection at + * this point, even though we don't care about link state here + * (and would prefer to have the NIC initialise even if no + * cable is present, to match the behaviour of all other iPXE + * drivers). + */ + if ( ( rc = nii_initialise_and_detect ( nii ) ) != 0 ) goto err_initialise; /* Attempt to set station address */ @@ -1023,8 +1102,9 @@ int nii_start ( struct efi_device *efidev ) { nii->issue = ( ( ( void * ) nii->undi ) + nii->undi->EntryPoint ); } - DBGC ( nii, "NII %s using UNDI v%x.%x at %p entry %p\n", nii->dev.name, - nii->nii->MajorVer, nii->nii->MinorVer, nii->undi, nii->issue ); + DBGC ( nii, "NII %s using UNDI v%x.%x at %p entry %p impl %#08x\n", + nii->dev.name, nii->nii->MajorVer, nii->nii->MinorVer, + nii->undi, nii->issue, nii->undi->Implementation ); /* Open PCI I/O protocols and locate BARs */ if ( ( rc = nii_pci_open ( nii ) ) != 0 ) @@ -1048,6 +1128,10 @@ int nii_start ( struct efi_device *efidev ) { DBGC ( nii, "NII %s registered as %s for %p %s\n", nii->dev.name, netdev->name, device, efi_handle_name ( device ) ); + /* Set initial link state (if media detection is not supported) */ + if ( ! nii->media ) + netdev_link_up ( netdev ); + return 0; unregister_netdev ( netdev ); diff --git a/qemu/roms/ipxe/src/drivers/net/efi/nii.h b/qemu/roms/ipxe/src/drivers/net/efi/nii.h index de0ac687b..c10be9db5 100644 --- a/qemu/roms/ipxe/src/drivers/net/efi/nii.h +++ b/qemu/roms/ipxe/src/drivers/net/efi/nii.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct efi_device; diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snp.c b/qemu/roms/ipxe/src/drivers/net/efi/snp.c index 2b5fc8618..acfcfba9f 100644 --- a/qemu/roms/ipxe/src/drivers/net/efi/snp.c +++ b/qemu/roms/ipxe/src/drivers/net/efi/snp.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <errno.h> #include <ipxe/efi/efi.h> diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snponly.c b/qemu/roms/ipxe/src/drivers/net/efi/snponly.c index 99f264bca..73abfdbf4 100644 --- a/qemu/roms/ipxe/src/drivers/net/efi/snponly.c +++ b/qemu/roms/ipxe/src/drivers/net/efi/snponly.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/drivers/net/etherfabric.c b/qemu/roms/ipxe/src/drivers/net/etherfabric.c index 5e0efb1e1..29d117443 100644 --- a/qemu/roms/ipxe/src/drivers/net/etherfabric.c +++ b/qemu/roms/ipxe/src/drivers/net/etherfabric.c @@ -3176,7 +3176,7 @@ falcon_probe_nic_variant ( struct efab_nic *efab, struct pci_device *pci ) uint8_t revision; /* PCI revision */ - pci_read_config_byte ( pci, PCI_CLASS_REVISION, &revision ); + pci_read_config_byte ( pci, PCI_REVISION, &revision ); efab->pci_revision = revision; /* Asic vs FPGA */ diff --git a/qemu/roms/ipxe/src/drivers/net/forcedeth.c b/qemu/roms/ipxe/src/drivers/net/forcedeth.c index d8ece9a7a..79938cbbb 100644 --- a/qemu/roms/ipxe/src/drivers/net/forcedeth.c +++ b/qemu/roms/ipxe/src/drivers/net/forcedeth.c @@ -1749,10 +1749,8 @@ forcedeth_map_regs ( struct forcedeth_private *priv ) for ( reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4 ) { pci_read_config_dword ( priv->pci_dev, reg, &bar ); - if ( ( ( bar & PCI_BASE_ADDRESS_SPACE ) == - PCI_BASE_ADDRESS_SPACE_MEMORY ) && - ( pci_bar_size ( priv->pci_dev, reg ) >= - register_size ) ) { + if ( ( ! ( bar & PCI_BASE_ADDRESS_SPACE_IO ) ) && + ( pci_bar_size ( priv->pci_dev, reg ) >= register_size ) ){ addr = pci_bar_start ( priv->pci_dev, reg ); break; } diff --git a/qemu/roms/ipxe/src/drivers/net/igbvf/igbvf_main.c b/qemu/roms/ipxe/src/drivers/net/igbvf/igbvf_main.c index aace5ad56..fc7021c38 100644 --- a/qemu/roms/ipxe/src/drivers/net/igbvf/igbvf_main.c +++ b/qemu/roms/ipxe/src/drivers/net/igbvf/igbvf_main.c @@ -461,7 +461,7 @@ static int __devinit igbvf_sw_init ( struct igbvf_adapter *adapter ) hw->vendor_id = pdev->vendor; hw->device_id = pdev->device; - pci_read_config_byte ( pdev, PCI_REVISION_ID, &hw->revision_id ); + pci_read_config_byte ( pdev, PCI_REVISION, &hw->revision_id ); pci_read_config_word ( pdev, PCI_COMMAND, &hw->bus.pci_cmd_word ); diff --git a/qemu/roms/ipxe/src/drivers/net/intel.c b/qemu/roms/ipxe/src/drivers/net/intel.c index a89f947b2..6309e9aa5 100644 --- a/qemu/roms/ipxe/src/drivers/net/intel.c +++ b/qemu/roms/ipxe/src/drivers/net/intel.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> @@ -248,32 +252,6 @@ static int intel_fetch_mac ( struct intel_nic *intel, uint8_t *hw_addr ) { /****************************************************************************** * - * Diagnostics - * - ****************************************************************************** - */ - -/** - * Dump diagnostic information - * - * @v intel Intel device - */ -static void __attribute__ (( unused )) intel_diag ( struct intel_nic *intel ) { - - DBGC ( intel, "INTEL %p TX %04x(%02x)/%04x(%02x) " - "RX %04x(%02x)/%04x(%02x)\n", intel, - ( intel->tx.cons & 0xffff ), - readl ( intel->regs + intel->tx.reg + INTEL_xDH ), - ( intel->tx.prod & 0xffff ), - readl ( intel->regs + intel->tx.reg + INTEL_xDT ), - ( intel->rx.cons & 0xffff ), - readl ( intel->regs + intel->rx.reg + INTEL_xDH ), - ( intel->rx.prod & 0xffff ), - readl ( intel->regs + intel->rx.reg + INTEL_xDT ) ); -} - -/****************************************************************************** - * * Device reset * ****************************************************************************** @@ -371,6 +349,67 @@ static void intel_check_link ( struct net_device *netdev ) { /****************************************************************************** * + * Descriptors + * + ****************************************************************************** + */ + +/** + * Populate transmit descriptor + * + * @v tx Transmit descriptor + * @v addr Data buffer address + * @v len Length of data + */ +void intel_describe_tx ( struct intel_descriptor *tx, physaddr_t addr, + size_t len ) { + + /* Populate transmit descriptor */ + tx->address = cpu_to_le64 ( addr ); + tx->length = cpu_to_le16 ( len ); + tx->flags = 0; + tx->command = ( INTEL_DESC_CMD_RS | INTEL_DESC_CMD_IFCS | + INTEL_DESC_CMD_EOP ); + tx->status = 0; +} + +/** + * Populate advanced transmit descriptor + * + * @v tx Transmit descriptor + * @v addr Data buffer address + * @v len Length of data + */ +void intel_describe_tx_adv ( struct intel_descriptor *tx, physaddr_t addr, + size_t len ) { + + /* Populate advanced transmit descriptor */ + tx->address = cpu_to_le64 ( addr ); + tx->length = cpu_to_le16 ( len ); + tx->flags = INTEL_DESC_FL_DTYP_DATA; + tx->command = ( INTEL_DESC_CMD_DEXT | INTEL_DESC_CMD_RS | + INTEL_DESC_CMD_IFCS | INTEL_DESC_CMD_EOP ); + tx->status = cpu_to_le32 ( INTEL_DESC_STATUS_PAYLEN ( len ) ); +} + +/** + * Populate receive descriptor + * + * @v rx Receive descriptor + * @v addr Data buffer address + * @v len Length of data + */ +void intel_describe_rx ( struct intel_descriptor *rx, physaddr_t addr, + size_t len __unused ) { + + /* Populate transmit descriptor */ + rx->address = cpu_to_le64 ( addr ); + rx->length = 0; + rx->status = 0; +} + +/****************************************************************************** + * * Network device interface * ****************************************************************************** @@ -479,10 +518,7 @@ void intel_refill_rx ( struct intel_nic *intel ) { /* Populate receive descriptor */ address = virt_to_bus ( iobuf->data ); - rx->address = cpu_to_le64 ( address ); - rx->length = 0; - rx->status = 0; - rx->errors = 0; + intel->rx.describe ( rx, address, 0 ); /* Record I/O buffer */ assert ( intel->rx_iobuf[rx_idx] == NULL ); @@ -568,6 +604,13 @@ static int intel_open ( struct net_device *netdev ) { /* Update link state */ intel_check_link ( netdev ); + /* Apply required errata */ + if ( intel->flags & INTEL_VMWARE ) { + DBGC ( intel, "INTEL %p applying VMware errata workaround\n", + intel ); + intel->force_icr = INTEL_IRQ_RXT0; + } + return 0; intel_destroy_ring ( intel, &intel->rx ); @@ -617,6 +660,7 @@ int intel_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { unsigned int tx_idx; unsigned int tx_tail; physaddr_t address; + size_t len; /* Get next transmit descriptor */ if ( ( intel->tx.prod - intel->tx.cons ) >= INTEL_TX_FILL ) { @@ -629,11 +673,8 @@ int intel_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { /* Populate transmit descriptor */ address = virt_to_bus ( iobuf->data ); - tx->address = cpu_to_le64 ( address ); - tx->length = cpu_to_le16 ( iob_len ( iobuf ) ); - tx->command = ( INTEL_DESC_CMD_RS | INTEL_DESC_CMD_IFCS | - INTEL_DESC_CMD_EOP ); - tx->status = 0; + len = iob_len ( iobuf ); + intel->tx.describe ( tx, address, len ); wmb(); /* Notify card that there are packets ready to transmit */ @@ -644,7 +685,7 @@ int intel_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { DBGC2 ( intel, "INTEL %p TX %d is [%llx,%llx)\n", intel, tx_idx, ( ( unsigned long long ) address ), - ( ( unsigned long long ) address + iob_len ( iobuf ) ) ); + ( ( unsigned long long ) address + len ) ); return 0; } @@ -667,7 +708,7 @@ void intel_poll_tx ( struct net_device *netdev ) { tx = &intel->tx.desc[tx_idx]; /* Stop if descriptor is still in use */ - if ( ! ( tx->status & INTEL_DESC_STATUS_DD ) ) + if ( ! ( tx->status & cpu_to_le32 ( INTEL_DESC_STATUS_DD ) ) ) return; DBGC2 ( intel, "INTEL %p TX %d complete\n", intel, tx_idx ); @@ -698,7 +739,7 @@ void intel_poll_rx ( struct net_device *netdev ) { rx = &intel->rx.desc[rx_idx]; /* Stop if descriptor is still in use */ - if ( ! ( rx->status & INTEL_DESC_STATUS_DD ) ) + if ( ! ( rx->status & cpu_to_le32 ( INTEL_DESC_STATUS_DD ) ) ) return; /* Populate I/O buffer */ @@ -708,10 +749,10 @@ void intel_poll_rx ( struct net_device *netdev ) { iob_put ( iobuf, len ); /* Hand off to network stack */ - if ( rx->errors ) { + if ( rx->status & cpu_to_le32 ( INTEL_DESC_STATUS_RXE ) ) { DBGC ( intel, "INTEL %p RX %d error (length %zd, " - "errors %02x)\n", - intel, rx_idx, len, rx->errors ); + "status %08x)\n", intel, rx_idx, len, + le32_to_cpu ( rx->status ) ); netdev_rx_err ( netdev, iobuf, -EIO ); } else { DBGC2 ( intel, "INTEL %p RX %d complete (length %zd)\n", @@ -736,6 +777,7 @@ static void intel_poll ( struct net_device *netdev ) { icr = readl ( intel->regs + INTEL_ICR ); profile_stop ( &intel_vm_poll_profiler ); profile_exclude ( &intel_vm_poll_profiler ); + icr |= intel->force_icr; if ( ! icr ) return; @@ -755,6 +797,14 @@ static void intel_poll ( struct net_device *netdev ) { if ( icr & INTEL_IRQ_LSC ) intel_check_link ( netdev ); + /* Check for unexpected interrupts */ + if ( icr & ~( INTEL_IRQ_TXDW | INTEL_IRQ_TXQE | INTEL_IRQ_LSC | + INTEL_IRQ_RXDMT0 | INTEL_IRQ_RXT0 | INTEL_IRQ_RXO ) ) { + DBGC ( intel, "INTEL %p unexpected ICR %08x\n", intel, icr ); + /* Report as a TX error */ + netdev_tx_err ( netdev, NULL, -ENOTSUP ); + } + /* Refill RX ring */ intel_refill_rx ( intel ); } @@ -817,8 +867,10 @@ static int intel_probe ( struct pci_device *pci ) { memset ( intel, 0, sizeof ( *intel ) ); intel->port = PCI_FUNC ( pci->busdevfn ); intel->flags = pci->id->driver_data; - intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTEL_TD ); - intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTEL_RD ); + intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTEL_TD, + intel_describe_tx ); + intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTEL_RD, + intel_describe_rx ); /* Fix up PCI device */ adjust_pci_device ( pci ); @@ -895,7 +947,7 @@ static struct pci_device_id intel_nics[] = { PCI_ROM ( 0x8086, 0x100c, "82544gc", "82544GC (Copper)", 0 ), PCI_ROM ( 0x8086, 0x100d, "82544gc-l", "82544GC (LOM)", 0 ), PCI_ROM ( 0x8086, 0x100e, "82540em", "82540EM", 0 ), - PCI_ROM ( 0x8086, 0x100f, "82545em", "82545EM (Copper)", 0 ), + PCI_ROM ( 0x8086, 0x100f, "82545em", "82545EM (Copper)", INTEL_VMWARE ), PCI_ROM ( 0x8086, 0x1010, "82546eb", "82546EB (Copper)", 0 ), PCI_ROM ( 0x8086, 0x1011, "82545em-f", "82545EM (Fiber)", 0 ), PCI_ROM ( 0x8086, 0x1012, "82546eb-f", "82546EB (Fiber)", 0 ), @@ -998,6 +1050,12 @@ static struct pci_device_id intel_nics[] = { PCI_ROM ( 0x8086, 0x1533, "i210", "I210", 0 ), PCI_ROM ( 0x8086, 0x153a, "i217lm", "I217-LM", 0 ), PCI_ROM ( 0x8086, 0x153b, "i217v", "I217-V", 0 ), + PCI_ROM ( 0x8086, 0x1559, "i218v", "I218-V", 0), + PCI_ROM ( 0x8086, 0x155a, "i218lm", "I218-LM", 0), + PCI_ROM ( 0x8086, 0x15a0, "i218lm-2", "I218-LM", 0 ), + PCI_ROM ( 0x8086, 0x15a1, "i218v-2", "I218-V", 0 ), + PCI_ROM ( 0x8086, 0x15a2, "i218lm-3", "I218-LM", 0 ), + PCI_ROM ( 0x8086, 0x15a3, "i218v-3", "I218-V", 0 ), PCI_ROM ( 0x8086, 0x294c, "82566dc-2", "82566DC-2", 0 ), PCI_ROM ( 0x8086, 0x2e6e, "cemedia", "CE Media Processor", 0 ), }; diff --git a/qemu/roms/ipxe/src/drivers/net/intel.h b/qemu/roms/ipxe/src/drivers/net/intel.h index 8c4479bb4..ce9e3f467 100644 --- a/qemu/roms/ipxe/src/drivers/net/intel.h +++ b/qemu/roms/ipxe/src/drivers/net/intel.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/if_ether.h> @@ -22,33 +22,38 @@ struct intel_descriptor { uint64_t address; /** Length */ uint16_t length; - /** Reserved */ - uint8_t reserved_a; + /** Flags */ + uint8_t flags; /** Command */ uint8_t command; /** Status */ - uint8_t status; - /** Errors */ - uint8_t errors; - /** Reserved */ - uint16_t reserved_b; + uint32_t status; } __attribute__ (( packed )); -/** Packet descriptor command bits */ -enum intel_descriptor_command { - /** Report status */ - INTEL_DESC_CMD_RS = 0x08, - /** Insert frame checksum (CRC) */ - INTEL_DESC_CMD_IFCS = 0x02, - /** End of packet */ - INTEL_DESC_CMD_EOP = 0x01, -}; +/** Descriptor type */ +#define INTEL_DESC_FL_DTYP( dtyp ) ( (dtyp) << 4 ) +#define INTEL_DESC_FL_DTYP_DATA INTEL_DESC_FL_DTYP ( 0x03 ) -/** Packet descriptor status bits */ -enum intel_descriptor_status { - /** Descriptor done */ - INTEL_DESC_STATUS_DD = 0x01, -}; +/** Descriptor extension */ +#define INTEL_DESC_CMD_DEXT 0x20 + +/** Report status */ +#define INTEL_DESC_CMD_RS 0x08 + +/** Insert frame checksum (CRC) */ +#define INTEL_DESC_CMD_IFCS 0x02 + +/** End of packet */ +#define INTEL_DESC_CMD_EOP 0x01 + +/** Descriptor done */ +#define INTEL_DESC_STATUS_DD 0x00000001UL + +/** Receive error */ +#define INTEL_DESC_STATUS_RXE 0x00000100UL + +/** Payload length */ +#define INTEL_DESC_STATUS_PAYLEN( len ) ( (len) << 14 ) /** Device Control Register */ #define INTEL_CTRL 0x00000UL @@ -91,7 +96,9 @@ enum intel_descriptor_status { /** Interrupt Cause Read Register */ #define INTEL_ICR 0x000c0UL #define INTEL_IRQ_TXDW 0x00000001UL /**< Transmit descriptor done */ +#define INTEL_IRQ_TXQE 0x00000002UL /**< Transmit queue empty */ #define INTEL_IRQ_LSC 0x00000004UL /**< Link status change */ +#define INTEL_IRQ_RXDMT0 0x00000010UL /**< Receive queue low */ #define INTEL_IRQ_RXT0 0x00000080UL /**< Receive timer */ #define INTEL_IRQ_RXO 0x00000400UL /**< Receive overrun */ @@ -207,6 +214,15 @@ struct intel_ring { unsigned int reg; /** Length (in bytes) */ size_t len; + + /** Populate descriptor + * + * @v desc Descriptor + * @v addr Data buffer address + * @v len Length of data + */ + void ( * describe ) ( struct intel_descriptor *desc, physaddr_t addr, + size_t len ); }; /** @@ -215,12 +231,39 @@ struct intel_ring { * @v ring Descriptor ring * @v count Number of descriptors * @v reg Descriptor register block + * @v describe Method to populate descriptor */ static inline __attribute__ (( always_inline)) void -intel_init_ring ( struct intel_ring *ring, unsigned int count, - unsigned int reg ) { +intel_init_ring ( struct intel_ring *ring, unsigned int count, unsigned int reg, + void ( * describe ) ( struct intel_descriptor *desc, + physaddr_t addr, size_t len ) ) { + ring->len = ( count * sizeof ( ring->desc[0] ) ); ring->reg = reg; + ring->describe = describe; +} + +/** An Intel virtual function mailbox */ +struct intel_mailbox { + /** Mailbox control register */ + unsigned int ctrl; + /** Mailbox memory base */ + unsigned int mem; +}; + +/** + * Initialise mailbox + * + * @v mbox Mailbox + * @v ctrl Mailbox control register + * @v mem Mailbox memory register base + */ +static inline __attribute__ (( always_inline )) void +intel_init_mbox ( struct intel_mailbox *mbox, unsigned int ctrl, + unsigned int mem ) { + + mbox->ctrl = ctrl; + mbox->mem = mem; } /** An Intel network card */ @@ -231,6 +274,8 @@ struct intel_nic { unsigned int port; /** Flags */ unsigned int flags; + /** Forced interrupts */ + unsigned int force_icr; /** EEPROM */ struct nvs_device eeprom; @@ -239,6 +284,9 @@ struct intel_nic { /** EEPROM address shift */ unsigned int eerd_addr_shift; + /** Mailbox */ + struct intel_mailbox mbox; + /** Transmit descriptor ring */ struct intel_ring tx; /** Receive descriptor ring */ @@ -251,8 +299,35 @@ struct intel_nic { enum intel_flags { /** PBS/PBA errata workaround required */ INTEL_PBS_ERRATA = 0x0001, + /** VMware missing interrupt workaround required */ + INTEL_VMWARE = 0x0002, }; +/** + * Dump diagnostic information + * + * @v intel Intel device + */ +static inline void intel_diag ( struct intel_nic *intel ) { + + DBGC ( intel, "INTEL %p TX %04x(%02x)/%04x(%02x) " + "RX %04x(%02x)/%04x(%02x)\n", intel, + ( intel->tx.cons & 0xffff ), + readl ( intel->regs + intel->tx.reg + INTEL_xDH ), + ( intel->tx.prod & 0xffff ), + readl ( intel->regs + intel->tx.reg + INTEL_xDT ), + ( intel->rx.cons & 0xffff ), + readl ( intel->regs + intel->rx.reg + INTEL_xDH ), + ( intel->rx.prod & 0xffff ), + readl ( intel->regs + intel->rx.reg + INTEL_xDT ) ); +} + +extern void intel_describe_tx ( struct intel_descriptor *tx, + physaddr_t addr, size_t len ); +extern void intel_describe_tx_adv ( struct intel_descriptor *tx, + physaddr_t addr, size_t len ); +extern void intel_describe_rx ( struct intel_descriptor *rx, + physaddr_t addr, size_t len ); extern int intel_create_ring ( struct intel_nic *intel, struct intel_ring *ring ); extern void intel_destroy_ring ( struct intel_nic *intel, diff --git a/qemu/roms/ipxe/src/drivers/net/intelvf.c b/qemu/roms/ipxe/src/drivers/net/intelvf.c new file mode 100644 index 000000000..ac6fea745 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/intelvf.c @@ -0,0 +1,340 @@ +/* + * 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 <string.h> +#include <unistd.h> +#include <errno.h> +#include <ipxe/io.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include "intelvf.h" + +/** @file + * + * Intel 10/100/1000 virtual function network card driver + * + */ + +/****************************************************************************** + * + * Mailbox messages + * + ****************************************************************************** + */ + +/** + * Write message to mailbox + * + * @v intel Intel device + * @v msg Message + */ +static void intelvf_mbox_write ( struct intel_nic *intel, + const union intelvf_msg *msg ) { + unsigned int i; + + /* Write message */ + DBGC2 ( intel, "INTEL %p sending message", intel ); + for ( i = 0 ; i < ( sizeof ( *msg ) / sizeof ( msg->dword[0] ) ) ; i++){ + DBGC2 ( intel, "%c%08x", ( i ? ':' : ' ' ), msg->dword[i] ); + writel ( msg->dword[i], ( intel->regs + intel->mbox.mem + + ( i * sizeof ( msg->dword[0] ) ) ) ); + } + DBGC2 ( intel, "\n" ); +} + +/** + * Read message from mailbox + * + * @v intel Intel device + * @v msg Message + */ +static void intelvf_mbox_read ( struct intel_nic *intel, + union intelvf_msg *msg ) { + unsigned int i; + + /* Read message */ + DBGC2 ( intel, "INTEL %p received message", intel ); + for ( i = 0 ; i < ( sizeof ( *msg ) / sizeof ( msg->dword[0] ) ) ; i++){ + msg->dword[i] = readl ( intel->regs + intel->mbox.mem + + ( i * sizeof ( msg->dword[0] ) ) ); + DBGC2 ( intel, "%c%08x", ( i ? ':' : ' ' ), msg->dword[i] ); + } + DBGC2 ( intel, "\n" ); +} + +/** + * Poll mailbox + * + * @v intel Intel device + * @ret rc Return status code + * + * Note that polling the mailbox may fail if the underlying PF is + * reset. + */ +int intelvf_mbox_poll ( struct intel_nic *intel ) { + struct intel_mailbox *mbox = &intel->mbox; + union intelvf_msg msg; + uint32_t ctrl; + + /* Get mailbox status */ + ctrl = readl ( intel->regs + mbox->ctrl ); + + /* Fail if a reset is in progress */ + if ( ctrl & INTELVF_MBCTRL_RSTI ) + return -EPIPE; + + /* Acknowledge (and ignore) any received messages */ + if ( ctrl & INTELVF_MBCTRL_PFSTS ) { + intelvf_mbox_read ( intel, &msg ); + writel ( INTELVF_MBCTRL_ACK, intel->regs + mbox->ctrl ); + } + + return 0; +} + +/** + * Wait for PF reset to complete + * + * @v intel Intel device + * @ret rc Return status code + */ +int intelvf_mbox_wait ( struct intel_nic *intel ) { + unsigned int i; + int rc; + + /* Wait until a poll completes successfully */ + for ( i = 0 ; i < INTELVF_MBOX_MAX_WAIT_MS ; i++ ) { + + /* Check for successful poll */ + if ( ( rc = intelvf_mbox_poll ( intel ) ) == 0 ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( intel, "INTEL %p timed out waiting for reset\n", intel ); + return -ETIMEDOUT; +} + +/** + * Send/receive mailbox message + * + * @v intel Intel device + * @v msg Message buffer + * @ret rc Return status code + */ +int intelvf_mbox_msg ( struct intel_nic *intel, union intelvf_msg *msg ) { + struct intel_mailbox *mbox = &intel->mbox; + uint32_t ctrl; + uint32_t seen = 0; + unsigned int i; + + /* Sanity check */ + assert ( ! ( msg->hdr & INTELVF_MSG_RESPONSE ) ); + + /* Handle mailbox */ + for ( i = 0 ; i < INTELVF_MBOX_MAX_WAIT_MS ; i++ ) { + + /* Attempt to claim mailbox, if we have not yet sent + * our message. + */ + if ( ! ( seen & INTELVF_MBCTRL_VFU ) ) + writel ( INTELVF_MBCTRL_VFU, intel->regs + mbox->ctrl ); + + /* Get mailbox status and record observed flags */ + ctrl = readl ( intel->regs + mbox->ctrl ); + seen |= ctrl; + + /* If a reset is in progress, clear VFU and abort */ + if ( ctrl & INTELVF_MBCTRL_RSTI ) { + writel ( 0, intel->regs + mbox->ctrl ); + return -EPIPE; + } + + /* Write message to mailbox, if applicable. This + * potentially overwrites a message sent by the PF (if + * the PF has simultaneously released PFU (thus + * allowing our VFU) and asserted PFSTS), but that + * doesn't really matter since there are no + * unsolicited PF->VF messages that require the actual + * message content to be observed. + */ + if ( ctrl & INTELVF_MBCTRL_VFU ) + intelvf_mbox_write ( intel, msg ); + + /* Read message from mailbox, if applicable. */ + if ( ( seen & INTELVF_MBCTRL_VFU ) && + ( seen & INTELVF_MBCTRL_PFACK ) && + ( ctrl & INTELVF_MBCTRL_PFSTS ) ) + intelvf_mbox_read ( intel, msg ); + + /* Acknowledge received message (if applicable), + * release VFU lock, and send message (if applicable). + */ + ctrl = ( ( ( ctrl & INTELVF_MBCTRL_PFSTS ) ? + INTELVF_MBCTRL_ACK : 0 ) | + ( ( ctrl & INTELVF_MBCTRL_VFU ) ? + INTELVF_MBCTRL_REQ : 0 ) ); + writel ( ctrl, intel->regs + mbox->ctrl ); + + /* Exit successfully if we have received a response */ + if ( msg->hdr & INTELVF_MSG_RESPONSE ) { + + /* Sanity check */ + assert ( seen & INTELVF_MBCTRL_VFU ); + assert ( seen & INTELVF_MBCTRL_PFACK ); + assert ( seen & INTELVF_MBCTRL_PFSTS ); + + return 0; + } + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( intel, "INTEL %p timed out waiting for mailbox (seen %08x)\n", + intel, seen ); + return -ETIMEDOUT; +} + +/** + * Send reset message and get initial MAC address + * + * @v intel Intel device + * @v hw_addr Hardware address to fill in, or NULL + * @ret rc Return status code + */ +int intelvf_mbox_reset ( struct intel_nic *intel, uint8_t *hw_addr ) { + union intelvf_msg msg; + int rc; + + /* Send reset message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.hdr = INTELVF_MSG_TYPE_RESET; + if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) { + DBGC ( intel, "INTEL %p reset failed: %s\n", + intel, strerror ( rc ) ); + return rc; + } + + /* Check response */ + if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_RESET ) { + DBGC ( intel, "INTEL %p reset unexpected response:\n", intel ); + DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) ); + return -EPROTO; + } + + /* Fill in MAC address, if applicable */ + if ( hw_addr ) { + if ( msg.hdr & INTELVF_MSG_ACK ) { + memcpy ( hw_addr, msg.mac.mac, sizeof ( msg.mac.mac ) ); + DBGC ( intel, "INTEL %p reset assigned MAC address " + "%s\n", intel, eth_ntoa ( hw_addr ) ); + } else { + eth_random_addr ( hw_addr ); + DBGC ( intel, "INTEL %p reset generated MAC address " + "%s\n", intel, eth_ntoa ( hw_addr ) ); + } + } + + return 0; +} + +/** + * Send set MAC address message + * + * @v intel Intel device + * @v ll_addr Link-layer address + * @ret rc Return status code + */ +int intelvf_mbox_set_mac ( struct intel_nic *intel, const uint8_t *ll_addr ) { + union intelvf_msg msg; + int rc; + + /* Send set MAC address message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.hdr = INTELVF_MSG_TYPE_SET_MAC; + memcpy ( msg.mac.mac, ll_addr, sizeof ( msg.mac.mac ) ); + if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) { + DBGC ( intel, "INTEL %p set MAC address failed: %s\n", + intel, strerror ( rc ) ); + return rc; + } + + /* Check response */ + if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_SET_MAC ) { + DBGC ( intel, "INTEL %p set MAC address unexpected response:\n", + intel ); + DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) ); + return -EPROTO; + } + + /* Check that we were allowed to set the MAC address */ + if ( ! ( msg.hdr & INTELVF_MSG_ACK ) ) { + DBGC ( intel, "INTEL %p set MAC address refused\n", intel ); + return -EPERM; + } + + return 0; +} + +/** + * Send set MTU message + * + * @v intel Intel device + * @v mtu Maximum packet size + * @ret rc Return status code + */ +int intelvf_mbox_set_mtu ( struct intel_nic *intel, size_t mtu ) { + union intelvf_msg msg; + int rc; + + /* Send set MTU message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.hdr = INTELVF_MSG_TYPE_SET_MTU; + msg.mtu.mtu = mtu; + if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) { + DBGC ( intel, "INTEL %p set MTU failed: %s\n", + intel, strerror ( rc ) ); + return rc; + } + + /* Check response */ + if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_SET_MTU ) { + DBGC ( intel, "INTEL %p set MTU unexpected response:\n", + intel ); + DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) ); + return -EPROTO; + } + + /* Check that we were allowed to set the MTU */ + if ( ! ( msg.hdr & INTELVF_MSG_ACK ) ) { + DBGC ( intel, "INTEL %p set MTU refused\n", intel ); + return -EPERM; + } + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/net/intelvf.h b/qemu/roms/ipxe/src/drivers/net/intelvf.h new file mode 100644 index 000000000..d2f98d874 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/intelvf.h @@ -0,0 +1,109 @@ +#ifndef _INTELVF_H +#define _INTELVF_H + +/** @file + * + * Intel 10/100/1000 virtual function network card driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include "intel.h" + +/** Intel VF BAR size */ +#define INTELVF_BAR_SIZE ( 16 * 1024 ) + +/** Mailbox Control Register */ +#define INTELVF_MBCTRL 0x0c40UL +#define INTELVF_MBCTRL_REQ 0x00000001UL /**< Request for PF ready */ +#define INTELVF_MBCTRL_ACK 0x00000002UL /**< PF message received */ +#define INTELVF_MBCTRL_VFU 0x00000004UL /**< Buffer taken by VF */ +#define INTELVF_MBCTRL_PFU 0x00000008UL /**< Buffer taken to PF */ +#define INTELVF_MBCTRL_PFSTS 0x00000010UL /**< PF wrote a message */ +#define INTELVF_MBCTRL_PFACK 0x00000020UL /**< PF acknowledged message */ +#define INTELVF_MBCTRL_RSTI 0x00000040UL /**< PF reset in progress */ +#define INTELVF_MBCTRL_RSTD 0x00000080UL /**< PF reset complete */ + +/** Mailbox Memory Register Base */ +#define INTELVF_MBMEM 0x0800UL + +/** Reset mailbox message */ +#define INTELVF_MSG_TYPE_RESET 0x00000001UL + +/** Set MAC address mailbox message */ +#define INTELVF_MSG_TYPE_SET_MAC 0x00000002UL + +/** Set MTU mailbox message */ +#define INTELVF_MSG_TYPE_SET_MTU 0x00000005UL + +/** Control ("ping") mailbox message */ +#define INTELVF_MSG_TYPE_CONTROL 0x00000100UL + +/** Message type mask */ +#define INTELVF_MSG_TYPE_MASK 0x0000ffffUL + +/** Message NACK flag */ +#define INTELVF_MSG_NACK 0x40000000UL + +/** Message ACK flag */ +#define INTELVF_MSG_ACK 0x80000000UL + +/** Message is a response */ +#define INTELVF_MSG_RESPONSE ( INTELVF_MSG_ACK | INTELVF_MSG_NACK ) + +/** MAC address mailbox message */ +struct intelvf_msg_mac { + /** Message header */ + uint32_t hdr; + /** MAC address */ + uint8_t mac[ETH_ALEN]; + /** Alignment padding */ + uint8_t reserved[ (-ETH_ALEN) & 0x3 ]; +} __attribute__ (( packed )); + +/** Version number mailbox message */ +struct intelvf_msg_version { + /** Message header */ + uint32_t hdr; + /** API version */ + uint32_t version; +} __attribute__ (( packed )); + +/** MTU mailbox message */ +struct intelvf_msg_mtu { + /** Message header */ + uint32_t hdr; + /** Maximum packet size */ + uint32_t mtu; +} __attribute__ (( packed )); + +/** Mailbox message */ +union intelvf_msg { + /** Message header */ + uint32_t hdr; + /** MAC address message */ + struct intelvf_msg_mac mac; + /** Version number message */ + struct intelvf_msg_version version; + /** MTU message */ + struct intelvf_msg_mtu mtu; + /** Raw dwords */ + uint32_t dword[0]; +}; + +/** Maximum time to wait for mailbox message + * + * This is a policy decision. + */ +#define INTELVF_MBOX_MAX_WAIT_MS 500 + +extern int intelvf_mbox_msg ( struct intel_nic *intel, union intelvf_msg *msg ); +extern int intelvf_mbox_poll ( struct intel_nic *intel ); +extern int intelvf_mbox_wait ( struct intel_nic *intel ); +extern int intelvf_mbox_reset ( struct intel_nic *intel, uint8_t *hw_addr ); +extern int intelvf_mbox_set_mac ( struct intel_nic *intel, + const uint8_t *ll_addr ); +extern int intelvf_mbox_set_mtu ( struct intel_nic *intel, size_t mtu ); + +#endif /* _INTELVF_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/intelx.c b/qemu/roms/ipxe/src/drivers/net/intelx.c index d69900e41..982b74f12 100644 --- a/qemu/roms/ipxe/src/drivers/net/intelx.c +++ b/qemu/roms/ipxe/src/drivers/net/intelx.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> @@ -392,8 +396,10 @@ static int intelx_probe ( struct pci_device *pci ) { netdev->dev = &pci->dev; memset ( intel, 0, sizeof ( *intel ) ); intel->port = PCI_FUNC ( pci->busdevfn ); - intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTELX_TD ); - intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTELX_RD ); + intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTELX_TD, + intel_describe_tx ); + intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTELX_RD, + intel_describe_rx ); /* Fix up PCI device */ adjust_pci_device ( pci ); @@ -458,10 +464,15 @@ static void intelx_remove ( struct pci_device *pci ) { /** PCI device IDs */ static struct pci_device_id intelx_nics[] = { - PCI_ROM ( 0x8086, 0x10fb, "82599", "82599", 0 ), - PCI_ROM ( 0x8086, 0x1528, "x540at2", "X540-AT2", 0 ), - PCI_ROM ( 0x8086, 0x154d, "x520", "X520", 0 ), - PCI_ROM ( 0x8086, 0x1557, "82599", "82599", 0 ), + PCI_ROM ( 0x8086, 0x10f7, "82599-kx4", "82599 (KX/KX4)", 0 ), + PCI_ROM ( 0x8086, 0x10f8, "82599-combo-backplane", "82599 (combined backplane; KR/KX4/KX)", 0 ), + PCI_ROM ( 0x8086, 0x10f9, "82599-cx4", "82599 (CX4)", 0 ), + PCI_ROM ( 0x8086, 0x10fb, "82599-sfp", "82599 (SFI/SFP+)", 0 ), + PCI_ROM ( 0x8086, 0x10fc, "82599-xaui", "82599 (XAUI/BX4)", 0 ), + PCI_ROM ( 0x8086, 0x1528, "x540t", "X540-AT2/X540-BT2", 0 ), + PCI_ROM ( 0x8086, 0x154d, "82599-sfp-sf2", "82599 (SFI/SFP+)", 0 ), + PCI_ROM ( 0x8086, 0x1557, "82599en-sfp", "82599 (Single Port SFI Only)", 0 ), + PCI_ROM ( 0x8086, 0x1560, "x540t1", "X540-AT2/X540-BT2 (with single port NVM)", 0 ), }; /** PCI driver */ diff --git a/qemu/roms/ipxe/src/drivers/net/intelx.h b/qemu/roms/ipxe/src/drivers/net/intelx.h index 60bb294d5..6383dfcad 100644 --- a/qemu/roms/ipxe/src/drivers/net/intelx.h +++ b/qemu/roms/ipxe/src/drivers/net/intelx.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/if_ether.h> diff --git a/qemu/roms/ipxe/src/drivers/net/intelxvf.c b/qemu/roms/ipxe/src/drivers/net/intelxvf.c new file mode 100644 index 000000000..05e34c127 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/intelxvf.c @@ -0,0 +1,466 @@ +/* + * 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 <string.h> +#include <unistd.h> +#include <errno.h> +#include <ipxe/io.h> +#include <ipxe/pci.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include "intelxvf.h" + +/** @file + * + * Intel 10 Gigabit Ethernet virtual function network card driver + * + */ + +/****************************************************************************** + * + * Diagnostics + * + ****************************************************************************** + */ + +/** + * Dump statistics + * + * @v intel Intel device + */ +static __attribute__ (( unused )) void +intelxvf_stats ( struct intel_nic *intel ) { + + DBGC ( intel, "INTEL %p TX %d (%#x%08x) RX %d (%#x%08x) multi %d\n", + intel, readl ( intel->regs + INTELXVF_GPTC ), + readl ( intel->regs + INTELXVF_GOTCH ), + readl ( intel->regs + INTELXVF_GOTCL ), + readl ( intel->regs + INTELXVF_GPRC ), + readl ( intel->regs + INTELXVF_GORCH ), + readl ( intel->regs + INTELXVF_GORCL ), + readl ( intel->regs + INTELXVF_MPRC ) ); +} + +/****************************************************************************** + * + * Device reset + * + ****************************************************************************** + */ + +/** + * Reset hardware + * + * @v intel Intel device + */ +static void intelxvf_reset ( struct intel_nic *intel ) { + + /* Perform a function-level reset */ + writel ( INTELXVF_CTRL_RST, intel->regs + INTELXVF_CTRL ); +} + +/****************************************************************************** + * + * Link state + * + ****************************************************************************** + */ + +/** + * Check link state + * + * @v netdev Network device + */ +static void intelxvf_check_link ( struct net_device *netdev ) { + struct intel_nic *intel = netdev->priv; + uint32_t links; + + /* Read link status */ + links = readl ( intel->regs + INTELXVF_LINKS ); + DBGC ( intel, "INTEL %p link status is %08x\n", intel, links ); + + /* Update network device */ + if ( links & INTELXVF_LINKS_UP ) { + netdev_link_up ( netdev ); + } else { + netdev_link_down ( netdev ); + } +} + +/****************************************************************************** + * + * Mailbox messages + * + ****************************************************************************** + */ + +/** + * Send negotiate API version message + * + * @v intel Intel device + * @v version Requested version + * @ret rc Return status code + */ +static int intelxvf_mbox_version ( struct intel_nic *intel, + unsigned int version ) { + union intelvf_msg msg; + int rc; + + /* Send set MTU message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.hdr = INTELXVF_MSG_TYPE_VERSION; + msg.version.version = version; + if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) { + DBGC ( intel, "INTEL %p negotiate API version failed: %s\n", + intel, strerror ( rc ) ); + return rc; + } + + /* Check response */ + if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELXVF_MSG_TYPE_VERSION ){ + DBGC ( intel, "INTEL %p negotiate API version unexpected " + "response:\n", intel ); + DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) ); + return -EPROTO; + } + + /* Check that this version is supported */ + if ( ! ( msg.hdr & INTELVF_MSG_ACK ) ) { + DBGC ( intel, "INTEL %p negotiate API version failed\n", + intel ); + return -EPERM; + } + + return 0; +} + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxvf_open ( struct net_device *netdev ) { + struct intel_nic *intel = netdev->priv; + uint32_t srrctl; + uint32_t dca_rxctrl; + int rc; + + /* Reset the function */ + intelxvf_reset ( intel ); + + /* Notify PF that reset is complete */ + if ( ( rc = intelvf_mbox_reset ( intel, NULL ) ) != 0 ) { + DBGC ( intel, "INTEL %p could not reset: %s\n", + intel, strerror ( rc ) ); + goto err_mbox_reset; + } + + /* Negotiate API version 1.1. If we do not negotiate at least + * this version, then the RX datapath will remain disabled if + * the PF has jumbo frames enabled. + * + * Ignore failures, since the host may not actually support + * v1.1. + */ + intelxvf_mbox_version ( intel, INTELXVF_MSG_VERSION_1_1 ); + + /* Set MAC address */ + if ( ( rc = intelvf_mbox_set_mac ( intel, netdev->ll_addr ) ) != 0 ) { + DBGC ( intel, "INTEL %p could not set MAC address: %s\n", + intel, strerror ( rc ) ); + goto err_mbox_set_mac; + } + + /* Set MTU */ + if ( ( rc = intelvf_mbox_set_mtu ( intel, netdev->max_pkt_len ) ) != 0){ + DBGC ( intel, "INTEL %p could not set MTU %zd: %s\n", + intel, netdev->max_pkt_len, strerror ( rc ) ); + goto err_mbox_set_mtu; + } + + /* Create transmit descriptor ring */ + if ( ( rc = intel_create_ring ( intel, &intel->tx ) ) != 0 ) + goto err_create_tx; + + /* Create receive descriptor ring */ + if ( ( rc = intel_create_ring ( intel, &intel->rx ) ) != 0 ) + goto err_create_rx; + + /* Allocate interrupt vectors */ + writel ( ( INTELXVF_IVAR_RX0_DEFAULT | INTELXVF_IVAR_RX0_VALID | + INTELXVF_IVAR_TX0_DEFAULT | INTELXVF_IVAR_TX0_VALID ), + intel->regs + INTELXVF_IVAR ); + writel ( ( INTELXVF_IVARM_MBOX_DEFAULT | INTELXVF_IVARM_MBOX_VALID ), + intel->regs + INTELXVF_IVARM ); + + /* Configure receive buffer sizes and set receive descriptor type */ + srrctl = readl ( intel->regs + INTELXVF_SRRCTL ); + srrctl &= ~( INTELXVF_SRRCTL_BSIZE_MASK | + INTELXVF_SRRCTL_DESCTYPE_MASK ); + srrctl |= ( INTELXVF_SRRCTL_BSIZE_DEFAULT | + INTELXVF_SRRCTL_DESCTYPE_DEFAULT ); + writel ( srrctl, intel->regs + INTELXVF_SRRCTL ); + + /* Clear "must-be-zero" bit for direct cache access (DCA). We + * leave DCA disabled anyway, but if we do not clear this bit + * then the received packets contain garbage data. + */ + dca_rxctrl = readl ( intel->regs + INTELXVF_DCA_RXCTRL ); + dca_rxctrl &= ~INTELXVF_DCA_RXCTRL_MUST_BE_ZERO; + writel ( dca_rxctrl, intel->regs + INTELXVF_DCA_RXCTRL ); + + /* Fill receive ring */ + intel_refill_rx ( intel ); + + /* Update link state */ + intelxvf_check_link ( netdev ); + + return 0; + + intel_destroy_ring ( intel, &intel->rx ); + err_create_rx: + intel_destroy_ring ( intel, &intel->tx ); + err_create_tx: + err_mbox_set_mtu: + err_mbox_set_mac: + err_mbox_reset: + intelxvf_reset ( intel ); + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void intelxvf_close ( struct net_device *netdev ) { + struct intel_nic *intel = netdev->priv; + + /* Destroy receive descriptor ring */ + intel_destroy_ring ( intel, &intel->rx ); + + /* Discard any unused receive buffers */ + intel_empty_rx ( intel ); + + /* Destroy transmit descriptor ring */ + intel_destroy_ring ( intel, &intel->tx ); + + /* Reset the function */ + intelxvf_reset ( intel ); +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void intelxvf_poll ( struct net_device *netdev ) { + struct intel_nic *intel = netdev->priv; + uint32_t eicr; + int rc; + + /* Check for and acknowledge interrupts */ + eicr = readl ( intel->regs + INTELXVF_EICR ); + if ( ! eicr ) + return; + + /* Poll for TX completions, if applicable */ + if ( eicr & INTELXVF_EIRQ_TX0 ) + intel_poll_tx ( netdev ); + + /* Poll for RX completions, if applicable */ + if ( eicr & INTELXVF_EIRQ_RX0 ) + intel_poll_rx ( netdev ); + + /* Poll for mailbox messages, if applicable */ + if ( eicr & INTELXVF_EIRQ_MBOX ) { + + /* Poll mailbox */ + if ( ( rc = intelvf_mbox_poll ( intel ) ) != 0 ) { + DBGC ( intel, "INTEL %p mailbox poll failed!\n", + intel ); + netdev_rx_err ( netdev, NULL, rc ); + } + + /* Update link state */ + intelxvf_check_link ( netdev ); + } + + /* Refill RX ring */ + intel_refill_rx ( intel ); +} + +/** + * Enable or disable interrupts + * + * @v netdev Network device + * @v enable Interrupts should be enabled + */ +static void intelxvf_irq ( struct net_device *netdev, int enable ) { + struct intel_nic *intel = netdev->priv; + uint32_t mask; + + mask = ( INTELXVF_EIRQ_MBOX | INTELXVF_EIRQ_TX0 | INTELXVF_EIRQ_RX0 ); + if ( enable ) { + writel ( mask, intel->regs + INTELXVF_EIMS ); + } else { + writel ( mask, intel->regs + INTELXVF_EIMC ); + } +} + +/** Network device operations */ +static struct net_device_operations intelxvf_operations = { + .open = intelxvf_open, + .close = intelxvf_close, + .transmit = intel_transmit, + .poll = intelxvf_poll, + .irq = intelxvf_irq, +}; + +/****************************************************************************** + * + * PCI interface + * + ****************************************************************************** + */ + +/** + * Probe PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +static int intelxvf_probe ( struct pci_device *pci ) { + struct net_device *netdev; + struct intel_nic *intel; + int rc; + + /* Allocate and initialise net device */ + netdev = alloc_etherdev ( sizeof ( *intel ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &intelxvf_operations ); + intel = netdev->priv; + pci_set_drvdata ( pci, netdev ); + netdev->dev = &pci->dev; + memset ( intel, 0, sizeof ( *intel ) ); + intel_init_mbox ( &intel->mbox, INTELXVF_MBCTRL, INTELXVF_MBMEM ); + intel_init_ring ( &intel->tx, INTEL_NUM_TX_DESC, INTELXVF_TD, + intel_describe_tx_adv ); + intel_init_ring ( &intel->rx, INTEL_NUM_RX_DESC, INTELXVF_RD, + intel_describe_rx ); + + /* Fix up PCI device */ + adjust_pci_device ( pci ); + + /* Map registers */ + intel->regs = ioremap ( pci->membase, INTELVF_BAR_SIZE ); + if ( ! intel->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Reset the function */ + intelxvf_reset ( intel ); + + /* Send reset message and fetch MAC address */ + if ( ( rc = intelvf_mbox_reset ( intel, netdev->hw_addr ) ) != 0 ) { + DBGC ( intel, "INTEL %p could not reset and fetch MAC: %s\n", + intel, strerror ( rc ) ); + goto err_mbox_reset; + } + + /* Reset the function (since we will not respond to Control + * ("ping") mailbox messages until the network device is opened. + */ + intelxvf_reset ( intel ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register_netdev; + + /* Set initial link state */ + intelxvf_check_link ( netdev ); + + return 0; + + unregister_netdev ( netdev ); + err_register_netdev: + err_mbox_reset: + intelxvf_reset ( intel ); + iounmap ( intel->regs ); + err_ioremap: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void intelxvf_remove ( struct pci_device *pci ) { + struct net_device *netdev = pci_get_drvdata ( pci ); + struct intel_nic *intel = netdev->priv; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Reset the NIC */ + intelxvf_reset ( intel ); + + /* Free network device */ + iounmap ( intel->regs ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** PCI device IDs */ +static struct pci_device_id intelxvf_nics[] = { + PCI_ROM ( 0x8086, 0x10ed, "82599-vf", "82599 VF", 0 ), + PCI_ROM ( 0x8086, 0x1515, "x540-vf", "X540 VF", 0 ), + PCI_ROM ( 0x8086, 0x1565, "x550-vf", "X550 VF", 0 ), + PCI_ROM ( 0x8086, 0x15a8, "x552-vf", "X552 VF", 0 ), +}; + +/** PCI driver */ +struct pci_driver intelxvf_driver __pci_driver = { + .ids = intelxvf_nics, + .id_count = ( sizeof ( intelxvf_nics ) / sizeof ( intelxvf_nics[0] ) ), + .probe = intelxvf_probe, + .remove = intelxvf_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/intelxvf.h b/qemu/roms/ipxe/src/drivers/net/intelxvf.h new file mode 100644 index 000000000..ad046a65c --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/intelxvf.h @@ -0,0 +1,104 @@ +#ifndef _INTELXVF_H +#define _INTELXVF_H + +/** @file + * + * Intel 10 Gigabit Ethernet virtual function network card driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include "intelvf.h" + +/** Control Register */ +#define INTELXVF_CTRL 0x0000UL +#define INTELXVF_CTRL_RST 0x04000000UL /**< Function-level reset */ + +/** Link Status Register */ +#define INTELXVF_LINKS 0x0010UL +#define INTELXVF_LINKS_UP 0x40000000UL /**< Link up */ + +/** Extended Interrupt Cause Read Register */ +#define INTELXVF_EICR 0x0100UL +#define INTELXVF_EIRQ_RX0 0x00000001UL /**< RX queue 0 (via IVAR) */ +#define INTELXVF_EIRQ_TX0 0x00000002UL /**< TX queue 0 (via IVAR) */ +#define INTELXVF_EIRQ_MBOX 0x00000004UL /**< Mailbox (via IVARM) */ + +/** Extended Interrupt Mask Set/Read Register */ +#define INTELXVF_EIMS 0x0108UL + +/** Extended Interrupt Mask Clear Register */ +#define INTELXVF_EIMC 0x010cUL + +/** Interrupt Vector Allocation Register */ +#define INTELXVF_IVAR 0x0120UL +#define INTELXVF_IVAR_RX0(bit) ( (bit) << 0 ) /**< RX queue 0 allocation */ +#define INTELXVF_IVAR_RX0_DEFAULT INTELXVF_IVAR_RX0 ( 0x00 ) +#define INTELXVF_IVAR_RX0_MASK INTELXVF_IVAR_RX0 ( 0x01 ) +#define INTELXVF_IVAR_RX0_VALID 0x00000080UL /**< RX queue 0 valid */ +#define INTELXVF_IVAR_TX0(bit) ( (bit) << 8 ) /**< TX queue 0 allocation */ +#define INTELXVF_IVAR_TX0_DEFAULT INTELXVF_IVAR_TX0 ( 0x01 ) +#define INTELXVF_IVAR_TX0_MASK INTELXVF_IVAR_TX0 ( 0x01 ) +#define INTELXVF_IVAR_TX0_VALID 0x00008000UL /**< TX queue 0 valid */ + +/** Interrupt Vector Allocation Miscellaneous Register */ +#define INTELXVF_IVARM 0x0140UL +#define INTELXVF_IVARM_MBOX(bit) ( (bit) << 0 ) /**< Mailbox allocation */ +#define INTELXVF_IVARM_MBOX_DEFAULT INTELXVF_IVARM_MBOX ( 0x02 ) +#define INTELXVF_IVARM_MBOX_MASK INTELXVF_IVARM_MBOX ( 0x03 ) +#define INTELXVF_IVARM_MBOX_VALID 0x00000080UL /**< Mailbox valid */ + +/** Mailbox Memory Register Base */ +#define INTELXVF_MBMEM 0x0200UL + +/** Mailbox Control Register */ +#define INTELXVF_MBCTRL 0x02fcUL + +/** Receive Descriptor register block */ +#define INTELXVF_RD 0x1000UL + +/** RX DCA Control Register */ +#define INTELXVF_DCA_RXCTRL 0x100cUL +#define INTELXVF_DCA_RXCTRL_MUST_BE_ZERO 0x00001000UL /**< Must be zero */ + +/** Split Receive Control Register */ +#define INTELXVF_SRRCTL 0x1014UL +#define INTELXVF_SRRCTL_BSIZE(kb) ( (kb) << 0 ) /**< Receive buffer size */ +#define INTELXVF_SRRCTL_BSIZE_DEFAULT INTELXVF_SRRCTL_BSIZE ( 0x02 ) +#define INTELXVF_SRRCTL_BSIZE_MASK INTELXVF_SRRCTL_BSIZE ( 0x1f ) +#define INTELXVF_SRRCTL_DESCTYPE(typ) ( (typ) << 25 ) /**< Descriptor type */ +#define INTELXVF_SRRCTL_DESCTYPE_DEFAULT INTELXVF_SRRCTL_DESCTYPE ( 0x00 ) +#define INTELXVF_SRRCTL_DESCTYPE_MASK INTELXVF_SRRCTL_DESCTYPE ( 0x07 ) + +/** Good Packets Received Count */ +#define INTELXVF_GPRC 0x101c + +/** Good Packets Received Count Low */ +#define INTELXVF_GORCL 0x1020 + +/** Good Packets Received Count High */ +#define INTELXVF_GORCH 0x1024 + +/* Multicast Packets Received Count */ +#define INTELXVF_MPRC 0x1034 + +/** Transmit Descriptor register block */ +#define INTELXVF_TD 0x2000UL + +/** Good Packets Transmitted Count */ +#define INTELXVF_GPTC 0x201c + +/** Good Packets Transmitted Count Low */ +#define INTELXVF_GOTCL 0x2020 + +/** Good Packets Transmitted Count High */ +#define INTELXVF_GOTCH 0x2024 + +/** Negotiate API version mailbox message */ +#define INTELXVF_MSG_TYPE_VERSION 0x00000008UL + +/** API version 1.1 */ +#define INTELXVF_MSG_VERSION_1_1 0x00000002UL + +#endif /* _INTELXVF_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/ipoib.c b/qemu/roms/ipxe/src/drivers/net/ipoib.c index 1b5391776..6552d764e 100644 --- a/qemu/roms/ipxe/src/drivers/net/ipoib.c +++ b/qemu/roms/ipxe/src/drivers/net/ipoib.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -29,8 +33,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/errortab.h> #include <ipxe/malloc.h> #include <ipxe/if_arp.h> +#include <ipxe/arp.h> #include <ipxe/if_ether.h> #include <ipxe/ethernet.h> +#include <ipxe/ip.h> #include <ipxe/iobuf.h> #include <ipxe/netdevice.h> #include <ipxe/infiniband.h> @@ -44,6 +50,20 @@ FILE_LICENCE ( GPL2_OR_LATER ); * IP over Infiniband */ +/* Disambiguate the various error causes */ +#define ENXIO_ARP_REPLY __einfo_error ( EINFO_ENXIO_ARP_REPLY ) +#define EINFO_ENXIO_ARP_REPLY \ + __einfo_uniqify ( EINFO_ENXIO, 0x01, \ + "Missing REMAC for ARP reply target address" ) +#define ENXIO_NON_IPV4 __einfo_error ( EINFO_ENXIO_NON_IPV4 ) +#define EINFO_ENXIO_NON_IPV4 \ + __einfo_uniqify ( EINFO_ENXIO, 0x02, \ + "Missing REMAC for non-IPv4 packet" ) +#define ENXIO_ARP_SENT __einfo_error ( EINFO_ENXIO_ARP_SENT ) +#define EINFO_ENXIO_ARP_SENT \ + __einfo_uniqify ( EINFO_ENXIO, 0x03, \ + "Missing REMAC for IPv4 packet (ARP sent)" ) + /** Number of IPoIB send work queue entries */ #define IPOIB_NUM_SEND_WQES 2 @@ -96,6 +116,8 @@ struct errortab ipoib_errors[] __errortab = { __einfo_errortab ( EINFO_EINPROGRESS_JOINING ), }; +static struct net_device_operations ipoib_operations; + /**************************************************************************** * * IPoIB REMAC cache @@ -124,8 +146,10 @@ static struct ipoib_mac * ipoib_find_remac ( struct ipoib_device *ipoib, const struct ipoib_remac *remac ) { struct ipoib_peer *peer; - /* Check for broadcast REMAC */ - if ( is_broadcast_ether_addr ( remac ) ) + /* Check for broadcast or multicast REMAC. We transmit + * multicasts as broadcasts for simplicity. + */ + if ( is_multicast_ether_addr ( remac ) ) return &ipoib->broadcast; /* Try to find via REMAC cache */ @@ -202,14 +226,20 @@ static void ipoib_flush_remac ( struct ipoib_device *ipoib ) { * @ret discarded Number of cached items discarded */ static unsigned int ipoib_discard_remac ( void ) { - struct ib_device *ibdev; + struct net_device *netdev; struct ipoib_device *ipoib; struct ipoib_peer *peer; unsigned int discarded = 0; /* Try to discard one cache entry for each IPoIB device */ - for_each_ibdev ( ibdev ) { - ipoib = ib_get_ownerdata ( ibdev ); + for_each_netdev ( netdev ) { + + /* Skip non-IPoIB devices */ + if ( netdev->op != &ipoib_operations ) + continue; + ipoib = netdev->priv; + + /* Discard least recently used cache entry (if any) */ list_for_each_entry_reverse ( peer, &ipoib->peers, list ) { list_del ( &peer->list ); free ( peer ); @@ -222,7 +252,7 @@ static unsigned int ipoib_discard_remac ( void ) { } /** IPoIB cache discarder */ -struct cache_discarder ipoib_discarder __cache_discarder ( CACHE_NORMAL ) = { +struct cache_discarder ipoib_discarder __cache_discarder ( CACHE_EXPENSIVE ) = { .discard = ipoib_discard_remac, }; @@ -324,8 +354,11 @@ static int ipoib_translate_tx_arp ( struct net_device *netdev, /* Look up REMAC, if applicable */ if ( arphdr->ar_op == ARPOP_REPLY ) { target_ha = ipoib_find_remac ( ipoib, arp_target_pa ( arphdr )); - if ( ! target_ha ) - return -ENXIO; + if ( ! target_ha ) { + DBGC ( ipoib, "IPoIB %p no REMAC for %s ARP reply\n", + ipoib, eth_ntoa ( arp_target_pa ( arphdr ) ) ); + return -ENXIO_ARP_REPLY; + } } /* Construct new packet */ @@ -461,6 +494,7 @@ static int ipoib_transmit ( struct net_device *netdev, struct ipoib_device *ipoib = netdev->priv; struct ib_device *ibdev = ipoib->ibdev; struct ethhdr *ethhdr; + struct iphdr *iphdr; struct ipoib_hdr *ipoib_hdr; struct ipoib_mac *mac; struct ib_address_vector dest; @@ -485,9 +519,34 @@ static int ipoib_transmit ( struct net_device *netdev, iob_pull ( iobuf, sizeof ( *ethhdr ) ); /* Identify destination address */ - mac = ipoib_find_remac ( ipoib, ( ( void *) ethhdr->h_dest ) ); - if ( ! mac ) - return -ENXIO; + mac = ipoib_find_remac ( ipoib, ( ( void * ) ethhdr->h_dest ) ); + if ( ! mac ) { + /* Generate a new ARP request (if possible) to trigger + * population of the REMAC cache entry. + */ + if ( ( net_proto != htons ( ETH_P_IP ) ) || + ( iob_len ( iobuf ) < sizeof ( *iphdr ) ) ) { + DBGC ( ipoib, "IPoIB %p no REMAC for %s non-IPv4 " + "packet type %04x\n", ipoib, + eth_ntoa ( ethhdr->h_dest ), + ntohs ( net_proto ) ); + return -ENXIO_NON_IPV4; + } + iphdr = iobuf->data; + if ( ( rc = arp_tx_request ( netdev, &ipv4_protocol, + &iphdr->dest, &iphdr->src ) ) !=0){ + DBGC ( ipoib, "IPoIB %p could not ARP for %s/%s/", + ipoib, eth_ntoa ( ethhdr->h_dest ), + inet_ntoa ( iphdr->dest ) ); + DBGC ( ipoib, "%s: %s\n", inet_ntoa ( iphdr->src ), + strerror ( rc ) ); + return rc; + } + DBGC ( ipoib, "IPoIB %p no REMAC for %s/%s/", ipoib, + eth_ntoa ( ethhdr->h_dest ), inet_ntoa ( iphdr->dest ) ); + DBGC ( ipoib, "%s\n", inet_ntoa ( iphdr->src ) ); + return -ENXIO_ARP_SENT; + } /* Translate packet if applicable */ if ( ( rc = ipoib_translate_tx ( netdev, iobuf, net_proto ) ) != 0 ) @@ -732,7 +791,8 @@ static void ipoib_link_state_changed ( struct ib_device *ibdev ) { int rc; /* Leave existing broadcast group */ - ipoib_leave_broadcast_group ( ipoib ); + if ( ipoib->qp ) + ipoib_leave_broadcast_group ( ipoib ); /* Update MAC address based on potentially-new GID prefix */ memcpy ( &ipoib->mac.gid.s.prefix, &ibdev->gid.s.prefix, @@ -747,7 +807,7 @@ static void ipoib_link_state_changed ( struct ib_device *ibdev ) { netdev_link_err ( netdev, ( rc ? rc : -EINPROGRESS_JOINING ) ); /* Join new broadcast group */ - if ( ib_is_open ( ibdev ) && ib_link_ok ( ibdev ) && + if ( ib_is_open ( ibdev ) && ib_link_ok ( ibdev ) && ipoib->qp && ( ( rc = ipoib_join_broadcast_group ( ipoib ) ) != 0 ) ) { DBGC ( ipoib, "IPoIB %p could not rejoin broadcast group: " "%s\n", ipoib, strerror ( rc ) ); @@ -835,7 +895,9 @@ static void ipoib_close ( struct net_device *netdev ) { /* Tear down the queues */ ib_destroy_qp ( ibdev, ipoib->qp ); + ipoib->qp = NULL; ib_destroy_cq ( ibdev, ipoib->cq ); + ipoib->cq = NULL; /* Close IB device */ ib_close ( ibdev ); diff --git a/qemu/roms/ipxe/src/drivers/net/legacy.c b/qemu/roms/ipxe/src/drivers/net/legacy.c index 4edbef162..73a80194f 100644 --- a/qemu/roms/ipxe/src/drivers/net/legacy.c +++ b/qemu/roms/ipxe/src/drivers/net/legacy.c @@ -17,7 +17,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct nic nic; diff --git a/qemu/roms/ipxe/src/drivers/net/mii.c b/qemu/roms/ipxe/src/drivers/net/mii.c index c4d32514d..9b297029a 100644 --- a/qemu/roms/ipxe/src/drivers/net/mii.c +++ b/qemu/roms/ipxe/src/drivers/net/mii.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <unistd.h> @@ -111,3 +115,35 @@ int mii_reset ( struct mii_interface *mii ) { DBGC ( mii, "MII %p timed out waiting for reset\n", mii ); return -ETIMEDOUT; } + +/** + * Update link status via MII + * + * @v mii MII interface + * @v netdev Network device + * @ret rc Return status code + */ +int mii_check_link ( struct mii_interface *mii, struct net_device *netdev ) { + int bmsr; + int link; + int rc; + + /* Read BMSR */ + bmsr = mii_read ( mii, MII_BMSR ); + if ( bmsr < 0 ) { + rc = bmsr; + return rc; + } + + /* Report link status */ + link = ( bmsr & BMSR_LSTATUS ); + DBGC ( mii, "MII %p link %s (BMSR %#04x)\n", + mii, ( link ? "up" : "down" ), bmsr ); + if ( link ) { + netdev_link_up ( netdev ); + } else { + netdev_link_down ( netdev ); + } + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/net/myson.c b/qemu/roms/ipxe/src/drivers/net/myson.c index 6abb55660..84a550596 100644 --- a/qemu/roms/ipxe/src/drivers/net/myson.c +++ b/qemu/roms/ipxe/src/drivers/net/myson.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/drivers/net/myson.h b/qemu/roms/ipxe/src/drivers/net/myson.h index 8d7cc5855..05a6b8a58 100644 --- a/qemu/roms/ipxe/src/drivers/net/myson.h +++ b/qemu/roms/ipxe/src/drivers/net/myson.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/if_ether.h> diff --git a/qemu/roms/ipxe/src/drivers/net/ncm.c b/qemu/roms/ipxe/src/drivers/net/ncm.c new file mode 100644 index 000000000..10728d2a1 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/ncm.c @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2014 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 <string.h> +#include <errno.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/if_ether.h> +#include <ipxe/profile.h> +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include "ecm.h" +#include "ncm.h" + +/** @file + * + * CDC-NCM USB Ethernet driver + * + */ + +/** Interrupt completion profiler */ +static struct profiler ncm_intr_profiler __profiler = + { .name = "ncm.intr" }; + +/** Bulk IN completion profiler */ +static struct profiler ncm_in_profiler __profiler = + { .name = "ncm.in" }; + +/** Bulk IN per-datagram profiler */ +static struct profiler ncm_in_datagram_profiler __profiler = + { .name = "ncm.in_dgram" }; + +/** Bulk OUT profiler */ +static struct profiler ncm_out_profiler __profiler = + { .name = "ncm.out" }; + +/****************************************************************************** + * + * CDC-NCM communications interface + * + ****************************************************************************** + */ + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ncm_intr_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct ncm_device *ncm = container_of ( ep, struct ncm_device, + usbnet.intr ); + struct net_device *netdev = ncm->netdev; + struct usb_setup_packet *message; + size_t len = iob_len ( iobuf ); + + /* Profile completions */ + profile_start ( &ncm_intr_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto ignore; + + /* Ignore packets with errors */ + if ( rc != 0 ) { + DBGC ( ncm, "NCM %p interrupt failed: %s\n", + ncm, strerror ( rc ) ); + DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); + goto error; + } + + /* Extract message header */ + if ( len < sizeof ( *message ) ) { + DBGC ( ncm, "NCM %p underlength interrupt:\n", ncm ); + DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto error; + } + message = iobuf->data; + + /* Parse message header */ + switch ( message->request ) { + + case cpu_to_le16 ( CDC_NETWORK_CONNECTION ) : + if ( message->value ) { + DBGC ( ncm, "NCM %p link up\n", ncm ); + netdev_link_up ( netdev ); + } else { + DBGC ( ncm, "NCM %p link down\n", ncm ); + netdev_link_down ( netdev ); + } + break; + + case cpu_to_le16 ( CDC_CONNECTION_SPEED_CHANGE ) : + /* Ignore */ + break; + + default: + DBGC ( ncm, "NCM %p unrecognised interrupt:\n", ncm ); + DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); + goto error; + } + + /* Free I/O buffer */ + free_iob ( iobuf ); + profile_stop ( &ncm_intr_profiler ); + + return; + + error: + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); + ignore: + free_iob ( iobuf ); + return; +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations ncm_intr_operations = { + .complete = ncm_intr_complete, +}; + +/****************************************************************************** + * + * CDC-NCM data interface + * + ****************************************************************************** + */ + +/** + * Prefill bulk IN endpoint + * + * @v ncm CDC-NCM device + * @ret rc Return status code + */ +static int ncm_in_prefill ( struct ncm_device *ncm ) { + struct usb_bus *bus = ncm->bus; + size_t mtu; + unsigned int count; + int rc; + + /* Some devices have a very small number of internal buffers, + * and rely on being able to pack multiple packets into each + * buffer. We therefore want to use large buffers if + * possible. However, large allocations have a reasonable + * chance of failure, especially if this is not the first or + * only device to be opened. + * + * We therefore attempt to find a usable buffer size, starting + * large and working downwards until allocation succeeds. + * Smaller buffers will still work, albeit with a higher + * chance of packet loss and so lower overall throughput. + */ + for ( mtu = ncm->mtu ; mtu >= NCM_MIN_NTB_INPUT_SIZE ; mtu >>= 1 ) { + + /* Attempt allocation at this MTU */ + if ( mtu > NCM_MAX_NTB_INPUT_SIZE ) + continue; + if ( mtu > bus->mtu ) + continue; + count = ( NCM_IN_MIN_SIZE / mtu ); + if ( count < NCM_IN_MIN_COUNT ) + count = NCM_IN_MIN_COUNT; + if ( ( count * mtu ) > NCM_IN_MAX_SIZE ) + continue; + usb_refill_init ( &ncm->usbnet.in, mtu, count ); + if ( ( rc = usb_prefill ( &ncm->usbnet.in ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not prefill %dx %zd-byte " + "buffers for bulk IN\n", ncm, count, mtu ); + continue; + } + + DBGC ( ncm, "NCM %p using %dx %zd-byte buffers for bulk IN\n", + ncm, count, mtu ); + return 0; + } + + DBGC ( ncm, "NCM %p could not prefill bulk IN endpoint\n", ncm ); + return -ENOMEM; +} + +/** + * Complete bulk IN transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int rc ) { + struct ncm_device *ncm = container_of ( ep, struct ncm_device, + usbnet.in ); + struct net_device *netdev = ncm->netdev; + struct ncm_transfer_header *nth; + struct ncm_datagram_pointer *ndp; + struct ncm_datagram_descriptor *desc; + struct io_buffer *pkt; + unsigned int remaining; + size_t ndp_offset; + size_t ndp_len; + size_t pkt_offset; + size_t pkt_len; + size_t headroom; + size_t len; + + /* Profile overall bulk IN completion */ + profile_start ( &ncm_in_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto ignore; + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( ncm, "NCM %p bulk IN failed: %s\n", + ncm, strerror ( rc ) ); + goto error; + } + + /* Locate transfer header */ + len = iob_len ( iobuf ); + if ( sizeof ( *nth ) > len ) { + DBGC ( ncm, "NCM %p packet too short for NTH:\n", ncm ); + rc = -EINVAL; + goto error; + } + nth = iobuf->data; + + /* Locate datagram pointer */ + ndp_offset = le16_to_cpu ( nth->offset ); + if ( ( ndp_offset + sizeof ( *ndp ) ) > len ) { + DBGC ( ncm, "NCM %p packet too short for NDP:\n", ncm ); + rc = -EINVAL; + goto error; + } + ndp = ( iobuf->data + ndp_offset ); + ndp_len = le16_to_cpu ( ndp->header_len ); + if ( ndp_len < offsetof ( typeof ( *ndp ), desc ) ) { + DBGC ( ncm, "NCM %p NDP header length too short:\n", ncm ); + rc = -EINVAL; + goto error; + } + if ( ( ndp_offset + ndp_len ) > len ) { + DBGC ( ncm, "NCM %p packet too short for NDP:\n", ncm ); + rc = -EINVAL; + goto error; + } + + /* Process datagrams */ + remaining = ( ( ndp_len - offsetof ( typeof ( *ndp ), desc ) ) / + sizeof ( ndp->desc[0] ) ); + for ( desc = ndp->desc ; remaining && desc->offset ; remaining-- ) { + + /* Profile individual datagrams */ + profile_start ( &ncm_in_datagram_profiler ); + + /* Locate datagram */ + pkt_offset = le16_to_cpu ( desc->offset ); + pkt_len = le16_to_cpu ( desc->len ); + if ( pkt_len < ETH_HLEN ) { + DBGC ( ncm, "NCM %p underlength datagram:\n", ncm ); + rc = -EINVAL; + goto error; + } + if ( ( pkt_offset + pkt_len ) > len ) { + DBGC ( ncm, "NCM %p datagram exceeds packet:\n", ncm ); + rc = -EINVAL; + goto error; + } + + /* Move to next descriptor */ + desc++; + + /* Copy data to a new I/O buffer. Our USB buffers may + * be very large and so we choose to recycle the + * buffers directly rather than attempt reallocation + * while the device is running. We therefore copy the + * data to a new I/O buffer even if this is the only + * (or last) packet within the buffer. + * + * We reserve enough space at the start of each buffer + * to allow for our own transmission header, to + * support protocols such as ARP which may modify the + * received packet and reuse the same I/O buffer for + * transmission. + */ + headroom = ( sizeof ( struct ncm_ntb_header ) + ncm->padding ); + pkt = alloc_iob ( headroom + pkt_len ); + if ( ! pkt ) { + /* Record error and continue */ + netdev_rx_err ( netdev, NULL, -ENOMEM ); + continue; + } + iob_reserve ( pkt, headroom ); + memcpy ( iob_put ( pkt, pkt_len ), + ( iobuf->data + pkt_offset ), pkt_len ); + + /* Strip CRC, if present */ + if ( ndp->magic & cpu_to_le32 ( NCM_DATAGRAM_POINTER_MAGIC_CRC)) + iob_unput ( pkt, 4 /* CRC32 */ ); + + /* Hand off to network stack */ + netdev_rx ( netdev, pkt ); + profile_stop ( &ncm_in_datagram_profiler ); + } + + /* Recycle I/O buffer */ + usb_recycle ( &ncm->usbnet.in, iobuf ); + profile_stop ( &ncm_in_profiler ); + + return; + + error: + /* Record error against network device */ + DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); + netdev_rx_err ( netdev, NULL, rc ); + ignore: + usb_recycle ( &ncm->usbnet.in, iobuf ); +} + +/** Bulk IN endpoint operations */ +static struct usb_endpoint_driver_operations ncm_in_operations = { + .complete = ncm_in_complete, +}; + +/** + * Transmit packet + * + * @v ncm CDC-NCM device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ncm_out_transmit ( struct ncm_device *ncm, + struct io_buffer *iobuf ) { + struct ncm_ntb_header *header; + size_t len = iob_len ( iobuf ); + size_t header_len = ( sizeof ( *header ) + ncm->padding ); + int rc; + + /* Profile transmissions */ + profile_start ( &ncm_out_profiler ); + + /* Prepend header */ + if ( ( rc = iob_ensure_headroom ( iobuf, header_len ) ) != 0 ) + return rc; + header = iob_push ( iobuf, header_len ); + + /* Populate header */ + header->nth.magic = cpu_to_le32 ( NCM_TRANSFER_HEADER_MAGIC ); + header->nth.header_len = cpu_to_le16 ( sizeof ( header->nth ) ); + header->nth.sequence = cpu_to_le16 ( ncm->sequence ); + header->nth.len = cpu_to_le16 ( iob_len ( iobuf ) ); + header->nth.offset = + cpu_to_le16 ( offsetof ( typeof ( *header ), ndp ) ); + header->ndp.magic = cpu_to_le32 ( NCM_DATAGRAM_POINTER_MAGIC ); + header->ndp.header_len = cpu_to_le16 ( sizeof ( header->ndp ) + + sizeof ( header->desc ) ); + header->ndp.offset = cpu_to_le16 ( 0 ); + header->desc[0].offset = cpu_to_le16 ( header_len ); + header->desc[0].len = cpu_to_le16 ( len ); + memset ( &header->desc[1], 0, sizeof ( header->desc[1] ) ); + + /* Enqueue I/O buffer */ + if ( ( rc = usb_stream ( &ncm->usbnet.out, iobuf, 0 ) ) != 0 ) + return rc; + + /* Increment sequence number */ + ncm->sequence++; + + profile_stop ( &ncm_out_profiler ); + return 0; +} + +/** + * Complete bulk OUT transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void ncm_out_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, + int rc ) { + struct ncm_device *ncm = container_of ( ep, struct ncm_device, + usbnet.out ); + struct net_device *netdev = ncm->netdev; + + /* Report TX completion */ + netdev_tx_complete_err ( netdev, iobuf, rc ); +} + +/** Bulk OUT endpoint operations */ +static struct usb_endpoint_driver_operations ncm_out_operations = { + .complete = ncm_out_complete, +}; + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int ncm_open ( struct net_device *netdev ) { + struct ncm_device *ncm = netdev->priv; + struct usb_device *usb = ncm->usb; + struct ncm_set_ntb_input_size size; + int rc; + + /* Reset sequence number */ + ncm->sequence = 0; + + /* Prefill I/O buffers */ + if ( ( rc = ncm_in_prefill ( ncm ) ) != 0 ) + goto err_prefill; + + /* Set maximum input size */ + memset ( &size, 0, sizeof ( size ) ); + size.mtu = cpu_to_le32 ( ncm->usbnet.in.len ); + if ( ( rc = usb_control ( usb, NCM_SET_NTB_INPUT_SIZE, 0, + ncm->usbnet.comms, &size, + sizeof ( size ) ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not set input size to %zd: %s\n", + ncm, ncm->usbnet.in.len, strerror ( rc ) ); + goto err_set_ntb_input_size; + } + + /* Open USB network device */ + if ( ( rc = usbnet_open ( &ncm->usbnet ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not open: %s\n", + ncm, strerror ( rc ) ); + goto err_open; + } + + return 0; + + usbnet_close ( &ncm->usbnet ); + err_open: + err_set_ntb_input_size: + usb_flush ( &ncm->usbnet.in ); + err_prefill: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void ncm_close ( struct net_device *netdev ) { + struct ncm_device *ncm = netdev->priv; + + /* Close USB network device */ + usbnet_close ( &ncm->usbnet ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ncm_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct ncm_device *ncm = netdev->priv; + int rc; + + /* Transmit packet */ + if ( ( rc = ncm_out_transmit ( ncm, iobuf ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void ncm_poll ( struct net_device *netdev ) { + struct ncm_device *ncm = netdev->priv; + int rc; + + /* Poll USB bus */ + usb_poll ( ncm->bus ); + + /* Refill endpoints */ + if ( ( rc = usbnet_refill ( &ncm->usbnet ) ) != 0 ) + netdev_rx_err ( netdev, NULL, rc ); + +} + +/** CDC-NCM network device operations */ +static struct net_device_operations ncm_operations = { + .open = ncm_open, + .close = ncm_close, + .transmit = ncm_transmit, + .poll = ncm_poll, +}; + +/****************************************************************************** + * + * USB interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int ncm_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct net_device *netdev; + struct ncm_device *ncm; + struct usb_interface_descriptor *comms; + struct ecm_ethernet_descriptor *ethernet; + struct ncm_ntb_parameters params; + int rc; + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *ncm ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &ncm_operations ); + netdev->dev = &func->dev; + ncm = netdev->priv; + memset ( ncm, 0, sizeof ( *ncm ) ); + ncm->usb = usb; + ncm->bus = usb->port->hub->bus; + ncm->netdev = netdev; + usbnet_init ( &ncm->usbnet, func, &ncm_intr_operations, + &ncm_in_operations, &ncm_out_operations ); + usb_refill_init ( &ncm->usbnet.intr, 0, NCM_INTR_COUNT ); + DBGC ( ncm, "NCM %p on %s\n", ncm, func->name ); + + /* Describe USB network device */ + if ( ( rc = usbnet_describe ( &ncm->usbnet, config ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not describe: %s\n", + ncm, strerror ( rc ) ); + goto err_describe; + } + + /* Locate Ethernet descriptor */ + comms = usb_interface_descriptor ( config, ncm->usbnet.comms, 0 ); + assert ( comms != NULL ); + ethernet = ecm_ethernet_descriptor ( config, comms ); + if ( ! ethernet ) { + DBGC ( ncm, "NCM %p has no Ethernet descriptor\n", ncm ); + rc = -EINVAL; + goto err_ethernet; + } + + /* Fetch MAC address */ + if ( ( rc = ecm_fetch_mac ( usb, ethernet, netdev->hw_addr ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not fetch MAC address: %s\n", + ncm, strerror ( rc ) ); + goto err_fetch_mac; + } + + /* Get NTB parameters */ + if ( ( rc = usb_control ( usb, NCM_GET_NTB_PARAMETERS, 0, + ncm->usbnet.comms, ¶ms, + sizeof ( params ) ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not get NTB parameters: %s\n", + ncm, strerror ( rc ) ); + goto err_ntb_parameters; + } + + /* Get maximum supported input size */ + ncm->mtu = le32_to_cpu ( params.in.mtu ); + DBGC2 ( ncm, "NCM %p maximum IN size is %zd bytes\n", ncm, ncm->mtu ); + + /* Calculate transmit padding */ + ncm->padding = ( ( le16_to_cpu ( params.out.remainder ) - + sizeof ( struct ncm_ntb_header ) - ETH_HLEN ) & + ( le16_to_cpu ( params.out.divisor ) - 1 ) ); + DBGC2 ( ncm, "NCM %p using %zd-byte transmit padding\n", + ncm, ncm->padding ); + assert ( ( ( sizeof ( struct ncm_ntb_header ) + ncm->padding + + ETH_HLEN ) % le16_to_cpu ( params.out.divisor ) ) == + le16_to_cpu ( params.out.remainder ) ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + usb_func_set_drvdata ( func, ncm ); + return 0; + + unregister_netdev ( netdev ); + err_register: + err_ntb_parameters: + err_fetch_mac: + err_ethernet: + err_describe: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void ncm_remove ( struct usb_function *func ) { + struct ncm_device *ncm = usb_func_get_drvdata ( func ); + struct net_device *netdev = ncm->netdev; + + unregister_netdev ( netdev ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** CDC-NCM device IDs */ +static struct usb_device_id ncm_ids[] = { + { + .name = "cdc-ncm", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + .class = { + .class = USB_CLASS_CDC, + .subclass = USB_SUBCLASS_CDC_NCM, + .protocol = 0, + }, + }, +}; + +/** CDC-NCM driver */ +struct usb_driver ncm_driver __usb_driver = { + .ids = ncm_ids, + .id_count = ( sizeof ( ncm_ids ) / sizeof ( ncm_ids[0] ) ), + .probe = ncm_probe, + .remove = ncm_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/ncm.h b/qemu/roms/ipxe/src/drivers/net/ncm.h new file mode 100644 index 000000000..a9565a56b --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/ncm.h @@ -0,0 +1,173 @@ +#ifndef _NCM_H +#define _NCM_H + +/** @file + * + * CDC-NCM USB Ethernet driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> +#include <ipxe/usb.h> +#include <ipxe/cdc.h> +#include <byteswap.h> +#include "ecm.h" + +/** CDC-NCM subclass */ +#define USB_SUBCLASS_CDC_NCM 0x0d + +/** Get NTB parameters */ +#define NCM_GET_NTB_PARAMETERS \ + ( USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE | \ + USB_REQUEST_TYPE ( 0x80 ) ) + +/** NTB datagram parameters */ +struct ncm_ntb_datagram_parameters { + /** Maximum size */ + uint32_t mtu; + /** Alignment divisor */ + uint16_t divisor; + /** Alignment remainder */ + uint16_t remainder; + /** Alignment modulus */ + uint16_t modulus; +} __attribute__ (( packed )); + +/** NTB parameters */ +struct ncm_ntb_parameters { + /** Length */ + uint16_t len; + /** Supported formats */ + uint16_t formats; + /** IN datagram parameters */ + struct ncm_ntb_datagram_parameters in; + /** Reserved */ + uint16_t reserved; + /** OUT datagram parameters */ + struct ncm_ntb_datagram_parameters out; + /** Maximum number of datagrams per OUT NTB */ + uint16_t max; +} __attribute__ (( packed )); + +/** Set NTB input size */ +#define NCM_SET_NTB_INPUT_SIZE \ + ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE | \ + USB_REQUEST_TYPE ( 0x86 ) ) + +/** Set NTB input size */ +struct ncm_set_ntb_input_size { + /** Maximum size */ + uint32_t mtu; +} __attribute__ (( packed )); + +/** Minimum allowed NTB input size */ +#define NCM_MIN_NTB_INPUT_SIZE 2048 + +/** Maximum allowed NTB input size (16-bit) */ +#define NCM_MAX_NTB_INPUT_SIZE 65536 + +/** CDC-NCM transfer header (16-bit) */ +struct ncm_transfer_header { + /** Signature */ + uint32_t magic; + /** Header length */ + uint16_t header_len; + /** Sequence number */ + uint16_t sequence; + /** Total length */ + uint16_t len; + /** Offset of first datagram pointer */ + uint16_t offset; +} __attribute__ (( packed )); + +/** CDC-NCM transfer header magic */ +#define NCM_TRANSFER_HEADER_MAGIC 0x484d434eUL + +/** CDC-NCM datagram descriptor (16-bit) */ +struct ncm_datagram_descriptor { + /** Starting offset */ + uint16_t offset; + /** Length */ + uint16_t len; +} __attribute__ (( packed )); + +/** CDC-NCM datagram pointer (16-bit) */ +struct ncm_datagram_pointer { + /** Signature */ + uint32_t magic; + /** Header length */ + uint16_t header_len; + /** Offset of next datagram pointer */ + uint16_t offset; + /** Datagram descriptors + * + * Must be terminated by an empty descriptor. + */ + struct ncm_datagram_descriptor desc[0]; +} __attribute__ (( packed )); + +/** CDC-NCM datagram pointer magic */ +#define NCM_DATAGRAM_POINTER_MAGIC 0x304d434eUL + +/** CDC-NCM datagram pointer CRC present flag */ +#define NCM_DATAGRAM_POINTER_MAGIC_CRC 0x01000000UL + +/** NTB constructed for transmitted packets (excluding padding) + * + * This is a policy decision. + */ +struct ncm_ntb_header { + /** Transfer header */ + struct ncm_transfer_header nth; + /** Datagram pointer */ + struct ncm_datagram_pointer ndp; + /** Datagram descriptors */ + struct ncm_datagram_descriptor desc[2]; +} __attribute__ (( packed )); + +/** A CDC-NCM network device */ +struct ncm_device { + /** USB device */ + struct usb_device *usb; + /** USB bus */ + struct usb_bus *bus; + /** Network device */ + struct net_device *netdev; + /** USB network device */ + struct usbnet_device usbnet; + + /** Maximum supported NTB input size */ + size_t mtu; + /** Transmitted packet sequence number */ + uint16_t sequence; + /** Alignment padding required on transmitted packets */ + size_t padding; +}; + +/** Bulk IN ring minimum buffer count + * + * This is a policy decision. + */ +#define NCM_IN_MIN_COUNT 3 + +/** Bulk IN ring minimum total buffer size + * + * This is a policy decision. + */ +#define NCM_IN_MIN_SIZE 16384 + +/** Bulk IN ring maximum total buffer size + * + * This is a policy decision. + */ +#define NCM_IN_MAX_SIZE 131072 + +/** Interrupt ring buffer count + * + * This is a policy decision. + */ +#define NCM_INTR_COUNT 2 + +#endif /* _NCM_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/netfront.c b/qemu/roms/ipxe/src/drivers/net/netfront.c index 4b816329e..2f4bbf2a0 100644 --- a/qemu/roms/ipxe/src/drivers/net/netfront.c +++ b/qemu/roms/ipxe/src/drivers/net/netfront.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -135,7 +139,7 @@ static int netfront_read_mac ( struct netfront_nic *netfront, void *hw_addr ) { xendev->key, mac ); /* Decode MAC address */ - len = hex_decode ( mac, ':', hw_addr, ETH_ALEN ); + len = hex_decode ( ':', mac, hw_addr, ETH_ALEN ); if ( len < 0 ) { rc = len; DBGC ( netfront, "NETFRONT %s could not decode MAC address " @@ -593,6 +597,11 @@ static int netfront_open ( struct net_device *netdev ) { "feature-no-csum-offload" ) ) != 0 ) goto err_feature_no_csum_offload; + /* Inform backend that we will send notifications for RX requests */ + if ( ( rc = netfront_write_flag ( netfront, + "feature-rx-notify" ) ) != 0 ) + goto err_feature_rx_notify; + /* Set state to Connected */ if ( ( rc = xenbus_set_state ( xendev, XenbusStateConnected ) ) != 0 ) { DBGC ( netfront, "NETFRONT %s could not set state=\"%d\": %s\n", @@ -618,6 +627,8 @@ static int netfront_open ( struct net_device *netdev ) { err_backend_wait: netfront_reset ( netfront ); err_set_state: + netfront_rm ( netfront, "feature-rx-notify" ); + err_feature_rx_notify: netfront_rm ( netfront, "feature-no-csum-offload" ); err_feature_no_csum_offload: netfront_rm ( netfront, "request-rx-copy" ); @@ -661,6 +672,7 @@ static void netfront_close ( struct net_device *netdev ) { } /* Delete flags */ + netfront_rm ( netfront, "feature-rx-notify" ); netfront_rm ( netfront, "feature-no-csum-offload" ); netfront_rm ( netfront, "request-rx-copy" ); diff --git a/qemu/roms/ipxe/src/drivers/net/netfront.h b/qemu/roms/ipxe/src/drivers/net/netfront.h index b3f899f3c..38fd0a77e 100644 --- a/qemu/roms/ipxe/src/drivers/net/netfront.h +++ b/qemu/roms/ipxe/src/drivers/net/netfront.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/xen.h> #include <xen/io/netif.h> diff --git a/qemu/roms/ipxe/src/drivers/net/netvsc.c b/qemu/roms/ipxe/src/drivers/net/netvsc.c new file mode 100644 index 000000000..d269cd63e --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/netvsc.c @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2014 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 ); + +/** @file + * + * Hyper-V network virtual service client + * + * The network virtual service client (NetVSC) connects to the network + * virtual service provider (NetVSP) via the Hyper-V virtual machine + * bus (VMBus). It provides a transport layer for RNDIS packets. + */ + +#include <errno.h> +#include <unistd.h> +#include <byteswap.h> +#include <ipxe/umalloc.h> +#include <ipxe/rndis.h> +#include <ipxe/vmbus.h> +#include "netvsc.h" + +/** + * Send control message and wait for completion + * + * @v netvsc NetVSC device + * @v xrid Relative transaction ID + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int netvsc_control ( struct netvsc_device *netvsc, unsigned int xrid, + const void *data, size_t len ) { + uint64_t xid = ( NETVSC_BASE_XID + xrid ); + unsigned int i; + int rc; + + /* Send control message */ + if ( ( rc = vmbus_send_control ( netvsc->vmdev, xid, data, len ) ) !=0){ + DBGC ( netvsc, "NETVSC %s could not send control message: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + /* Record transaction ID */ + netvsc->wait_xrid = xrid; + + /* Wait for operation to complete */ + for ( i = 0 ; i < NETVSC_MAX_WAIT_MS ; i++ ) { + + /* Check for completion */ + if ( ! netvsc->wait_xrid ) + return netvsc->wait_rc; + + /* Poll VMBus device */ + vmbus_poll ( netvsc->vmdev ); + + /* Delay for 1ms */ + mdelay ( 1 ); + } + + DBGC ( netvsc, "NETVSC %s timed out waiting for XRID %d\n", + netvsc->name, xrid ); + vmbus_dump_channel ( netvsc->vmdev ); + return -ETIMEDOUT; +} + +/** + * Handle generic completion + * + * @v netvsc NetVSC device + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int netvsc_completed ( struct netvsc_device *netvsc __unused, + const void *data __unused, size_t len __unused ) { + return 0; +} + +/** + * Initialise communication + * + * @v netvsc NetVSC device + * @ret rc Return status code + */ +static int netvsc_initialise ( struct netvsc_device *netvsc ) { + struct netvsc_init_message msg; + int rc; + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.header.type = cpu_to_le32 ( NETVSC_INIT_MSG ); + msg.min = cpu_to_le32 ( NETVSC_VERSION_1 ); + msg.max = cpu_to_le32 ( NETVSC_VERSION_1 ); + + /* Send message and wait for completion */ + if ( ( rc = netvsc_control ( netvsc, NETVSC_INIT_XRID, &msg, + sizeof ( msg ) ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not initialise: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Handle initialisation completion + * + * @v netvsc NetVSC device + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int +netvsc_initialised ( struct netvsc_device *netvsc, const void *data, + size_t len ) { + const struct netvsc_init_completion *cmplt = data; + + /* Check completion */ + if ( len < sizeof ( *cmplt ) ) { + DBGC ( netvsc, "NETVSC %s underlength initialisation " + "completion (%zd bytes)\n", netvsc->name, len ); + return -EINVAL; + } + if ( cmplt->header.type != cpu_to_le32 ( NETVSC_INIT_CMPLT ) ) { + DBGC ( netvsc, "NETVSC %s unexpected initialisation completion " + "type %d\n", netvsc->name, + le32_to_cpu ( cmplt->header.type ) ); + return -EPROTO; + } + if ( cmplt->status != cpu_to_le32 ( NETVSC_OK ) ) { + DBGC ( netvsc, "NETVSC %s initialisation failure status %d\n", + netvsc->name, le32_to_cpu ( cmplt->status ) ); + return -EPROTO; + } + + return 0; +} + +/** + * Set NDIS version + * + * @v netvsc NetVSC device + * @ret rc Return status code + */ +static int netvsc_ndis_version ( struct netvsc_device *netvsc ) { + struct netvsc_ndis_version_message msg; + int rc; + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.header.type = cpu_to_le32 ( NETVSC_NDIS_VERSION_MSG ); + msg.major = cpu_to_le32 ( NETVSC_NDIS_MAJOR ); + msg.minor = cpu_to_le32 ( NETVSC_NDIS_MINOR ); + + /* Send message and wait for completion */ + if ( ( rc = netvsc_control ( netvsc, NETVSC_NDIS_VERSION_XRID, + &msg, sizeof ( msg ) ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not set NDIS version: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Establish data buffer + * + * @v netvsc NetVSC device + * @v buffer Data buffer + * @ret rc Return status code + */ +static int netvsc_establish_buffer ( struct netvsc_device *netvsc, + struct netvsc_buffer *buffer ) { + struct netvsc_establish_buffer_message msg; + int rc; + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.header.type = cpu_to_le32 ( buffer->establish_type ); + msg.gpadl = cpu_to_le32 ( buffer->gpadl ); + msg.pageset = buffer->pages.pageset; /* Already protocol-endian */ + + /* Send message and wait for completion */ + if ( ( rc = netvsc_control ( netvsc, buffer->establish_xrid, &msg, + sizeof ( msg ) ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not establish buffer: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Handle establish receive data buffer completion + * + * @v netvsc NetVSC device + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int netvsc_rx_established_buffer ( struct netvsc_device *netvsc, + const void *data, size_t len ) { + const struct netvsc_rx_establish_buffer_completion *cmplt = data; + + /* Check completion */ + if ( len < sizeof ( *cmplt ) ) { + DBGC ( netvsc, "NETVSC %s underlength buffer completion (%zd " + "bytes)\n", netvsc->name, len ); + return -EINVAL; + } + if ( cmplt->header.type != cpu_to_le32 ( NETVSC_RX_ESTABLISH_CMPLT ) ) { + DBGC ( netvsc, "NETVSC %s unexpected buffer completion type " + "%d\n", netvsc->name, le32_to_cpu ( cmplt->header.type)); + return -EPROTO; + } + if ( cmplt->status != cpu_to_le32 ( NETVSC_OK ) ) { + DBGC ( netvsc, "NETVSC %s buffer failure status %d\n", + netvsc->name, le32_to_cpu ( cmplt->status ) ); + return -EPROTO; + } + + return 0; +} + +/** + * Revoke data buffer + * + * @v netvsc NetVSC device + * @v buffer Data buffer + * @ret rc Return status code + */ +static int netvsc_revoke_buffer ( struct netvsc_device *netvsc, + struct netvsc_buffer *buffer ) { + struct netvsc_revoke_buffer_message msg; + int rc; + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.header.type = cpu_to_le32 ( buffer->revoke_type ); + msg.pageset = buffer->pages.pageset; /* Already protocol-endian */ + + /* Send message and wait for completion */ + if ( ( rc = netvsc_control ( netvsc, buffer->revoke_xrid, + &msg, sizeof ( msg ) ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not revoke buffer: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Handle received control packet + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int netvsc_recv_control ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len ) { + struct rndis_device *rndis = vmbus_get_drvdata ( vmdev ); + struct netvsc_device *netvsc = rndis->priv; + + DBGC ( netvsc, "NETVSC %s received unsupported control packet " + "(%08llx):\n", netvsc->name, xid ); + DBGC_HDA ( netvsc, 0, data, len ); + return -ENOTSUP; +} + +/** + * Handle received data packet + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @v data Data + * @v len Length of data + * @v list List of I/O buffers + * @ret rc Return status code + */ +static int netvsc_recv_data ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len, + struct list_head *list ) { + struct rndis_device *rndis = vmbus_get_drvdata ( vmdev ); + struct netvsc_device *netvsc = rndis->priv; + const struct netvsc_rndis_message *msg = data; + struct io_buffer *iobuf; + struct io_buffer *tmp; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *msg ) ) { + DBGC ( netvsc, "NETVSC %s received underlength RNDIS packet " + "(%zd bytes)\n", netvsc->name, len ); + rc = -EINVAL; + goto err_sanity; + } + if ( msg->header.type != cpu_to_le32 ( NETVSC_RNDIS_MSG ) ) { + DBGC ( netvsc, "NETVSC %s received unexpected RNDIS packet " + "type %d\n", netvsc->name, + le32_to_cpu ( msg->header.type ) ); + rc = -EINVAL; + goto err_sanity; + } + + /* Send completion back to host */ + if ( ( rc = vmbus_send_completion ( vmdev, xid, NULL, 0 ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not send completion: %s\n", + netvsc->name, strerror ( rc ) ); + goto err_completion; + } + + /* Hand off to RNDIS */ + list_for_each_entry_safe ( iobuf, tmp, list, list ) { + list_del ( &iobuf->list ); + rndis_rx ( rndis, iob_disown ( iobuf ) ); + } + + return 0; + + err_completion: + err_sanity: + list_for_each_entry_safe ( iobuf, tmp, list, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } + return rc; +} + +/** + * Handle received completion packet + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @v data Data + * @v len Length of data + * @ret rc Return status code + */ +static int netvsc_recv_completion ( struct vmbus_device *vmdev, uint64_t xid, + const void *data, size_t len ) { + struct rndis_device *rndis = vmbus_get_drvdata ( vmdev ); + struct netvsc_device *netvsc = rndis->priv; + struct io_buffer *iobuf; + int ( * completion ) ( struct netvsc_device *netvsc, + const void *data, size_t len ); + unsigned int xrid = ( xid - NETVSC_BASE_XID ); + unsigned int tx_id; + int rc; + + /* Handle transmit completion, if applicable */ + tx_id = ( xrid - NETVSC_TX_BASE_XRID ); + if ( ( tx_id < NETVSC_TX_NUM_DESC ) && + ( ( iobuf = netvsc->tx.iobufs[tx_id] ) != NULL ) ) { + + /* Free buffer ID */ + netvsc->tx.iobufs[tx_id] = NULL; + netvsc->tx.ids[ ( netvsc->tx.id_cons++ ) & + ( netvsc->tx.count - 1 ) ] = tx_id; + + /* Hand back to RNDIS */ + rndis_tx_complete ( rndis, iobuf ); + return 0; + } + + /* Otherwise determine completion handler */ + if ( xrid == NETVSC_INIT_XRID ) { + completion = netvsc_initialised; + } else if ( xrid == NETVSC_RX_ESTABLISH_XRID ) { + completion = netvsc_rx_established_buffer; + } else if ( ( netvsc->wait_xrid != 0 ) && + ( xrid == netvsc->wait_xrid ) ) { + completion = netvsc_completed; + } else { + DBGC ( netvsc, "NETVSC %s received unexpected completion " + "(%08llx)\n", netvsc->name, xid ); + return -EPIPE; + } + + /* Hand off to completion handler */ + rc = completion ( netvsc, data, len ); + + /* Record completion handler result if applicable */ + if ( xrid == netvsc->wait_xrid ) { + netvsc->wait_xrid = 0; + netvsc->wait_rc = rc; + } + + return rc; +} + +/** + * Handle received cancellation packet + * + * @v vmdev VMBus device + * @v xid Transaction ID + * @ret rc Return status code + */ +static int netvsc_recv_cancellation ( struct vmbus_device *vmdev, + uint64_t xid ) { + struct rndis_device *rndis = vmbus_get_drvdata ( vmdev ); + struct netvsc_device *netvsc = rndis->priv; + + DBGC ( netvsc, "NETVSC %s received unsupported cancellation packet " + "(%08llx):\n", netvsc->name, xid ); + return -ENOTSUP; +} + +/** VMBus channel operations */ +static struct vmbus_channel_operations netvsc_channel_operations = { + .recv_control = netvsc_recv_control, + .recv_data = netvsc_recv_data, + .recv_completion = netvsc_recv_completion, + .recv_cancellation = netvsc_recv_cancellation, +}; + +/** + * Poll for completed and received packets + * + * @v rndis RNDIS device + */ +static void netvsc_poll ( struct rndis_device *rndis ) { + struct netvsc_device *netvsc = rndis->priv; + struct vmbus_device *vmdev = netvsc->vmdev; + + /* Poll VMBus device */ + while ( vmbus_has_data ( vmdev ) ) + vmbus_poll ( vmdev ); +} + +/** + * Transmit packet + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @ret rc Return status code + * + * If this method returns success then the RNDIS device must + * eventually report completion via rndis_tx_complete(). + */ +static int netvsc_transmit ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct netvsc_device *netvsc = rndis->priv; + struct rndis_header *header = iobuf->data; + struct netvsc_rndis_message msg; + unsigned int tx_id; + unsigned int xrid; + uint64_t xid; + int rc; + + /* Sanity check */ + assert ( iob_len ( iobuf ) >= sizeof ( *header ) ); + assert ( iob_len ( iobuf ) == le32_to_cpu ( header->len ) ); + + /* Check that we have space in the transmit ring */ + if ( netvsc_ring_is_full ( &netvsc->tx ) ) + return rndis_tx_defer ( rndis, iobuf ); + + /* Allocate buffer ID and calculate transaction ID */ + tx_id = netvsc->tx.ids[ netvsc->tx.id_prod & ( netvsc->tx.count - 1 ) ]; + assert ( netvsc->tx.iobufs[tx_id] == NULL ); + xrid = ( NETVSC_TX_BASE_XRID + tx_id ); + xid = ( NETVSC_BASE_XID + xrid ); + + /* Construct message */ + memset ( &msg, 0, sizeof ( msg ) ); + msg.header.type = cpu_to_le32 ( NETVSC_RNDIS_MSG ); + msg.channel = ( ( header->type == cpu_to_le32 ( RNDIS_PACKET_MSG ) ) ? + NETVSC_RNDIS_DATA : NETVSC_RNDIS_CONTROL ); + msg.buffer = cpu_to_le32 ( NETVSC_RNDIS_NO_BUFFER ); + + /* Send message */ + if ( ( rc = vmbus_send_data ( netvsc->vmdev, xid, &msg, sizeof ( msg ), + iobuf ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not send RNDIS message: %s\n", + netvsc->name, strerror ( rc ) ); + return rc; + } + + /* Store I/O buffer and consume buffer ID */ + netvsc->tx.iobufs[tx_id] = iobuf; + netvsc->tx.id_prod++; + + return 0; +} + +/** + * Cancel transmission + * + * @v netvsc NetVSC device + * @v iobuf I/O buffer + * @v tx_id Transmission ID + */ +static void netvsc_cancel_transmit ( struct netvsc_device *netvsc, + struct io_buffer *iobuf, + unsigned int tx_id ) { + unsigned int xrid; + uint64_t xid; + + /* Send cancellation */ + xrid = ( NETVSC_TX_BASE_XRID + tx_id ); + xid = ( NETVSC_BASE_XID + xrid ); + DBGC ( netvsc, "NETVSC %s cancelling transmission %#x\n", + netvsc->name, tx_id ); + vmbus_send_cancellation ( netvsc->vmdev, xid ); + + /* Report back to RNDIS */ + rndis_tx_complete_err ( netvsc->rndis, iobuf, -ECANCELED ); +} + +/** + * Create descriptor ring + * + * @v netvsc NetVSC device + * @v ring Descriptor ring + * @ret rc Return status code + */ +static int netvsc_create_ring ( struct netvsc_device *netvsc __unused, + struct netvsc_ring *ring ) { + unsigned int i; + + /* Initialise buffer ID ring */ + for ( i = 0 ; i < ring->count ; i++ ) { + ring->ids[i] = i; + assert ( ring->iobufs[i] == NULL ); + } + ring->id_prod = 0; + ring->id_cons = 0; + + return 0; +} + +/** + * Destroy descriptor ring + * + * @v netvsc NetVSC device + * @v ring Descriptor ring + * @v discard Method used to discard outstanding buffer, or NULL + */ +static void netvsc_destroy_ring ( struct netvsc_device *netvsc, + struct netvsc_ring *ring, + void ( * discard ) ( struct netvsc_device *, + struct io_buffer *, + unsigned int ) ) { + struct io_buffer *iobuf; + unsigned int i; + + /* Flush any outstanding buffers */ + for ( i = 0 ; i < ring->count ; i++ ) { + iobuf = ring->iobufs[i]; + if ( ! iobuf ) + continue; + ring->iobufs[i] = NULL; + ring->ids[ ( ring->id_cons++ ) & ( ring->count - 1 ) ] = i; + if ( discard ) + discard ( netvsc, iobuf, i ); + } + + /* Sanity check */ + assert ( netvsc_ring_is_empty ( ring ) ); +} + +/** + * Copy data from data buffer + * + * @v pages Transfer page set + * @v data Data buffer + * @v offset Offset within page set + * @v len Length within page set + * @ret rc Return status code + */ +static int netvsc_buffer_copy ( struct vmbus_xfer_pages *pages, void *data, + size_t offset, size_t len ) { + struct netvsc_buffer *buffer = + container_of ( pages, struct netvsc_buffer, pages ); + + /* Sanity check */ + if ( ( offset > buffer->len ) || ( len > ( buffer->len - offset ) ) ) + return -ERANGE; + + /* Copy data from buffer */ + copy_from_user ( data, buffer->data, offset, len ); + + return 0; +} + +/** Transfer page set operations */ +static struct vmbus_xfer_pages_operations netvsc_xfer_pages_operations = { + .copy = netvsc_buffer_copy, +}; + +/** + * Create data buffer + * + * @v netvsc NetVSC device + * @v buffer Data buffer + * @ret rc Return status code + */ +static int netvsc_create_buffer ( struct netvsc_device *netvsc, + struct netvsc_buffer *buffer ) { + struct vmbus_device *vmdev = netvsc->vmdev; + int gpadl; + int rc; + + /* Allocate receive buffer */ + buffer->data = umalloc ( buffer->len ); + if ( ! buffer->data ) { + DBGC ( netvsc, "NETVSC %s could not allocate %zd-byte buffer\n", + netvsc->name, buffer->len ); + rc = -ENOMEM; + goto err_alloc; + } + + /* Establish GPA descriptor list */ + gpadl = vmbus_establish_gpadl ( vmdev, buffer->data, buffer->len ); + if ( gpadl < 0 ) { + rc = gpadl; + DBGC ( netvsc, "NETVSC %s could not establish GPADL: %s\n", + netvsc->name, strerror ( rc ) ); + goto err_establish_gpadl; + } + buffer->gpadl = gpadl; + + /* Register transfer page set */ + if ( ( rc = vmbus_register_pages ( vmdev, &buffer->pages ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not register transfer pages: " + "%s\n", netvsc->name, strerror ( rc ) ); + goto err_register_pages; + } + + return 0; + + vmbus_unregister_pages ( vmdev, &buffer->pages ); + err_register_pages: + vmbus_gpadl_teardown ( vmdev, gpadl ); + err_establish_gpadl: + ufree ( buffer->data ); + err_alloc: + return rc; +} + +/** + * Destroy data buffer + * + * @v netvsc NetVSC device + * @v buffer Data buffer + */ +static void netvsc_destroy_buffer ( struct netvsc_device *netvsc, + struct netvsc_buffer *buffer ) { + struct vmbus_device *vmdev = netvsc->vmdev; + int rc; + + /* Unregister transfer pages */ + vmbus_unregister_pages ( vmdev, &buffer->pages ); + + /* Tear down GPA descriptor list */ + if ( ( rc = vmbus_gpadl_teardown ( vmdev, buffer->gpadl ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not tear down GPADL: %s\n", + netvsc->name, strerror ( rc ) ); + /* Death is imminent. The host may well continue to + * write to the data buffer. The best we can do is + * leak memory for now and hope that the host doesn't + * write to this region after we load an OS. + */ + return; + } + + /* Free buffer */ + ufree ( buffer->data ); +} + +/** + * Open device + * + * @v rndis RNDIS device + * @ret rc Return status code + */ +static int netvsc_open ( struct rndis_device *rndis ) { + struct netvsc_device *netvsc = rndis->priv; + int rc; + + /* Initialise receive buffer */ + if ( ( rc = netvsc_create_buffer ( netvsc, &netvsc->rx ) ) != 0 ) + goto err_create_rx; + + /* Open channel */ + if ( ( rc = vmbus_open ( netvsc->vmdev, &netvsc_channel_operations, + PAGE_SIZE, PAGE_SIZE, NETVSC_MTU ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not open VMBus: %s\n", + netvsc->name, strerror ( rc ) ); + goto err_vmbus_open; + } + + /* Initialise communication with NetVSP */ + if ( ( rc = netvsc_initialise ( netvsc ) ) != 0 ) + goto err_initialise; + if ( ( rc = netvsc_ndis_version ( netvsc ) ) != 0 ) + goto err_ndis_version; + + /* Initialise transmit ring */ + if ( ( rc = netvsc_create_ring ( netvsc, &netvsc->tx ) ) != 0 ) + goto err_create_tx; + + /* Establish receive buffer */ + if ( ( rc = netvsc_establish_buffer ( netvsc, &netvsc->rx ) ) != 0 ) + goto err_establish_rx; + + return 0; + + netvsc_revoke_buffer ( netvsc, &netvsc->rx ); + err_establish_rx: + netvsc_destroy_ring ( netvsc, &netvsc->tx, NULL ); + err_create_tx: + err_ndis_version: + err_initialise: + vmbus_close ( netvsc->vmdev ); + err_vmbus_open: + netvsc_destroy_buffer ( netvsc, &netvsc->rx ); + err_create_rx: + return rc; +} + +/** + * Close device + * + * @v rndis RNDIS device + */ +static void netvsc_close ( struct rndis_device *rndis ) { + struct netvsc_device *netvsc = rndis->priv; + + /* Revoke receive buffer */ + netvsc_revoke_buffer ( netvsc, &netvsc->rx ); + + /* Destroy transmit ring */ + netvsc_destroy_ring ( netvsc, &netvsc->tx, netvsc_cancel_transmit ); + + /* Close channel */ + vmbus_close ( netvsc->vmdev ); + + /* Destroy receive buffer */ + netvsc_destroy_buffer ( netvsc, &netvsc->rx ); +} + +/** RNDIS operations */ +static struct rndis_operations netvsc_operations = { + .open = netvsc_open, + .close = netvsc_close, + .transmit = netvsc_transmit, + .poll = netvsc_poll, +}; + +/** + * Probe device + * + * @v vmdev VMBus device + * @ret rc Return status code + */ +static int netvsc_probe ( struct vmbus_device *vmdev ) { + struct netvsc_device *netvsc; + struct rndis_device *rndis; + int rc; + + /* Allocate and initialise structure */ + rndis = alloc_rndis ( sizeof ( *netvsc ) ); + if ( ! rndis ) { + rc = -ENOMEM; + goto err_alloc; + } + rndis_init ( rndis, &netvsc_operations ); + rndis->netdev->dev = &vmdev->dev; + netvsc = rndis->priv; + netvsc->vmdev = vmdev; + netvsc->rndis = rndis; + netvsc->name = vmdev->dev.name; + netvsc_init_ring ( &netvsc->tx, NETVSC_TX_NUM_DESC, + netvsc->tx_iobufs, netvsc->tx_ids ); + netvsc_init_buffer ( &netvsc->rx, NETVSC_RX_BUF_PAGESET, + &netvsc_xfer_pages_operations, + NETVSC_RX_ESTABLISH_MSG, NETVSC_RX_ESTABLISH_XRID, + NETVSC_RX_REVOKE_MSG, NETVSC_RX_REVOKE_XRID, + NETVSC_RX_BUF_LEN ); + vmbus_set_drvdata ( vmdev, rndis ); + + /* Register RNDIS device */ + if ( ( rc = register_rndis ( rndis ) ) != 0 ) { + DBGC ( netvsc, "NETVSC %s could not register: %s\n", + netvsc->name, strerror ( rc ) ); + goto err_register; + } + + return 0; + + unregister_rndis ( rndis ); + err_register: + free_rndis ( rndis ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v vmdev VMBus device + */ +static void netvsc_remove ( struct vmbus_device *vmdev ) { + struct rndis_device *rndis = vmbus_get_drvdata ( vmdev ); + + /* Unregister RNDIS device */ + unregister_rndis ( rndis ); + + /* Free RNDIS device */ + free_rndis ( rndis ); +} + +/** NetVSC driver */ +struct vmbus_driver netvsc_driver __vmbus_driver = { + .name = "netvsc", + .type = VMBUS_TYPE ( 0xf8615163, 0xdf3e, 0x46c5, 0x913f, + 0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e ), + .probe = netvsc_probe, + .remove = netvsc_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/netvsc.h b/qemu/roms/ipxe/src/drivers/net/netvsc.h new file mode 100644 index 000000000..39eeb891c --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/netvsc.h @@ -0,0 +1,365 @@ +#ifndef _NETVSC_H +#define _NETVSC_H + +/** @file + * + * Hyper-V network virtual service client + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** Maximum supported NetVSC message length */ +#define NETVSC_MTU 512 + +/** Maximum time to wait for a transaction to complete + * + * This is a policy decision. + */ +#define NETVSC_MAX_WAIT_MS 1000 + +/** Number of transmit ring entries + * + * Must be a power of two. This is a policy decision. This value + * must be sufficiently small to guarantee that we never run out of + * space in the VMBus outbound ring buffer. + */ +#define NETVSC_TX_NUM_DESC 32 + +/** RX data buffer page set ID + * + * This is a policy decision. + */ +#define NETVSC_RX_BUF_PAGESET 0xbead + +/** RX data buffer length + * + * This is a policy decision. + */ +#define NETVSC_RX_BUF_LEN ( 16 * PAGE_SIZE ) + +/** Base transaction ID + * + * This is a policy decision. + */ +#define NETVSC_BASE_XID 0x18ae0000UL + +/** Relative transaction IDs */ +enum netvsc_xrid { + /** Transmit descriptors (one per transmit buffer ID) */ + NETVSC_TX_BASE_XRID = 0, + /** Initialisation */ + NETVSC_INIT_XRID = ( NETVSC_TX_BASE_XRID + NETVSC_TX_NUM_DESC ), + /** NDIS version */ + NETVSC_NDIS_VERSION_XRID, + /** Establish receive buffer */ + NETVSC_RX_ESTABLISH_XRID, + /** Revoke receive buffer */ + NETVSC_RX_REVOKE_XRID, +}; + +/** NetVSC status codes */ +enum netvsc_status { + NETVSC_NONE = 0, + NETVSC_OK = 1, + NETVSC_FAIL = 2, + NETVSC_TOO_NEW = 3, + NETVSC_TOO_OLD = 4, + NETVSC_BAD_PACKET = 5, + NETVSC_BUSY = 6, + NETVSC_UNSUPPORTED = 7, +}; + +/** NetVSC message header */ +struct netvsc_header { + /** Type */ + uint32_t type; +} __attribute__ (( packed )); + +/** NetVSC initialisation message */ +#define NETVSC_INIT_MSG 1 + +/** NetVSC initialisation message */ +struct netvsc_init_message { + /** Message header */ + struct netvsc_header header; + /** Minimum supported protocol version */ + uint32_t min; + /** Maximum supported protocol version */ + uint32_t max; + /** Reserved */ + uint8_t reserved[20]; +} __attribute__ (( packed )); + +/** Oldest known NetVSC protocol version */ +#define NETVSC_VERSION_1 2 /* sic */ + +/** NetVSC initialisation completion */ +#define NETVSC_INIT_CMPLT 2 + +/** NetVSC initialisation completion */ +struct netvsc_init_completion { + /** Message header */ + struct netvsc_header header; + /** Protocol version */ + uint32_t version; + /** Maximum memory descriptor list length */ + uint32_t max_mdl_len; + /** Status */ + uint32_t status; + /** Reserved */ + uint8_t reserved[16]; +} __attribute__ (( packed )); + +/** NetVSC NDIS version message */ +#define NETVSC_NDIS_VERSION_MSG 100 + +/** NetVSC NDIS version message */ +struct netvsc_ndis_version_message { + /** Message header */ + struct netvsc_header header; + /** Major version */ + uint32_t major; + /** Minor version */ + uint32_t minor; + /** Reserved */ + uint8_t reserved[20]; +} __attribute__ (( packed )); + +/** NetVSC NDIS major version */ +#define NETVSC_NDIS_MAJOR 6 + +/** NetVSC NDIS minor version */ +#define NETVSC_NDIS_MINOR 1 + +/** NetVSC establish receive data buffer message */ +#define NETVSC_RX_ESTABLISH_MSG 101 + +/** NetVSC establish receive data buffer completion */ +#define NETVSC_RX_ESTABLISH_CMPLT 102 + +/** NetVSC revoke receive data buffer message */ +#define NETVSC_RX_REVOKE_MSG 103 + +/** NetVSC establish transmit data buffer message */ +#define NETVSC_TX_ESTABLISH_MSG 104 + +/** NetVSC establish transmit data buffer completion */ +#define NETVSC_TX_ESTABLISH_CMPLT 105 + +/** NetVSC revoke transmit data buffer message */ +#define NETVSC_TX_REVOKE_MSG 106 + +/** NetVSC establish data buffer message */ +struct netvsc_establish_buffer_message { + /** Message header */ + struct netvsc_header header; + /** GPADL ID */ + uint32_t gpadl; + /** Page set ID */ + uint16_t pageset; + /** Reserved */ + uint8_t reserved[22]; +} __attribute__ (( packed )); + +/** NetVSC receive data buffer section */ +struct netvsc_rx_buffer_section { + /** Starting offset */ + uint32_t start; + /** Subsection length */ + uint32_t len; + /** Number of subsections */ + uint32_t count; + /** Ending offset */ + uint32_t end; +} __attribute__ (( packed )); + +/** NetVSC establish receive data buffer completion */ +struct netvsc_rx_establish_buffer_completion { + /** Message header */ + struct netvsc_header header; + /** Status */ + uint32_t status; + /** Number of sections (must be 1) */ + uint32_t count; + /** Section descriptors */ + struct netvsc_rx_buffer_section section[1]; +} __attribute__ (( packed )); + +/** NetVSC establish transmit data buffer completion */ +struct netvsc_tx_establish_buffer_completion { + /** Message header */ + struct netvsc_header header; + /** Status */ + uint32_t status; + /** Section length */ + uint32_t len; +} __attribute__ (( packed )); + +/** NetVSC revoke data buffer message */ +struct netvsc_revoke_buffer_message { + /** Message header */ + struct netvsc_header header; + /** Page set ID */ + uint16_t pageset; + /** Reserved */ + uint8_t reserved[26]; +} __attribute__ (( packed )); + +/** NetVSC RNDIS message */ +#define NETVSC_RNDIS_MSG 107 + +/** NetVSC RNDIS message */ +struct netvsc_rndis_message { + /** Message header */ + struct netvsc_header header; + /** RNDIS channel */ + uint32_t channel; + /** Buffer index (or NETVSC_RNDIS_NO_BUFFER) */ + uint32_t buffer; + /** Buffer length */ + uint32_t len; + /** Reserved */ + uint8_t reserved[16]; +} __attribute__ (( packed )); + +/** RNDIS data channel (for RNDIS_PACKET_MSG only) */ +#define NETVSC_RNDIS_DATA 0 + +/** RNDIS control channel (for all other RNDIS messages) */ +#define NETVSC_RNDIS_CONTROL 1 + +/** "No buffer used" index */ +#define NETVSC_RNDIS_NO_BUFFER 0xffffffffUL + +/** A NetVSC descriptor ring */ +struct netvsc_ring { + /** Number of descriptors */ + unsigned int count; + /** I/O buffers, indexed by buffer ID */ + struct io_buffer **iobufs; + /** Buffer ID ring */ + uint8_t *ids; + /** Buffer ID producer counter */ + unsigned int id_prod; + /** Buffer ID consumer counter */ + unsigned int id_cons; +}; + +/** + * Initialise descriptor ring + * + * @v ring Descriptor ring + * @v count Maximum number of used descriptors + * @v iobufs I/O buffers + * @v ids Buffer IDs + */ +static inline __attribute__ (( always_inline )) void +netvsc_init_ring ( struct netvsc_ring *ring, unsigned int count, + struct io_buffer **iobufs, uint8_t *ids ) { + + ring->count = count; + ring->iobufs = iobufs; + ring->ids = ids; +} + +/** + * Check whether or not descriptor ring is full + * + * @v ring Descriptor ring + * @v is_full Ring is full + */ +static inline __attribute__ (( always_inline )) int +netvsc_ring_is_full ( struct netvsc_ring *ring ) { + unsigned int fill_level; + + fill_level = ( ring->id_prod - ring->id_cons ); + assert ( fill_level <= ring->count ); + return ( fill_level >= ring->count ); +} + +/** + * Check whether or not descriptor ring is empty + * + * @v ring Descriptor ring + * @v is_empty Ring is empty + */ +static inline __attribute__ (( always_inline )) int +netvsc_ring_is_empty ( struct netvsc_ring *ring ) { + + return ( ring->id_prod == ring->id_cons ); +} + +/** A NetVSC data buffer */ +struct netvsc_buffer { + /** Transfer page set */ + struct vmbus_xfer_pages pages; + /** Establish data buffer message type */ + uint8_t establish_type; + /** Establish data buffer relative transaction ID */ + uint8_t establish_xrid; + /** Revoke data buffer message type */ + uint8_t revoke_type; + /** Revoke data buffer relative transaction ID */ + uint8_t revoke_xrid; + /** Buffer length */ + size_t len; + /** Buffer */ + userptr_t data; + /** GPADL ID */ + unsigned int gpadl; +}; + +/** + * Initialise data buffer + * + * @v buffer Data buffer + * @v pageset Page set ID + * @v op Page set operations + * @v establish_type Establish data buffer message type + * @v establish_xrid Establish data buffer relative transaction ID + * @v revoke_type Revoke data buffer message type + * @v revoke_type Revoke data buffer relative transaction ID + * @v len Required length + */ +static inline __attribute__ (( always_inline )) void +netvsc_init_buffer ( struct netvsc_buffer *buffer, uint16_t pageset, + struct vmbus_xfer_pages_operations *op, + uint8_t establish_type, uint8_t establish_xrid, + uint8_t revoke_type, uint8_t revoke_xrid, size_t len ) { + + buffer->pages.pageset = cpu_to_le16 ( pageset ); + buffer->pages.op = op; + buffer->establish_type = establish_type; + buffer->establish_xrid = establish_xrid; + buffer->revoke_type = revoke_type; + buffer->revoke_xrid = revoke_xrid; + buffer->len = len; +} + +/** A NetVSC device */ +struct netvsc_device { + /** VMBus device */ + struct vmbus_device *vmdev; + /** RNDIS device */ + struct rndis_device *rndis; + /** Name */ + const char *name; + + /** Transmit ring */ + struct netvsc_ring tx; + /** Transmit buffer IDs */ + uint8_t tx_ids[NETVSC_TX_NUM_DESC]; + /** Transmit I/O buffers */ + struct io_buffer *tx_iobufs[NETVSC_TX_NUM_DESC]; + + /** Receive buffer */ + struct netvsc_buffer rx; + + /** Relative transaction ID for current blocking transaction */ + unsigned int wait_xrid; + /** Return status code for current blocking transaction */ + int wait_rc; +}; + +#endif /* _NETVSC_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/phantom/nx_bitops.h b/qemu/roms/ipxe/src/drivers/net/phantom/nx_bitops.h index 15f3d3767..1687b6952 100644 --- a/qemu/roms/ipxe/src/drivers/net/phantom/nx_bitops.h +++ b/qemu/roms/ipxe/src/drivers/net/phantom/nx_bitops.h @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c index e70ded08c..38b66743c 100644 --- a/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c +++ b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c @@ -16,9 +16,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/drivers/net/phantom/phantom.h b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.h index 1647168ba..967603409 100644 --- a/qemu/roms/ipxe/src/drivers/net/phantom/phantom.h +++ b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.h @@ -19,9 +19,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/net/phantom/phantom_hw.h b/qemu/roms/ipxe/src/drivers/net/phantom/phantom_hw.h index 7dfff52b2..016730de3 100644 --- a/qemu/roms/ipxe/src/drivers/net/phantom/phantom_hw.h +++ b/qemu/roms/ipxe/src/drivers/net/phantom/phantom_hw.h @@ -19,9 +19,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/net/pnic.c b/qemu/roms/ipxe/src/drivers/net/pnic.c index 4170cc640..ca64299ea 100644 --- a/qemu/roms/ipxe/src/drivers/net/pnic.c +++ b/qemu/roms/ipxe/src/drivers/net/pnic.c @@ -6,8 +6,18 @@ Bochs Pseudo NIC driver for Etherboot /* * 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, or (at - * your option) any later version. + * 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. * * See pnic_api.h for an explanation of the Bochs Pseudo NIC. */ diff --git a/qemu/roms/ipxe/src/drivers/net/prism2.c b/qemu/roms/ipxe/src/drivers/net/prism2.c index ab974264c..4331f2cd0 100644 --- a/qemu/roms/ipxe/src/drivers/net/prism2.c +++ b/qemu/roms/ipxe/src/drivers/net/prism2.c @@ -9,8 +9,18 @@ $Id$ /* * 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, or (at - * your option) any later version. + * 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. */ FILE_LICENCE ( GPL2_OR_LATER ); diff --git a/qemu/roms/ipxe/src/drivers/net/prism2_pci.c b/qemu/roms/ipxe/src/drivers/net/prism2_pci.c index 72549babf..69ddf0fb0 100644 --- a/qemu/roms/ipxe/src/drivers/net/prism2_pci.c +++ b/qemu/roms/ipxe/src/drivers/net/prism2_pci.c @@ -10,8 +10,18 @@ $Id$ /* * 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, or (at - * your option) any later version. + * 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. */ FILE_LICENCE ( GPL2_OR_LATER ); @@ -40,8 +50,6 @@ static void prism2_pci_disable ( struct nic *nic ) { static struct pci_device_id prism2_pci_nics[] = { PCI_ROM(0x1260, 0x3873, "prism2_pci", "Harris Semiconductor Prism2.5 clone", 0), -PCI_ROM(0x1260, 0x3873, "hwp01170", "ActionTec HWP01170", 0), -PCI_ROM(0x1260, 0x3873, "dwl520", "DLink DWL-520", 0), }; PCI_DRIVER ( prism2_pci_driver, prism2_pci_nics, PCI_NO_CLASS ); diff --git a/qemu/roms/ipxe/src/drivers/net/prism2_plx.c b/qemu/roms/ipxe/src/drivers/net/prism2_plx.c index 2098f7f09..a73b0e087 100644 --- a/qemu/roms/ipxe/src/drivers/net/prism2_plx.c +++ b/qemu/roms/ipxe/src/drivers/net/prism2_plx.c @@ -10,8 +10,18 @@ $Id$ /* * 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, or (at - * your option) any later version. + * 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. */ FILE_LICENCE ( GPL2_OR_LATER ); @@ -44,10 +54,10 @@ static int prism2_find_plx ( hfa384x_t *hw, struct pci_device *p ) /* Obtain all memory and IO base addresses */ pci_read_config_dword( p, PLX_LOCAL_CONFIG_REGISTER_BASE, &plx_lcr); - plx_lcr &= PCI_BASE_ADDRESS_IO_MASK; + plx_lcr &= ~PCI_BASE_ADDRESS_IO_MASK; pci_read_config_dword( p, PRISM2_PLX_ATTR_MEM_BASE, &attr_mem); pci_read_config_dword( p, PRISM2_PLX_IO_BASE, &iobase); - iobase &= PCI_BASE_ADDRESS_IO_MASK; + iobase &= ~PCI_BASE_ADDRESS_IO_MASK; /* Fill out hw structure */ hw->iobase = iobase; diff --git a/qemu/roms/ipxe/src/drivers/net/realtek.c b/qemu/roms/ipxe/src/drivers/net/realtek.c index 0aca8c77f..022b59324 100644 --- a/qemu/roms/ipxe/src/drivers/net/realtek.c +++ b/qemu/roms/ipxe/src/drivers/net/realtek.c @@ -17,9 +17,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> @@ -194,7 +198,6 @@ static int realtek_init_eeprom ( struct net_device *netdev ) { DBGC ( rtl, "REALTEK %p EEPROM is a 93C46\n", rtl ); init_at93c46 ( &rtl->eeprom, 16 ); } - rtl->eeprom.bus = &rtl->spibit.bus; /* Check for EEPROM presence. Some onboard NICs will have no * EEPROM connected, with the BIOS being responsible for @@ -1085,6 +1088,7 @@ static void realtek_detect ( struct realtek_nic *rtl ) { rtl ); rtl->legacy = 1; } + rtl->eeprom.bus = &rtl->spibit.bus; } } @@ -1132,7 +1136,8 @@ static int realtek_probe ( struct pci_device *pci ) { realtek_detect ( rtl ); /* Initialise EEPROM */ - if ( ( rc = realtek_init_eeprom ( netdev ) ) == 0 ) { + if ( rtl->eeprom.bus && + ( ( rc = realtek_init_eeprom ( netdev ) ) == 0 ) ) { /* Read MAC address from EEPROM */ if ( ( rc = nvs_read ( &rtl->eeprom.nvs, RTL_EEPROM_MAC, diff --git a/qemu/roms/ipxe/src/drivers/net/realtek.h b/qemu/roms/ipxe/src/drivers/net/realtek.h index ac33405e8..b1ce7f98f 100644 --- a/qemu/roms/ipxe/src/drivers/net/realtek.h +++ b/qemu/roms/ipxe/src/drivers/net/realtek.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/spi.h> #include <ipxe/spi_bit.h> diff --git a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8180.c b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8180.c index 8851d1bfb..5f97480fa 100644 --- a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8180.c +++ b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8180.c @@ -3,15 +3,23 @@ FILE_LICENCE(GPL2_OR_LATER); #include <ipxe/pci.h> +#include "rtl818x.h" -REQUIRE_OBJECT(rtl818x); -REQUIRE_OBJECT(rtl8180_grf5101); -REQUIRE_OBJECT(rtl8180_max2820); -REQUIRE_OBJECT(rtl8180_sa2400); - -static struct pci_device_id rtl8180_nics[] __unused = { +static struct pci_device_id rtl8180_nics[] = { PCI_ROM(0x10ec, 0x8180, "rtl8180", "Realtek 8180", 0), PCI_ROM(0x1799, 0x6001, "f5d6001", "Belkin F5D6001", 0), PCI_ROM(0x1799, 0x6020, "f5d6020", "Belkin F5D6020", 0), PCI_ROM(0x1186, 0x3300, "dwl510", "D-Link DWL-510", 0), }; + +struct pci_driver rtl8180_driver __pci_driver = { + .ids = rtl8180_nics, + .id_count = sizeof(rtl8180_nics) / sizeof(rtl8180_nics[0]), + .probe = rtl818x_probe, + .remove = rtl818x_remove, +}; + +REQUIRING_SYMBOL(rtl8180_driver); +REQUIRE_OBJECT(rtl8180_grf5101); +REQUIRE_OBJECT(rtl8180_max2820); +REQUIRE_OBJECT(rtl8180_sa2400); diff --git a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8185.c b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8185.c index fd27e5c8c..234978cea 100644 --- a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8185.c +++ b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl8185.c @@ -3,12 +3,20 @@ FILE_LICENCE(GPL2_OR_LATER); #include <ipxe/pci.h> - -REQUIRE_OBJECT(rtl818x); -REQUIRE_OBJECT(rtl8185_rtl8225); +#include "rtl818x.h" static struct pci_device_id rtl8185_nics[] __unused = { PCI_ROM(0x10ec, 0x8185, "rtl8185", "Realtek 8185", 0), PCI_ROM(0x1799, 0x700f, "f5d7000", "Belkin F5D7000", 0), PCI_ROM(0x1799, 0x701f, "f5d7010", "Belkin F5D7010", 0), }; + +struct pci_driver rtl8185_driver __pci_driver = { + .ids = rtl8185_nics, + .id_count = sizeof(rtl8185_nics) / sizeof(rtl8185_nics[0]), + .probe = rtl818x_probe, + .remove = rtl818x_remove, +}; + +REQUIRING_SYMBOL(rtl8185_driver); +REQUIRE_OBJECT(rtl8185_rtl8225); diff --git a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.c b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.c index cf4c7556f..8b3c206d4 100644 --- a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.c +++ b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.c @@ -649,7 +649,7 @@ struct net80211_device_operations rtl818x_operations = { .config = rtl818x_config, }; -static int rtl818x_probe(struct pci_device *pdev ) +int rtl818x_probe(struct pci_device *pdev ) { struct net80211_device *dev; struct rtl818x_priv *priv; @@ -820,7 +820,7 @@ static int rtl818x_probe(struct pci_device *pdev ) return err; } -static void rtl818x_remove(struct pci_device *pdev) +void rtl818x_remove(struct pci_device *pdev) { struct net80211_device *dev = pci_get_drvdata(pdev); @@ -830,25 +830,3 @@ static void rtl818x_remove(struct pci_device *pdev) net80211_unregister(dev); net80211_free(dev); } - -/* Hide PCI_ROM definitions in here from parserom.pl; the definitions - that should be used are in rtl8180.c and rtl8185.c. */ -#define RTL_ROM PCI_ROM - -static struct pci_device_id rtl818x_nics[] = { - RTL_ROM(0x10ec, 0x8185, "rtl8185", "Realtek 8185", 0), - RTL_ROM(0x1799, 0x700f, "f5d7000", "Belkin F5D7000", 0), - RTL_ROM(0x1799, 0x701f, "f5d7010", "Belkin F5D7010", 0), - - RTL_ROM(0x10ec, 0x8180, "rtl8180", "Realtek 8180", 0), - RTL_ROM(0x1799, 0x6001, "f5d6001", "Belkin F5D6001", 0), - RTL_ROM(0x1799, 0x6020, "f5d6020", "Belkin F5D6020", 0), - RTL_ROM(0x1186, 0x3300, "dwl510", "D-Link DWL-510", 0), -}; - -struct pci_driver rtl818x_driver __pci_driver = { - .ids = rtl818x_nics, - .id_count = sizeof(rtl818x_nics) / sizeof(rtl818x_nics[0]), - .probe = rtl818x_probe, - .remove = rtl818x_remove, -}; diff --git a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.h b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.h index 4e57d0bd3..ae4b8a96f 100644 --- a/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.h +++ b/qemu/roms/ipxe/src/drivers/net/rtl818x/rtl818x.h @@ -19,6 +19,7 @@ #include <ipxe/spi_bit.h> #include <ipxe/tables.h> +#include <ipxe/net80211.h> FILE_LICENCE(GPL2_ONLY); @@ -356,4 +357,7 @@ struct rtl818x_rf_ops { void (*conf_erp)(struct net80211_device *dev); /* set based on dev->erp_flags */ }; +extern int rtl818x_probe(struct pci_device *pdev ); +extern void rtl818x_remove(struct pci_device *pdev); + #endif /* RTL818X_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/skeleton.c b/qemu/roms/ipxe/src/drivers/net/skeleton.c index 365111b8d..0435b9d0e 100644 --- a/qemu/roms/ipxe/src/drivers/net/skeleton.c +++ b/qemu/roms/ipxe/src/drivers/net/skeleton.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/drivers/net/skeleton.h b/qemu/roms/ipxe/src/drivers/net/skeleton.h index 3de2afa5b..2ab01bd56 100644 --- a/qemu/roms/ipxe/src/drivers/net/skeleton.h +++ b/qemu/roms/ipxe/src/drivers/net/skeleton.h @@ -7,7 +7,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** Skeleton BAR size */ #define SKELETON_BAR_SIZE 256 diff --git a/qemu/roms/ipxe/src/drivers/net/smsc75xx.c b/qemu/roms/ipxe/src/drivers/net/smsc75xx.c new file mode 100644 index 000000000..017e02a59 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/smsc75xx.c @@ -0,0 +1,1057 @@ +/* + * 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 <string.h> +#include <unistd.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/ethernet.h> +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include <ipxe/profile.h> +#include "smsc75xx.h" + +/** @file + * + * SMSC LAN75xx USB Ethernet driver + * + */ + +/** Interrupt completion profiler */ +static struct profiler smsc75xx_intr_profiler __profiler = + { .name = "smsc75xx.intr" }; + +/** Bulk IN completion profiler */ +static struct profiler smsc75xx_in_profiler __profiler = + { .name = "smsc75xx.in" }; + +/** Bulk OUT profiler */ +static struct profiler smsc75xx_out_profiler __profiler = + { .name = "smsc75xx.out" }; + +/****************************************************************************** + * + * Register access + * + ****************************************************************************** + */ + +/** + * Write register (without byte-swapping) + * + * @v smsc75xx SMSC75xx device + * @v address Register address + * @v value Register value + * @ret rc Return status code + */ +static int smsc75xx_raw_writel ( struct smsc75xx_device *smsc75xx, + unsigned int address, uint32_t value ) { + int rc; + + /* Write register */ + if ( ( rc = usb_control ( smsc75xx->usb, SMSC75XX_REGISTER_WRITE, 0, + address, &value, sizeof ( value ) ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not write %03x: %s\n", + smsc75xx, address, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Write register + * + * @v smsc75xx SMSC75xx device + * @v address Register address + * @v value Register value + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +smsc75xx_writel ( struct smsc75xx_device *smsc75xx, unsigned int address, + uint32_t value ) { + int rc; + + /* Write register */ + if ( ( rc = smsc75xx_raw_writel ( smsc75xx, address, + cpu_to_le32 ( value ) ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Read register (without byte-swapping) + * + * @v smsc75xx SMSC75xx device + * @v address Register address + * @ret value Register value + * @ret rc Return status code + */ +static int smsc75xx_raw_readl ( struct smsc75xx_device *smsc75xx, + unsigned int address, uint32_t *value ) { + int rc; + + /* Read register */ + if ( ( rc = usb_control ( smsc75xx->usb, SMSC75XX_REGISTER_READ, 0, + address, value, sizeof ( *value ) ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not read %03x: %s\n", + smsc75xx, address, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Read register + * + * @v smsc75xx SMSC75xx device + * @v address Register address + * @ret value Register value + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +smsc75xx_readl ( struct smsc75xx_device *smsc75xx, unsigned int address, + uint32_t *value ) { + int rc; + + /* Read register */ + if ( ( rc = smsc75xx_raw_readl ( smsc75xx, address, value ) ) != 0 ) + return rc; + le32_to_cpus ( value ); + + return 0; +} + +/****************************************************************************** + * + * EEPROM access + * + ****************************************************************************** + */ + +/** + * Wait for EEPROM to become idle + * + * @v smsc75xx SMSC75xx device + * @ret rc Return status code + */ +static int smsc75xx_eeprom_wait ( struct smsc75xx_device *smsc75xx ) { + uint32_t e2p_cmd; + unsigned int i; + int rc; + + /* Wait for EPC_BSY to become clear */ + for ( i = 0 ; i < SMSC75XX_EEPROM_MAX_WAIT_MS ; i++ ) { + + /* Read E2P_CMD and check EPC_BSY */ + if ( ( rc = smsc75xx_readl ( smsc75xx, SMSC75XX_E2P_CMD, + &e2p_cmd ) ) != 0 ) + return rc; + if ( ! ( e2p_cmd & SMSC75XX_E2P_CMD_EPC_BSY ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( smsc75xx, "SMSC75XX %p timed out waiting for EEPROM\n", + smsc75xx ); + return -ETIMEDOUT; +} + +/** + * Read byte from EEPROM + * + * @v smsc75xx SMSC75xx device + * @v address EEPROM address + * @ret byte Byte read, or negative error + */ +static int smsc75xx_eeprom_read_byte ( struct smsc75xx_device *smsc75xx, + unsigned int address ) { + uint32_t e2p_cmd; + uint32_t e2p_data; + int rc; + + /* Wait for EEPROM to become idle */ + if ( ( rc = smsc75xx_eeprom_wait ( smsc75xx ) ) != 0 ) + return rc; + + /* Initiate read command */ + e2p_cmd = ( SMSC75XX_E2P_CMD_EPC_BSY | SMSC75XX_E2P_CMD_EPC_CMD_READ | + SMSC75XX_E2P_CMD_EPC_ADDR ( address ) ); + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_E2P_CMD, + e2p_cmd ) ) != 0 ) + return rc; + + /* Wait for command to complete */ + if ( ( rc = smsc75xx_eeprom_wait ( smsc75xx ) ) != 0 ) + return rc; + + /* Read EEPROM data */ + if ( ( rc = smsc75xx_readl ( smsc75xx, SMSC75XX_E2P_DATA, + &e2p_data ) ) != 0 ) + return rc; + + return SMSC75XX_E2P_DATA_GET ( e2p_data ); +} + +/** + * Read data from EEPROM + * + * @v smsc75xx SMSC75xx device + * @v address EEPROM address + * @v data Data buffer + * @v len Length of data + * @ret rc Return status code + */ +static int smsc75xx_eeprom_read ( struct smsc75xx_device *smsc75xx, + unsigned int address, void *data, + size_t len ) { + uint8_t *bytes; + int byte; + + /* Read bytes */ + for ( bytes = data ; len-- ; address++, bytes++ ) { + byte = smsc75xx_eeprom_read_byte ( smsc75xx, address ); + if ( byte < 0 ) + return byte; + *bytes = byte; + } + + return 0; +} + +/****************************************************************************** + * + * MII access + * + ****************************************************************************** + */ + +/** + * Wait for MII to become idle + * + * @v smsc75xx SMSC75xx device + * @ret rc Return status code + */ +static int smsc75xx_mii_wait ( struct smsc75xx_device *smsc75xx ) { + uint32_t mii_access; + unsigned int i; + int rc; + + /* Wait for MIIBZY to become clear */ + for ( i = 0 ; i < SMSC75XX_MII_MAX_WAIT_MS ; i++ ) { + + /* Read MII_ACCESS and check MIIBZY */ + if ( ( rc = smsc75xx_readl ( smsc75xx, SMSC75XX_MII_ACCESS, + &mii_access ) ) != 0 ) + return rc; + if ( ! ( mii_access & SMSC75XX_MII_ACCESS_MIIBZY ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( smsc75xx, "SMSC75XX %p timed out waiting for MII\n", + smsc75xx ); + return -ETIMEDOUT; +} + +/** + * Read from MII register + * + * @v mii MII interface + * @v reg Register address + * @ret value Data read, or negative error + */ +static int smsc75xx_mii_read ( struct mii_interface *mii, unsigned int reg ) { + struct smsc75xx_device *smsc75xx = + container_of ( mii, struct smsc75xx_device, mii ); + uint32_t mii_access; + uint32_t mii_data; + int rc; + + /* Wait for MII to become idle */ + if ( ( rc = smsc75xx_mii_wait ( smsc75xx ) ) != 0 ) + return rc; + + /* Initiate read command */ + mii_access = ( SMSC75XX_MII_ACCESS_PHY_ADDRESS | + SMSC75XX_MII_ACCESS_MIIRINDA ( reg ) | + SMSC75XX_MII_ACCESS_MIIBZY ); + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_MII_ACCESS, + mii_access ) ) != 0 ) + return rc; + + /* Wait for command to complete */ + if ( ( rc = smsc75xx_mii_wait ( smsc75xx ) ) != 0 ) + return rc; + + /* Read MII data */ + if ( ( rc = smsc75xx_readl ( smsc75xx, SMSC75XX_MII_DATA, + &mii_data ) ) != 0 ) + return rc; + + return SMSC75XX_MII_DATA_GET ( mii_data ); +} + +/** + * Write to MII register + * + * @v mii MII interface + * @v reg Register address + * @v data Data to write + * @ret rc Return status code + */ +static int smsc75xx_mii_write ( struct mii_interface *mii, unsigned int reg, + unsigned int data ) { + struct smsc75xx_device *smsc75xx = + container_of ( mii, struct smsc75xx_device, mii ); + uint32_t mii_access; + uint32_t mii_data; + int rc; + + /* Wait for MII to become idle */ + if ( ( rc = smsc75xx_mii_wait ( smsc75xx ) ) != 0 ) + return rc; + + /* Write MII data */ + mii_data = SMSC75XX_MII_DATA_SET ( data ); + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_MII_DATA, + mii_data ) ) != 0 ) + return rc; + + /* Initiate write command */ + mii_access = ( SMSC75XX_MII_ACCESS_PHY_ADDRESS | + SMSC75XX_MII_ACCESS_MIIRINDA ( reg ) | + SMSC75XX_MII_ACCESS_MIIWNR | + SMSC75XX_MII_ACCESS_MIIBZY ); + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_MII_ACCESS, + mii_access ) ) != 0 ) + return rc; + + /* Wait for command to complete */ + if ( ( rc = smsc75xx_mii_wait ( smsc75xx ) ) != 0 ) + return rc; + + return 0; +} + +/** MII operations */ +static struct mii_operations smsc75xx_mii_operations = { + .read = smsc75xx_mii_read, + .write = smsc75xx_mii_write, +}; + +/** + * Check link status + * + * @v smsc75xx SMSC75xx device + * @ret rc Return status code + */ +static int smsc75xx_check_link ( struct smsc75xx_device *smsc75xx ) { + struct net_device *netdev = smsc75xx->netdev; + int intr; + int rc; + + /* Read PHY interrupt source */ + intr = mii_read ( &smsc75xx->mii, SMSC75XX_MII_PHY_INTR_SOURCE ); + if ( intr < 0 ) { + rc = intr; + DBGC ( smsc75xx, "SMSC75XX %p could not get PHY interrupt " + "source: %s\n", smsc75xx, strerror ( rc ) ); + return rc; + } + + /* Acknowledge PHY interrupt */ + if ( ( rc = mii_write ( &smsc75xx->mii, SMSC75XX_MII_PHY_INTR_SOURCE, + intr ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not acknowledge PHY " + "interrupt: %s\n", smsc75xx, strerror ( rc ) ); + return rc; + } + + /* Check link status */ + if ( ( rc = mii_check_link ( &smsc75xx->mii, netdev ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not check link: %s\n", + smsc75xx, strerror ( rc ) ); + return rc; + } + + DBGC ( smsc75xx, "SMSC75XX %p link %s (intr %#04x)\n", + smsc75xx, ( netdev_link_ok ( netdev ) ? "up" : "down" ), intr ); + return 0; +} + +/****************************************************************************** + * + * Statistics (for debugging) + * + ****************************************************************************** + */ + +/** + * Get statistics + * + * @v smsc75xx SMSC75xx device + * @v stats Statistics to fill in + * @ret rc Return status code + */ +static int smsc75xx_get_statistics ( struct smsc75xx_device *smsc75xx, + struct smsc75xx_statistics *stats ) { + int rc; + + /* Get statistics */ + if ( ( rc = usb_control ( smsc75xx->usb, SMSC75XX_GET_STATISTICS, 0, 0, + stats, sizeof ( *stats ) ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not get statistics: %s\n", + smsc75xx, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Dump statistics (for debugging) + * + * @v smsc75xx SMSC75xx device + * @ret rc Return status code + */ +static int smsc75xx_dump_statistics ( struct smsc75xx_device *smsc75xx ) { + struct smsc75xx_statistics stats; + int rc; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_LOG ) + return 0; + + /* Get statistics */ + if ( ( rc = smsc75xx_get_statistics ( smsc75xx, &stats ) ) != 0 ) + return rc; + + /* Dump statistics */ + DBGC ( smsc75xx, "SMSC75XX %p RXE fcs %d aln %d frg %d jab %d und %d " + "ovr %d drp %d\n", smsc75xx, le32_to_cpu ( stats.rx.err.fcs ), + le32_to_cpu ( stats.rx.err.alignment ), + le32_to_cpu ( stats.rx.err.fragment ), + le32_to_cpu ( stats.rx.err.jabber ), + le32_to_cpu ( stats.rx.err.undersize ), + le32_to_cpu ( stats.rx.err.oversize ), + le32_to_cpu ( stats.rx.err.dropped ) ); + DBGC ( smsc75xx, "SMSC75XX %p RXB ucast %d bcast %d mcast %d\n", + smsc75xx, le32_to_cpu ( stats.rx.byte.unicast ), + le32_to_cpu ( stats.rx.byte.broadcast ), + le32_to_cpu ( stats.rx.byte.multicast ) ); + DBGC ( smsc75xx, "SMSC75XX %p RXF ucast %d bcast %d mcast %d pause " + "%d\n", smsc75xx, le32_to_cpu ( stats.rx.frame.unicast ), + le32_to_cpu ( stats.rx.frame.broadcast ), + le32_to_cpu ( stats.rx.frame.multicast ), + le32_to_cpu ( stats.rx.frame.pause ) ); + DBGC ( smsc75xx, "SMSC75XX %p TXE fcs %d def %d car %d cnt %d sgl %d " + "mul %d exc %d lat %d\n", smsc75xx, + le32_to_cpu ( stats.tx.err.fcs ), + le32_to_cpu ( stats.tx.err.deferral ), + le32_to_cpu ( stats.tx.err.carrier ), + le32_to_cpu ( stats.tx.err.count ), + le32_to_cpu ( stats.tx.err.single ), + le32_to_cpu ( stats.tx.err.multiple ), + le32_to_cpu ( stats.tx.err.excessive ), + le32_to_cpu ( stats.tx.err.late ) ); + DBGC ( smsc75xx, "SMSC75XX %p TXB ucast %d bcast %d mcast %d\n", + smsc75xx, le32_to_cpu ( stats.tx.byte.unicast ), + le32_to_cpu ( stats.tx.byte.broadcast ), + le32_to_cpu ( stats.tx.byte.multicast ) ); + DBGC ( smsc75xx, "SMSC75XX %p TXF ucast %d bcast %d mcast %d pause " + "%d\n", smsc75xx, le32_to_cpu ( stats.tx.frame.unicast ), + le32_to_cpu ( stats.tx.frame.broadcast ), + le32_to_cpu ( stats.tx.frame.multicast ), + le32_to_cpu ( stats.tx.frame.pause ) ); + + return 0; +} + +/****************************************************************************** + * + * Device reset + * + ****************************************************************************** + */ + +/** + * Reset device + * + * @v smsc75xx SMSC75xx device + * @ret rc Return status code + */ +static int smsc75xx_reset ( struct smsc75xx_device *smsc75xx ) { + uint32_t hw_cfg; + int rc; + + /* Reset device */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_HW_CFG, + SMSC75XX_HW_CFG_LRST ) ) != 0 ) + return rc; + + /* Wait for reset to complete */ + udelay ( SMSC75XX_RESET_DELAY_US ); + + /* Check that reset has completed */ + if ( ( rc = smsc75xx_readl ( smsc75xx, SMSC75XX_HW_CFG, + &hw_cfg ) ) != 0 ) + return rc; + if ( hw_cfg & SMSC75XX_HW_CFG_LRST ) { + DBGC ( smsc75xx, "SMSC75XX %p failed to reset\n", smsc75xx ); + return -ETIMEDOUT; + } + + return 0; +} + +/****************************************************************************** + * + * Endpoint operations + * + ****************************************************************************** + */ + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void smsc75xx_intr_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct smsc75xx_device *smsc75xx = + container_of ( ep, struct smsc75xx_device, usbnet.intr ); + struct net_device *netdev = smsc75xx->netdev; + struct smsc75xx_interrupt *intr; + + /* Profile completions */ + profile_start ( &smsc75xx_intr_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto done; + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p interrupt failed: %s\n", + smsc75xx, strerror ( rc ) ); + DBGC_HDA ( smsc75xx, 0, iobuf->data, iob_len ( iobuf ) ); + netdev_rx_err ( netdev, NULL, rc ); + goto done; + } + + /* Extract interrupt data */ + if ( iob_len ( iobuf ) != sizeof ( *intr ) ) { + DBGC ( smsc75xx, "SMSC75XX %p malformed interrupt\n", + smsc75xx ); + DBGC_HDA ( smsc75xx, 0, iobuf->data, iob_len ( iobuf ) ); + netdev_rx_err ( netdev, NULL, rc ); + goto done; + } + intr = iobuf->data; + + /* Record interrupt status */ + smsc75xx->int_sts = le32_to_cpu ( intr->int_sts ); + profile_stop ( &smsc75xx_intr_profiler ); + + done: + /* Free I/O buffer */ + free_iob ( iobuf ); +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations smsc75xx_intr_operations = { + .complete = smsc75xx_intr_complete, +}; + +/** + * Complete bulk IN transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void smsc75xx_in_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct smsc75xx_device *smsc75xx = + container_of ( ep, struct smsc75xx_device, usbnet.in ); + struct net_device *netdev = smsc75xx->netdev; + struct smsc75xx_rx_header *header; + + /* Profile completions */ + profile_start ( &smsc75xx_in_profiler ); + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) { + free_iob ( iobuf ); + return; + } + + /* Record USB errors against the network device */ + if ( rc != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p bulk IN failed: %s\n", + smsc75xx, strerror ( rc ) ); + goto err; + } + + /* Sanity check */ + if ( iob_len ( iobuf ) < ( sizeof ( *header ) ) ) { + DBGC ( smsc75xx, "SMSC75XX %p underlength bulk IN\n", + smsc75xx ); + DBGC_HDA ( smsc75xx, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto err; + } + + /* Strip header */ + header = iobuf->data; + iob_pull ( iobuf, sizeof ( *header ) ); + + /* Check for errors */ + if ( header->command & cpu_to_le32 ( SMSC75XX_RX_RED ) ) { + DBGC ( smsc75xx, "SMSC75XX %p receive error (%08x):\n", + smsc75xx, le32_to_cpu ( header->command ) ); + DBGC_HDA ( smsc75xx, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EIO; + goto err; + } + + /* Hand off to network stack */ + netdev_rx ( netdev, iob_disown ( iobuf ) ); + + profile_stop ( &smsc75xx_in_profiler ); + return; + + err: + /* Hand off to network stack */ + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); +} + +/** Bulk IN endpoint operations */ +static struct usb_endpoint_driver_operations smsc75xx_in_operations = { + .complete = smsc75xx_in_complete, +}; + +/** + * Transmit packet + * + * @v smsc75xx SMSC75xx device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int smsc75xx_out_transmit ( struct smsc75xx_device *smsc75xx, + struct io_buffer *iobuf ) { + struct smsc75xx_tx_header *header; + size_t len = iob_len ( iobuf ); + int rc; + + /* Profile transmissions */ + profile_start ( &smsc75xx_out_profiler ); + + /* Prepend header */ + if ( ( rc = iob_ensure_headroom ( iobuf, sizeof ( *header ) ) ) != 0 ) + return rc; + header = iob_push ( iobuf, sizeof ( *header ) ); + header->command = cpu_to_le32 ( SMSC75XX_TX_FCS | len ); + header->tag = 0; + header->mss = 0; + + /* Enqueue I/O buffer */ + if ( ( rc = usb_stream ( &smsc75xx->usbnet.out, iobuf, 0 ) ) != 0 ) + return rc; + + profile_stop ( &smsc75xx_out_profiler ); + return 0; +} + +/** + * Complete bulk OUT transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void smsc75xx_out_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct smsc75xx_device *smsc75xx = + container_of ( ep, struct smsc75xx_device, usbnet.out ); + struct net_device *netdev = smsc75xx->netdev; + + /* Report TX completion */ + netdev_tx_complete_err ( netdev, iobuf, rc ); +} + +/** Bulk OUT endpoint operations */ +static struct usb_endpoint_driver_operations smsc75xx_out_operations = { + .complete = smsc75xx_out_complete, +}; + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int smsc75xx_open ( struct net_device *netdev ) { + struct smsc75xx_device *smsc75xx = netdev->priv; + union smsc75xx_mac mac; + int rc; + + /* Clear stored interrupt status */ + smsc75xx->int_sts = 0; + + /* Copy MAC address */ + memset ( &mac, 0, sizeof ( mac ) ); + memcpy ( mac.raw, netdev->ll_addr, ETH_ALEN ); + + /* Configure bulk IN empty response */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_HW_CFG, + SMSC75XX_HW_CFG_BIR ) ) != 0 ) + goto err_hw_cfg; + + /* Open USB network device */ + if ( ( rc = usbnet_open ( &smsc75xx->usbnet ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not open: %s\n", + smsc75xx, strerror ( rc ) ); + goto err_open; + } + + /* Configure interrupt endpoint */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_INT_EP_CTL, + ( SMSC75XX_INT_EP_CTL_RDFO_EN | + SMSC75XX_INT_EP_CTL_PHY_EN ) ) ) != 0 ) + goto err_int_ep_ctl; + + /* Configure bulk IN delay */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_BULK_IN_DLY, + SMSC75XX_BULK_IN_DLY_SET ( 0 ) ) ) != 0 ) + goto err_bulk_in_dly; + + /* Configure receive filters */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_RFE_CTL, + ( SMSC75XX_RFE_CTL_AB | + SMSC75XX_RFE_CTL_AM | + SMSC75XX_RFE_CTL_AU ) ) ) != 0 ) + goto err_rfe_ctl; + + /* Configure receive FIFO */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_FCT_RX_CTL, + ( SMSC75XX_FCT_RX_CTL_EN | + SMSC75XX_FCT_RX_CTL_BAD ) ) ) != 0 ) + goto err_fct_rx_ctl; + + /* Configure transmit FIFO */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_FCT_TX_CTL, + SMSC75XX_FCT_TX_CTL_EN ) ) != 0 ) + goto err_fct_tx_ctl; + + /* Configure receive datapath */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_MAC_RX, + ( SMSC75XX_MAC_RX_MAX_SIZE_DEFAULT | + SMSC75XX_MAC_RX_FCS | + SMSC75XX_MAC_RX_EN ) ) ) != 0 ) + goto err_mac_rx; + + /* Configure transmit datapath */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_MAC_TX, + SMSC75XX_MAC_TX_EN ) ) != 0 ) + goto err_mac_tx; + + /* Write MAC address high register */ + if ( ( rc = smsc75xx_raw_writel ( smsc75xx, SMSC75XX_RX_ADDRH, + mac.addr.h ) ) != 0 ) + goto err_rx_addrh; + + /* Write MAC address low register */ + if ( ( rc = smsc75xx_raw_writel ( smsc75xx, SMSC75XX_RX_ADDRL, + mac.addr.l ) ) != 0 ) + goto err_rx_addrl; + + /* Write MAC address perfect filter high register */ + mac.addr.h |= cpu_to_le32 ( SMSC75XX_ADDR_FILTH_VALID ); + if ( ( rc = smsc75xx_raw_writel ( smsc75xx, SMSC75XX_ADDR_FILTH ( 0 ), + mac.addr.h ) ) != 0 ) + goto err_addr_filth; + + /* Write MAC address perfect filter low register */ + if ( ( rc = smsc75xx_raw_writel ( smsc75xx, SMSC75XX_ADDR_FILTL ( 0 ), + mac.addr.l ) ) != 0 ) + goto err_addr_filtl; + + /* Enable PHY interrupts */ + if ( ( rc = mii_write ( &smsc75xx->mii, SMSC75XX_MII_PHY_INTR_MASK, + ( SMSC75XX_PHY_INTR_ANEG_DONE | + SMSC75XX_PHY_INTR_LINK_DOWN ) ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not set PHY interrupt " + "mask: %s\n", smsc75xx, strerror ( rc ) ); + goto err_phy_intr_mask; + } + + /* Update link status */ + smsc75xx_check_link ( smsc75xx ); + + return 0; + + err_phy_intr_mask: + err_addr_filtl: + err_addr_filth: + err_rx_addrl: + err_rx_addrh: + err_mac_tx: + err_mac_rx: + err_fct_tx_ctl: + err_fct_rx_ctl: + err_rfe_ctl: + err_bulk_in_dly: + err_int_ep_ctl: + usbnet_close ( &smsc75xx->usbnet ); + err_open: + err_hw_cfg: + smsc75xx_reset ( smsc75xx ); + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void smsc75xx_close ( struct net_device *netdev ) { + struct smsc75xx_device *smsc75xx = netdev->priv; + + /* Close USB network device */ + usbnet_close ( &smsc75xx->usbnet ); + + /* Dump statistics (for debugging) */ + smsc75xx_dump_statistics ( smsc75xx ); + + /* Reset device */ + smsc75xx_reset ( smsc75xx ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int smsc75xx_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct smsc75xx_device *smsc75xx = netdev->priv; + int rc; + + /* Transmit packet */ + if ( ( rc = smsc75xx_out_transmit ( smsc75xx, iobuf ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void smsc75xx_poll ( struct net_device *netdev ) { + struct smsc75xx_device *smsc75xx = netdev->priv; + uint32_t int_sts; + int rc; + + /* Poll USB bus */ + usb_poll ( smsc75xx->bus ); + + /* Refill endpoints */ + if ( ( rc = usbnet_refill ( &smsc75xx->usbnet ) ) != 0 ) + netdev_rx_err ( netdev, NULL, rc ); + + /* Do nothing more unless there are interrupts to handle */ + int_sts = smsc75xx->int_sts; + if ( ! int_sts ) + return; + + /* Check link status if applicable */ + if ( int_sts & SMSC75XX_INT_STS_PHY_INT ) { + smsc75xx_check_link ( smsc75xx ); + int_sts &= ~SMSC75XX_INT_STS_PHY_INT; + } + + /* Record RX FIFO overflow if applicable */ + if ( int_sts & SMSC75XX_INT_STS_RDFO_INT ) { + DBGC2 ( smsc75xx, "SMSC75XX %p RX FIFO overflowed\n", + smsc75xx ); + netdev_rx_err ( netdev, NULL, -ENOBUFS ); + int_sts &= ~SMSC75XX_INT_STS_RDFO_INT; + } + + /* Check for unexpected interrupts */ + if ( int_sts ) { + DBGC ( smsc75xx, "SMSC75XX %p unexpected interrupt %#08x\n", + smsc75xx, int_sts ); + netdev_rx_err ( netdev, NULL, -ENOTTY ); + } + + /* Clear interrupts */ + if ( ( rc = smsc75xx_writel ( smsc75xx, SMSC75XX_INT_STS, + smsc75xx->int_sts ) ) != 0 ) + netdev_rx_err ( netdev, NULL, rc ); + smsc75xx->int_sts = 0; +} + +/** SMSC75xx network device operations */ +static struct net_device_operations smsc75xx_operations = { + .open = smsc75xx_open, + .close = smsc75xx_close, + .transmit = smsc75xx_transmit, + .poll = smsc75xx_poll, +}; + +/****************************************************************************** + * + * USB interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int smsc75xx_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct net_device *netdev; + struct smsc75xx_device *smsc75xx; + int rc; + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *smsc75xx ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &smsc75xx_operations ); + netdev->dev = &func->dev; + smsc75xx = netdev->priv; + memset ( smsc75xx, 0, sizeof ( *smsc75xx ) ); + smsc75xx->usb = usb; + smsc75xx->bus = usb->port->hub->bus; + smsc75xx->netdev = netdev; + usbnet_init ( &smsc75xx->usbnet, func, &smsc75xx_intr_operations, + &smsc75xx_in_operations, &smsc75xx_out_operations ); + usb_refill_init ( &smsc75xx->usbnet.intr, 0, SMSC75XX_INTR_MAX_FILL ); + usb_refill_init ( &smsc75xx->usbnet.in, SMSC75XX_IN_MTU, + SMSC75XX_IN_MAX_FILL ); + mii_init ( &smsc75xx->mii, &smsc75xx_mii_operations ); + DBGC ( smsc75xx, "SMSC75XX %p on %s\n", smsc75xx, func->name ); + + /* Describe USB network device */ + if ( ( rc = usbnet_describe ( &smsc75xx->usbnet, config ) ) != 0 ) { + DBGC ( smsc75xx, "SMSC75XX %p could not describe: %s\n", + smsc75xx, strerror ( rc ) ); + goto err_describe; + } + + /* Reset device */ + if ( ( rc = smsc75xx_reset ( smsc75xx ) ) != 0 ) + goto err_reset; + + /* Read MAC address */ + if ( ( rc = smsc75xx_eeprom_read ( smsc75xx, SMSC75XX_EEPROM_MAC, + netdev->hw_addr, ETH_ALEN ) ) != 0 ) + goto err_eeprom_read; + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + usb_func_set_drvdata ( func, netdev ); + return 0; + + unregister_netdev ( netdev ); + err_register: + err_eeprom_read: + err_reset: + err_describe: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void smsc75xx_remove ( struct usb_function *func ) { + struct net_device *netdev = usb_func_get_drvdata ( func ); + + unregister_netdev ( netdev ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** SMSC75xx device IDs */ +static struct usb_device_id smsc75xx_ids[] = { + { + .name = "smsc7500", + .vendor = 0x0424, + .product = 0x7500, + .class = { 0xff, 0x00, 0xff }, + }, + { + .name = "smsc7505", + .vendor = 0x0424, + .product = 0x7505, + .class = { 0xff, 0x00, 0xff }, + }, +}; + +/** SMSC LAN75xx driver */ +struct usb_driver smsc75xx_driver __usb_driver = { + .ids = smsc75xx_ids, + .id_count = ( sizeof ( smsc75xx_ids ) / sizeof ( smsc75xx_ids[0] ) ), + .probe = smsc75xx_probe, + .remove = smsc75xx_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/smsc75xx.h b/qemu/roms/ipxe/src/drivers/net/smsc75xx.h new file mode 100644 index 000000000..2463b72a1 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/smsc75xx.h @@ -0,0 +1,309 @@ +#ifndef _SMSC75XX_H +#define _SMSC75XX_H + +/** @file + * + * SMSC LAN75xx USB Ethernet driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> +#include <ipxe/if_ether.h> +#include <ipxe/mii.h> + +/** Register write command */ +#define SMSC75XX_REGISTER_WRITE \ + ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0xa0 ) ) + +/** Register read command */ +#define SMSC75XX_REGISTER_READ \ + ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0xa1 ) ) + +/** Get statistics command */ +#define SMSC75XX_GET_STATISTICS \ + ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 0xa2 ) ) + +/** Interrupt status register */ +#define SMSC75XX_INT_STS 0x00c +#define SMSC75XX_INT_STS_RDFO_INT 0x00400000UL /**< RX FIFO overflow */ +#define SMSC75XX_INT_STS_PHY_INT 0x00020000UL /**< PHY interrupt */ + +/** Hardware configuration register */ +#define SMSC75XX_HW_CFG 0x010 +#define SMSC75XX_HW_CFG_BIR 0x00000080UL /**< Bulk IN use NAK */ +#define SMSC75XX_HW_CFG_LRST 0x00000002UL /**< Soft lite reset */ + +/** Interrupt endpoint control register */ +#define SMSC75XX_INT_EP_CTL 0x038 +#define SMSC75XX_INT_EP_CTL_RDFO_EN 0x00400000UL /**< RX FIFO overflow */ +#define SMSC75XX_INT_EP_CTL_PHY_EN 0x00020000UL /**< PHY interrupt */ + +/** Bulk IN delay register */ +#define SMSC75XX_BULK_IN_DLY 0x03c +#define SMSC75XX_BULK_IN_DLY_SET(ticks) ( (ticks) << 0 ) /**< Delay / 16.7ns */ + +/** EEPROM command register */ +#define SMSC75XX_E2P_CMD 0x040 +#define SMSC75XX_E2P_CMD_EPC_BSY 0x80000000UL /**< EPC busy */ +#define SMSC75XX_E2P_CMD_EPC_CMD_READ 0x00000000UL /**< READ command */ +#define SMSC75XX_E2P_CMD_EPC_ADDR(addr) ( (addr) << 0 ) /**< EPC address */ + +/** EEPROM data register */ +#define SMSC75XX_E2P_DATA 0x044 +#define SMSC75XX_E2P_DATA_GET(e2p_data) \ + ( ( (e2p_data) >> 0 ) & 0xff ) /**< EEPROM data */ + +/** MAC address EEPROM address */ +#define SMSC75XX_EEPROM_MAC 0x01 + +/** Receive filtering engine control register */ +#define SMSC75XX_RFE_CTL 0x060 +#define SMSC75XX_RFE_CTL_AB 0x00000400UL /**< Accept broadcast */ +#define SMSC75XX_RFE_CTL_AM 0x00000200UL /**< Accept multicast */ +#define SMSC75XX_RFE_CTL_AU 0x00000100UL /**< Accept unicast */ + +/** FIFO controller RX FIFO control register */ +#define SMSC75XX_FCT_RX_CTL 0x090 +#define SMSC75XX_FCT_RX_CTL_EN 0x80000000UL /**< FCT RX enable */ +#define SMSC75XX_FCT_RX_CTL_BAD 0x02000000UL /**< Store bad frames */ + +/** FIFO controller TX FIFO control register */ +#define SMSC75XX_FCT_TX_CTL 0x094 +#define SMSC75XX_FCT_TX_CTL_EN 0x80000000UL /**< FCT TX enable */ + +/** MAC receive register */ +#define SMSC75XX_MAC_RX 0x104 +#define SMSC75XX_MAC_RX_MAX_SIZE(mtu) ( (mtu) << 16 ) /**< Max frame size */ +#define SMSC75XX_MAC_RX_MAX_SIZE_DEFAULT \ + SMSC75XX_MAC_RX_MAX_SIZE ( ETH_FRAME_LEN + 4 /* VLAN */ + 4 /* CRC */ ) +#define SMSC75XX_MAC_RX_FCS 0x00000010UL /**< FCS stripping */ +#define SMSC75XX_MAC_RX_EN 0x00000001UL /**< RX enable */ + +/** MAC transmit register */ +#define SMSC75XX_MAC_TX 0x108 +#define SMSC75XX_MAC_TX_EN 0x00000001UL /**< TX enable */ + +/** MAC receive address high register */ +#define SMSC75XX_RX_ADDRH 0x118 + +/** MAC receive address low register */ +#define SMSC75XX_RX_ADDRL 0x11c + +/** MII access register */ +#define SMSC75XX_MII_ACCESS 0x120 +#define SMSC75XX_MII_ACCESS_PHY_ADDRESS 0x00000800UL /**< PHY address */ +#define SMSC75XX_MII_ACCESS_MIIRINDA(addr) ( (addr) << 6 ) /**< MII register */ +#define SMSC75XX_MII_ACCESS_MIIWNR 0x00000002UL /**< MII write */ +#define SMSC75XX_MII_ACCESS_MIIBZY 0x00000001UL /**< MII busy */ + +/** MII data register */ +#define SMSC75XX_MII_DATA 0x124 +#define SMSC75XX_MII_DATA_SET(data) ( (data) << 0 ) /**< Set data */ +#define SMSC75XX_MII_DATA_GET(mii_data) \ + ( ( (mii_data) >> 0 ) & 0xffff ) /**< Get data */ + +/** PHY interrupt source MII register */ +#define SMSC75XX_MII_PHY_INTR_SOURCE 29 + +/** PHY interrupt mask MII register */ +#define SMSC75XX_MII_PHY_INTR_MASK 30 + +/** PHY interrupt: auto-negotiation complete */ +#define SMSC75XX_PHY_INTR_ANEG_DONE 0x0040 + +/** PHY interrupt: link down */ +#define SMSC75XX_PHY_INTR_LINK_DOWN 0x0010 + +/** MAC address perfect filter N high register */ +#define SMSC75XX_ADDR_FILTH(n) ( 0x300 + ( 8 * (n) ) ) +#define SMSC75XX_ADDR_FILTH_VALID 0x80000000UL /**< Address valid */ + +/** MAC address perfect filter N low register */ +#define SMSC75XX_ADDR_FILTL(n) ( 0x304 + ( 8 * (n) ) ) + +/** MAC address */ +union smsc75xx_mac { + /** MAC receive address registers */ + struct { + /** MAC receive address low register */ + uint32_t l; + /** MAC receive address high register */ + uint32_t h; + } __attribute__ (( packed )) addr; + /** Raw MAC address */ + uint8_t raw[ETH_ALEN]; +}; + +/** Receive packet header */ +struct smsc75xx_rx_header { + /** RX command word */ + uint32_t command; + /** VLAN tag */ + uint16_t vtag; + /** Checksum */ + uint16_t csum; + /** Two-byte padding used to align Ethernet payload */ + uint16_t pad; +} __attribute__ (( packed )); + +/** Receive error detected */ +#define SMSC75XX_RX_RED 0x00400000UL + +/** Transmit packet header */ +struct smsc75xx_tx_header { + /** TX command word */ + uint32_t command; + /** VLAN tag */ + uint16_t tag; + /** Maximum segment size */ + uint16_t mss; +} __attribute__ (( packed )); + +/** Insert frame checksum and pad */ +#define SMSC75XX_TX_FCS 0x00400000UL + +/** Interrupt packet format */ +struct smsc75xx_interrupt { + /** Current value of INT_STS register */ + uint32_t int_sts; +} __attribute__ (( packed )); + +/** Byte count statistics */ +struct smsc75xx_byte_statistics { + /** Unicast byte count */ + uint32_t unicast; + /** Broadcast byte count */ + uint32_t broadcast; + /** Multicast byte count */ + uint32_t multicast; +} __attribute__ (( packed )); + +/** Frame count statistics */ +struct smsc75xx_frame_statistics { + /** Unicast frames */ + uint32_t unicast; + /** Broadcast frames */ + uint32_t broadcast; + /** Multicast frames */ + uint32_t multicast; + /** Pause frames */ + uint32_t pause; + /** Frames by length category */ + uint32_t len[7]; +} __attribute__ (( packed )); + +/** Receive error statistics */ +struct smsc75xx_rx_error_statistics { + /** FCS errors */ + uint32_t fcs; + /** Alignment errors */ + uint32_t alignment; + /** Fragment errors */ + uint32_t fragment; + /** Jabber errors */ + uint32_t jabber; + /** Undersize frame errors */ + uint32_t undersize; + /** Oversize frame errors */ + uint32_t oversize; + /** Dropped frame errors */ + uint32_t dropped; +} __attribute__ (( packed )); + +/** Receive statistics */ +struct smsc75xx_rx_statistics { + /** Error statistics */ + struct smsc75xx_rx_error_statistics err; + /** Byte count statistics */ + struct smsc75xx_byte_statistics byte; + /** Frame count statistics */ + struct smsc75xx_frame_statistics frame; +} __attribute__ (( packed )); + +/** Transmit error statistics */ +struct smsc75xx_tx_error_statistics { + /** FCS errors */ + uint32_t fcs; + /** Excess deferral errors */ + uint32_t deferral; + /** Carrier errors */ + uint32_t carrier; + /** Bad byte count */ + uint32_t count; + /** Single collisions */ + uint32_t single; + /** Multiple collisions */ + uint32_t multiple; + /** Excession collisions */ + uint32_t excessive; + /** Late collisions */ + uint32_t late; +} __attribute__ (( packed )); + +/** Transmit statistics */ +struct smsc75xx_tx_statistics { + /** Error statistics */ + struct smsc75xx_tx_error_statistics err; + /** Byte count statistics */ + struct smsc75xx_byte_statistics byte; + /** Frame count statistics */ + struct smsc75xx_frame_statistics frame; +} __attribute__ (( packed )); + +/** Statistics */ +struct smsc75xx_statistics { + /** Receive statistics */ + struct smsc75xx_rx_statistics rx; + /** Transmit statistics */ + struct smsc75xx_tx_statistics tx; +} __attribute__ (( packed )); + +/** A SMSC75xx network device */ +struct smsc75xx_device { + /** USB device */ + struct usb_device *usb; + /** USB bus */ + struct usb_bus *bus; + /** Network device */ + struct net_device *netdev; + /** USB network device */ + struct usbnet_device usbnet; + /** MII interface */ + struct mii_interface mii; + /** Interrupt status */ + uint32_t int_sts; +}; + +/** Reset delay (in microseconds) */ +#define SMSC75XX_RESET_DELAY_US 2 + +/** Maximum time to wait for EEPROM (in milliseconds) */ +#define SMSC75XX_EEPROM_MAX_WAIT_MS 100 + +/** Maximum time to wait for MII (in milliseconds) */ +#define SMSC75XX_MII_MAX_WAIT_MS 100 + +/** Interrupt maximum fill level + * + * This is a policy decision. + */ +#define SMSC75XX_INTR_MAX_FILL 2 + +/** Bulk IN maximum fill level + * + * This is a policy decision. + */ +#define SMSC75XX_IN_MAX_FILL 8 + +/** Bulk IN buffer size */ +#define SMSC75XX_IN_MTU \ + ( sizeof ( struct smsc75xx_rx_header ) + \ + ETH_FRAME_LEN + 4 /* possible VLAN header */ ) + +#endif /* _SMSC75XX_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/sundance.c b/qemu/roms/ipxe/src/drivers/net/sundance.c index eef7c9c7c..9127fa2cd 100644 --- a/qemu/roms/ipxe/src/drivers/net/sundance.c +++ b/qemu/roms/ipxe/src/drivers/net/sundance.c @@ -601,7 +601,7 @@ static int sundance_probe ( struct nic *nic, struct pci_device *pci ) { sdc->nic_name = pci->id->name; sdc->mtu = mtu; - pci_read_config_byte(pci, PCI_REVISION_ID, &sdc->pci_rev_id); + pci_read_config_byte(pci, PCI_REVISION, &sdc->pci_rev_id); DBG ( "Device revision id: %hx\n", sdc->pci_rev_id ); diff --git a/qemu/roms/ipxe/src/drivers/net/tg3/tg3.c b/qemu/roms/ipxe/src/drivers/net/tg3/tg3.c index 32ca1609c..42bfa2d99 100644 --- a/qemu/roms/ipxe/src/drivers/net/tg3/tg3.c +++ b/qemu/roms/ipxe/src/drivers/net/tg3/tg3.c @@ -928,6 +928,7 @@ static struct pci_device_id tg3_nics[] = { PCI_ROM(0x14e4, 0x16b6, "14e4-16b6", "14e4-16b6", 0), PCI_ROM(0x14e4, 0x1657, "14e4-1657", "14e4-1657", 0), PCI_ROM(0x14e4, 0x165f, "14e4-165f", "14e4-165f", 0), + PCI_ROM(0x14e4, 0x1686, "14e4-1686", "14e4-1686", 0), PCI_ROM(0x1148, 0x4400, "1148-4400", "1148-4400", 0), PCI_ROM(0x1148, 0x4500, "1148-4500", "1148-4500", 0), PCI_ROM(0x173b, 0x03e8, "173b-03e8", "173b-03e8", 0), diff --git a/qemu/roms/ipxe/src/drivers/net/tg3/tg3.h b/qemu/roms/ipxe/src/drivers/net/tg3/tg3.h index 660368394..2b85b065b 100644 --- a/qemu/roms/ipxe/src/drivers/net/tg3/tg3.h +++ b/qemu/roms/ipxe/src/drivers/net/tg3/tg3.h @@ -131,6 +131,10 @@ #define PCI_DEVICE_ID_TIGON3_5901_2 0x170e #define PCI_DEVICE_ID_TIGON3_5906 0x1712 #define PCI_DEVICE_ID_TIGON3_5906M 0x1713 +#define PCI_VENDOR_ID_COMPAQ 0x0e11 +#define PCI_VENDOR_ID_IBM 0x1014 +#define PCI_VENDOR_ID_DELL 0x1028 +#define PCI_VENDOR_ID_3COM 0x10b7 /* </pci_ids.h> */ #define SPEED_10 10 @@ -185,6 +189,7 @@ #define TG3PCI_DEVICE_TIGON3_57761 0x16b0 #define TG3PCI_DEVICE_TIGON3_57762 0x1682 #define TG3PCI_DEVICE_TIGON3_57765 0x16b4 +#define TG3PCI_DEVICE_TIGON3_57766 0x1686 #define TG3PCI_DEVICE_TIGON3_57791 0x16b2 #define TG3PCI_DEVICE_TIGON3_57795 0x16b6 #define TG3PCI_DEVICE_TIGON3_5719 0x1657 diff --git a/qemu/roms/ipxe/src/drivers/net/tg3/tg3_hw.c b/qemu/roms/ipxe/src/drivers/net/tg3/tg3_hw.c index 3a481aba3..50353cf36 100644 --- a/qemu/roms/ipxe/src/drivers/net/tg3/tg3_hw.c +++ b/qemu/roms/ipxe/src/drivers/net/tg3/tg3_hw.c @@ -436,6 +436,7 @@ int tg3_get_invariants(struct tg3 *tp) tp->pdev->device == TG3PCI_DEVICE_TIGON3_57761 || tp->pdev->device == TG3PCI_DEVICE_TIGON3_57762 || tp->pdev->device == TG3PCI_DEVICE_TIGON3_57765 || + tp->pdev->device == TG3PCI_DEVICE_TIGON3_57766 || tp->pdev->device == TG3PCI_DEVICE_TIGON3_57791 || tp->pdev->device == TG3PCI_DEVICE_TIGON3_57795) pci_read_config_dword(tp->pdev, diff --git a/qemu/roms/ipxe/src/drivers/net/virtio-net.c b/qemu/roms/ipxe/src/drivers/net/virtio-net.c index d5fd81979..533ccb0c6 100644 --- a/qemu/roms/ipxe/src/drivers/net/virtio-net.c +++ b/qemu/roms/ipxe/src/drivers/net/virtio-net.c @@ -20,7 +20,7 @@ * See the COPYING file in the top-level directory. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <errno.h> #include <stdlib.h> @@ -131,8 +131,8 @@ static void virtnet_enqueue_iob ( struct net_device *netdev, }, }; - DBGC ( virtnet, "VIRTIO-NET %p enqueuing iobuf %p on vq %d\n", - virtnet, iobuf, vq_idx ); + DBGC2 ( virtnet, "VIRTIO-NET %p enqueuing iobuf %p on vq %d\n", + virtnet, iobuf, vq_idx ); vring_add_buf ( vq, list, out, in, iobuf, 0 ); vring_kick ( virtnet->ioaddr, vq, 1 ); @@ -256,8 +256,8 @@ static void virtnet_process_tx_packets ( struct net_device *netdev ) { while ( vring_more_used ( tx_vq ) ) { struct io_buffer *iobuf = vring_get_buf ( tx_vq, NULL ); - DBGC ( virtnet, "VIRTIO-NET %p tx complete iobuf %p\n", - virtnet, iobuf ); + DBGC2 ( virtnet, "VIRTIO-NET %p tx complete iobuf %p\n", + virtnet, iobuf ); netdev_tx_complete ( netdev, iobuf ); } @@ -283,8 +283,8 @@ static void virtnet_process_rx_packets ( struct net_device *netdev ) { iob_unput ( iobuf, RX_BUF_SIZE ); iob_put ( iobuf, len - sizeof ( struct virtio_net_hdr ) ); - DBGC ( virtnet, "VIRTIO-NET %p rx complete iobuf %p len %zd\n", - virtnet, iobuf, iob_len ( iobuf ) ); + DBGC2 ( virtnet, "VIRTIO-NET %p rx complete iobuf %p len %zd\n", + virtnet, iobuf, iob_len ( iobuf ) ); /* Pass completed packet to the network stack */ netdev_rx ( netdev, iobuf ); diff --git a/qemu/roms/ipxe/src/drivers/net/vmxnet3.c b/qemu/roms/ipxe/src/drivers/net/vmxnet3.c index 31082bf6f..8d4f4b843 100644 --- a/qemu/roms/ipxe/src/drivers/net/vmxnet3.c +++ b/qemu/roms/ipxe/src/drivers/net/vmxnet3.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/drivers/net/vmxnet3.h b/qemu/roms/ipxe/src/drivers/net/vmxnet3.h index db313d4b8..a1671d9dd 100644 --- a/qemu/roms/ipxe/src/drivers/net/vmxnet3.h +++ b/qemu/roms/ipxe/src/drivers/net/vmxnet3.h @@ -18,9 +18,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/drivers/net/vxge/vxge.c b/qemu/roms/ipxe/src/drivers/net/vxge/vxge.c index bf20ec43c..d50ac05b5 100644 --- a/qemu/roms/ipxe/src/drivers/net/vxge/vxge.c +++ b/qemu/roms/ipxe/src/drivers/net/vxge/vxge.c @@ -5,10 +5,11 @@ * as "vxge" even though the code is in vxge_* named files. */ -FILE_LICENCE(GPL2_OR_LATER); +FILE_LICENCE(GPL2_OR_LATER_OR_UBDL); #include <ipxe/pci.h> +PROVIDE_REQUIRING_SYMBOL(); REQUIRE_OBJECT(vxge_main); /** vxge PCI IDs for util/parserom.pl which are put into bin/NIC */ diff --git a/qemu/roms/ipxe/src/drivers/net/vxge/vxge_main.c b/qemu/roms/ipxe/src/drivers/net/vxge/vxge_main.c index 130eab617..8b099c0e2 100644 --- a/qemu/roms/ipxe/src/drivers/net/vxge/vxge_main.c +++ b/qemu/roms/ipxe/src/drivers/net/vxge/vxge_main.c @@ -509,7 +509,7 @@ vxge_probe(struct pci_device *pdev) vxge_debug(VXGE_INFO, "vxge_probe for device " PCI_FMT "\n", PCI_ARGS(pdev)); - pci_read_config_byte(pdev, PCI_REVISION_ID, &revision); + pci_read_config_byte(pdev, PCI_REVISION, &revision); titan1 = is_titan1(pdev->device, revision); mmio_start = pci_bar_start(pdev, PCI_BASE_ADDRESS_0); diff --git a/qemu/roms/ipxe/src/drivers/net/w89c840.c b/qemu/roms/ipxe/src/drivers/net/w89c840.c index ce638ab99..d8144a8ce 100644 --- a/qemu/roms/ipxe/src/drivers/net/w89c840.c +++ b/qemu/roms/ipxe/src/drivers/net/w89c840.c @@ -641,7 +641,9 @@ static int w89c840_probe ( struct nic *nic, struct pci_device *p ) { ioaddr = ioaddr & ~3; /* Mask the bit that says "this is an io addr" */ +#define PCI_VENDOR_ID_WINBOND2 0x1050 #define PCI_DEVICE_ID_WINBOND2_89C840 0x0840 +#define PCI_VENDOR_ID_COMPEX 0x11f6 #define PCI_DEVICE_ID_COMPEX_RL100ATX 0x2011 /* From Matt Hortman <mbhortman@acpthinclient.com> */ diff --git a/qemu/roms/ipxe/src/drivers/nvs/nvs.c b/qemu/roms/ipxe/src/drivers/nvs/nvs.c index ccb2145bd..af7c466c4 100644 --- a/qemu/roms/ipxe/src/drivers/nvs/nvs.c +++ b/qemu/roms/ipxe/src/drivers/nvs/nvs.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c b/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c index 33148d5b9..3e88531c7 100644 --- a/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c +++ b/qemu/roms/ipxe/src/drivers/nvs/nvsvpd.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdio.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/drivers/nvs/spi.c b/qemu/roms/ipxe/src/drivers/nvs/spi.c index 84613b9dd..dcfe1af91 100644 --- a/qemu/roms/ipxe/src/drivers/nvs/spi.c +++ b/qemu/roms/ipxe/src/drivers/nvs/spi.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/drivers/nvs/threewire.c b/qemu/roms/ipxe/src/drivers/nvs/threewire.c index 53f1ad8de..547f35382 100644 --- a/qemu/roms/ipxe/src/drivers/nvs/threewire.c +++ b/qemu/roms/ipxe/src/drivers/nvs/threewire.c @@ -15,9 +15,13 @@ * 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 ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/drivers/usb/ehci.c b/qemu/roms/ipxe/src/drivers/usb/ehci.c new file mode 100644 index 000000000..4124692a6 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/ehci.c @@ -0,0 +1,1994 @@ +/* + * Copyright (C) 2014 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 <unistd.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/malloc.h> +#include <ipxe/pci.h> +#include <ipxe/usb.h> +#include <ipxe/init.h> +#include "ehci.h" + +/** @file + * + * USB Enhanced Host Controller Interface (EHCI) driver + * + */ + +/** + * Construct error code from transfer descriptor status + * + * @v status Transfer descriptor status + * @ret rc Error code + * + * Bits 2-5 of the status code provide some indication as to the root + * cause of the error. We incorporate these into the error code as + * reported to usb_complete_err(). + */ +#define EIO_STATUS( status ) EUNIQ ( EINFO_EIO, ( ( (status) >> 2 ) & 0xf ) ) + +/****************************************************************************** + * + * Register access + * + ****************************************************************************** + */ + +/** + * Initialise device + * + * @v ehci EHCI device + * @v regs MMIO registers + */ +static void ehci_init ( struct ehci_device *ehci, void *regs ) { + uint32_t hcsparams; + uint32_t hccparams; + size_t caplength; + + /* Locate capability and operational registers */ + ehci->cap = regs; + caplength = readb ( ehci->cap + EHCI_CAP_CAPLENGTH ); + ehci->op = ( ehci->cap + caplength ); + DBGC2 ( ehci, "EHCI %s cap %08lx op %08lx\n", ehci->name, + virt_to_phys ( ehci->cap ), virt_to_phys ( ehci->op ) ); + + /* Read structural parameters */ + hcsparams = readl ( ehci->cap + EHCI_CAP_HCSPARAMS ); + ehci->ports = EHCI_HCSPARAMS_PORTS ( hcsparams ); + DBGC ( ehci, "EHCI %s has %d ports\n", ehci->name, ehci->ports ); + + /* Read capability parameters 1 */ + hccparams = readl ( ehci->cap + EHCI_CAP_HCCPARAMS ); + ehci->addr64 = EHCI_HCCPARAMS_ADDR64 ( hccparams ); + ehci->flsize = ( EHCI_HCCPARAMS_FLSIZE ( hccparams ) ? + EHCI_FLSIZE_SMALL : EHCI_FLSIZE_DEFAULT ); + ehci->eecp = EHCI_HCCPARAMS_EECP ( hccparams ); + DBGC2 ( ehci, "EHCI %s %d-bit flsize %d\n", ehci->name, + ( ehci->addr64 ? 64 : 32 ), ehci->flsize ); +} + +/** + * Find extended capability + * + * @v ehci EHCI device + * @v pci PCI device + * @v id Capability ID + * @v offset Offset to previous extended capability instance, or zero + * @ret offset Offset to extended capability, or zero if not found + */ +static unsigned int ehci_extended_capability ( struct ehci_device *ehci, + struct pci_device *pci, + unsigned int id, + unsigned int offset ) { + uint32_t eecp; + + /* Locate the extended capability */ + while ( 1 ) { + + /* Locate first or next capability as applicable */ + if ( offset ) { + pci_read_config_dword ( pci, offset, &eecp ); + offset = EHCI_EECP_NEXT ( eecp ); + } else { + offset = ehci->eecp; + } + if ( ! offset ) + return 0; + + /* Check if this is the requested capability */ + pci_read_config_dword ( pci, offset, &eecp ); + if ( EHCI_EECP_ID ( eecp ) == id ) + return offset; + } +} + +/** + * Calculate buffer alignment + * + * @v len Length + * @ret align Buffer alignment + * + * Determine alignment required for a buffer which must be aligned to + * at least EHCI_MIN_ALIGN and which must not cross a page boundary. + */ +static inline size_t ehci_align ( size_t len ) { + size_t align; + + /* Align to own length (rounded up to a power of two) */ + align = ( 1 << fls ( len - 1 ) ); + + /* Round up to EHCI_MIN_ALIGN if needed */ + if ( align < EHCI_MIN_ALIGN ) + align = EHCI_MIN_ALIGN; + + return align; +} + +/** + * Check control data structure reachability + * + * @v ehci EHCI device + * @v ptr Data structure pointer + * @ret rc Return status code + */ +static int ehci_ctrl_reachable ( struct ehci_device *ehci, void *ptr ) { + physaddr_t phys = virt_to_phys ( ptr ); + uint32_t segment; + + /* Always reachable in a 32-bit build */ + if ( sizeof ( physaddr_t ) <= sizeof ( uint32_t ) ) + return 0; + + /* Reachable only if control segment matches in a 64-bit build */ + segment = ( ( ( uint64_t ) phys ) >> 32 ); + if ( segment == ehci->ctrldssegment ) + return 0; + + return -ENOTSUP; +} + +/****************************************************************************** + * + * USB legacy support + * + ****************************************************************************** + */ + +/** Prevent the release of ownership back to BIOS */ +static int ehci_legacy_prevent_release; + +/** + * Initialise USB legacy support + * + * @v ehci EHCI device + * @v pci PCI device + */ +static void ehci_legacy_init ( struct ehci_device *ehci, + struct pci_device *pci ) { + unsigned int legacy; + uint8_t bios; + + /* Locate USB legacy support capability (if present) */ + legacy = ehci_extended_capability ( ehci, pci, EHCI_EECP_ID_LEGACY, 0 ); + if ( ! legacy ) { + /* Not an error; capability may not be present */ + DBGC ( ehci, "EHCI %s has no USB legacy support capability\n", + ehci->name ); + return; + } + + /* Check if legacy USB support is enabled */ + pci_read_config_byte ( pci, ( legacy + EHCI_USBLEGSUP_BIOS ), &bios ); + if ( ! ( bios & EHCI_USBLEGSUP_BIOS_OWNED ) ) { + /* Not an error; already owned by OS */ + DBGC ( ehci, "EHCI %s USB legacy support already disabled\n", + ehci->name ); + return; + } + + /* Record presence of USB legacy support capability */ + ehci->legacy = legacy; +} + +/** + * Claim ownership from BIOS + * + * @v ehci EHCI device + * @v pci PCI device + */ +static void ehci_legacy_claim ( struct ehci_device *ehci, + struct pci_device *pci ) { + unsigned int legacy = ehci->legacy; + uint32_t ctlsts; + uint8_t bios; + unsigned int i; + + /* Do nothing unless legacy support capability is present */ + if ( ! legacy ) + return; + + /* Claim ownership */ + pci_write_config_byte ( pci, ( legacy + EHCI_USBLEGSUP_OS ), + EHCI_USBLEGSUP_OS_OWNED ); + + /* Wait for BIOS to release ownership */ + for ( i = 0 ; i < EHCI_USBLEGSUP_MAX_WAIT_MS ; i++ ) { + + /* Check if BIOS has released ownership */ + pci_read_config_byte ( pci, ( legacy + EHCI_USBLEGSUP_BIOS ), + &bios ); + if ( ! ( bios & EHCI_USBLEGSUP_BIOS_OWNED ) ) { + DBGC ( ehci, "EHCI %s claimed ownership from BIOS\n", + ehci->name ); + pci_read_config_dword ( pci, ( legacy + + EHCI_USBLEGSUP_CTLSTS ), + &ctlsts ); + if ( ctlsts ) { + DBGC ( ehci, "EHCI %s warning: BIOS retained " + "SMIs: %08x\n", ehci->name, ctlsts ); + } + return; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* BIOS did not release ownership. Claim it forcibly by + * disabling all SMIs. + */ + DBGC ( ehci, "EHCI %s could not claim ownership from BIOS: forcibly " + "disabling SMIs\n", ehci->name ); + pci_write_config_dword ( pci, ( legacy + EHCI_USBLEGSUP_CTLSTS ), 0 ); +} + +/** + * Release ownership back to BIOS + * + * @v ehci EHCI device + * @v pci PCI device + */ +static void ehci_legacy_release ( struct ehci_device *ehci, + struct pci_device *pci ) { + + /* Do nothing unless legacy support capability is present */ + if ( ! ehci->legacy ) + return; + + /* Do nothing if releasing ownership is prevented */ + if ( ehci_legacy_prevent_release ) { + DBGC ( ehci, "EHCI %s not releasing ownership to BIOS\n", + ehci->name ); + return; + } + + /* Release ownership */ + pci_write_config_byte ( pci, ( ehci->legacy + EHCI_USBLEGSUP_OS ), 0 ); + DBGC ( ehci, "EHCI %s released ownership to BIOS\n", ehci->name ); +} + +/****************************************************************************** + * + * Companion controllers + * + ****************************************************************************** + */ + +/** + * Poll child companion controllers + * + * @v ehci EHCI device + */ +static void ehci_poll_companions ( struct ehci_device *ehci ) { + struct usb_bus *bus; + struct device_description *desc; + + /* Poll any USB buses belonging to child companion controllers */ + for_each_usb_bus ( bus ) { + + /* Get underlying devices description */ + desc = &bus->dev->desc; + + /* Skip buses that are not PCI devices */ + if ( desc->bus_type != BUS_TYPE_PCI ) + continue; + + /* Skip buses that are not part of the same PCI device */ + if ( PCI_FIRST_FUNC ( desc->location ) != + PCI_FIRST_FUNC ( ehci->bus->dev->desc.location ) ) + continue; + + /* Skip buses that are not UHCI or OHCI PCI devices */ + if ( ( desc->class != PCI_CLASS ( PCI_CLASS_SERIAL, + PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_UHCI ))&& + ( desc->class != PCI_CLASS ( PCI_CLASS_SERIAL, + PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_OHCI ) )) + continue; + + /* Poll child companion controller bus */ + DBGC2 ( ehci, "EHCI %s polling companion %s\n", + ehci->name, bus->name ); + usb_poll ( bus ); + } +} + +/** + * Locate EHCI companion controller + * + * @v pci PCI device + * @ret busdevfn EHCI companion controller bus:dev.fn (if any) + */ +unsigned int ehci_companion ( struct pci_device *pci ) { + struct pci_device tmp; + unsigned int busdevfn; + int rc; + + /* Look for an EHCI function on the same PCI device */ + busdevfn = pci->busdevfn; + while ( ++busdevfn <= PCI_LAST_FUNC ( pci->busdevfn ) ) { + pci_init ( &tmp, busdevfn ); + if ( ( rc = pci_read_config ( &tmp ) ) != 0 ) + continue; + if ( tmp.class == PCI_CLASS ( PCI_CLASS_SERIAL, + PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_EHCI ) ) + return busdevfn; + } + + return 0; +} + +/****************************************************************************** + * + * Run / stop / reset + * + ****************************************************************************** + */ + +/** + * Start EHCI device + * + * @v ehci EHCI device + */ +static void ehci_run ( struct ehci_device *ehci ) { + uint32_t usbcmd; + + /* Set run/stop bit */ + usbcmd = readl ( ehci->op + EHCI_OP_USBCMD ); + usbcmd &= ~EHCI_USBCMD_FLSIZE_MASK; + usbcmd |= ( EHCI_USBCMD_RUN | EHCI_USBCMD_FLSIZE ( ehci->flsize ) | + EHCI_USBCMD_PERIODIC | EHCI_USBCMD_ASYNC ); + writel ( usbcmd, ehci->op + EHCI_OP_USBCMD ); +} + +/** + * Stop EHCI device + * + * @v ehci EHCI device + * @ret rc Return status code + */ +static int ehci_stop ( struct ehci_device *ehci ) { + uint32_t usbcmd; + uint32_t usbsts; + unsigned int i; + + /* Clear run/stop bit */ + usbcmd = readl ( ehci->op + EHCI_OP_USBCMD ); + usbcmd &= ~( EHCI_USBCMD_RUN | EHCI_USBCMD_PERIODIC | + EHCI_USBCMD_ASYNC ); + writel ( usbcmd, ehci->op + EHCI_OP_USBCMD ); + + /* Wait for device to stop */ + for ( i = 0 ; i < EHCI_STOP_MAX_WAIT_MS ; i++ ) { + + /* Check if device is stopped */ + usbsts = readl ( ehci->op + EHCI_OP_USBSTS ); + if ( usbsts & EHCI_USBSTS_HCH ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( ehci, "EHCI %s timed out waiting for stop\n", ehci->name ); + return -ETIMEDOUT; +} + +/** + * Reset EHCI device + * + * @v ehci EHCI device + * @ret rc Return status code + */ +static int ehci_reset ( struct ehci_device *ehci ) { + uint32_t usbcmd; + unsigned int i; + int rc; + + /* The EHCI specification states that resetting a running + * device may result in undefined behaviour, so try stopping + * it first. + */ + if ( ( rc = ehci_stop ( ehci ) ) != 0 ) { + /* Ignore errors and attempt to reset the device anyway */ + } + + /* Reset device */ + writel ( EHCI_USBCMD_HCRST, ehci->op + EHCI_OP_USBCMD ); + + /* Wait for reset to complete */ + for ( i = 0 ; i < EHCI_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check if reset is complete */ + usbcmd = readl ( ehci->op + EHCI_OP_USBCMD ); + if ( ! ( usbcmd & EHCI_USBCMD_HCRST ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( ehci, "EHCI %s timed out waiting for reset\n", ehci->name ); + return -ETIMEDOUT; +} + +/****************************************************************************** + * + * Transfer descriptor rings + * + ****************************************************************************** + */ + +/** + * Allocate transfer descriptor ring + * + * @v ehci EHCI device + * @v ring Transfer descriptor ring + * @ret rc Return status code + */ +static int ehci_ring_alloc ( struct ehci_device *ehci, + struct ehci_ring *ring ) { + struct ehci_transfer_descriptor *desc; + struct ehci_transfer_descriptor *next; + unsigned int i; + size_t len; + uint32_t link; + int rc; + + /* Initialise structure */ + memset ( ring, 0, sizeof ( *ring ) ); + + /* Allocate I/O buffers */ + ring->iobuf = zalloc ( EHCI_RING_COUNT * sizeof ( ring->iobuf[0] ) ); + if ( ! ring->iobuf ) { + rc = -ENOMEM; + goto err_alloc_iobuf; + } + + /* Allocate queue head */ + ring->head = malloc_dma ( sizeof ( *ring->head ), + ehci_align ( sizeof ( *ring->head ) ) ); + if ( ! ring->head ) { + rc = -ENOMEM; + goto err_alloc_queue; + } + if ( ( rc = ehci_ctrl_reachable ( ehci, ring->head ) ) != 0 ) { + DBGC ( ehci, "EHCI %s queue head unreachable\n", ehci->name ); + goto err_unreachable_queue; + } + memset ( ring->head, 0, sizeof ( *ring->head ) ); + + /* Allocate transfer descriptors */ + len = ( EHCI_RING_COUNT * sizeof ( ring->desc[0] ) ); + ring->desc = malloc_dma ( len, sizeof ( ring->desc[0] ) ); + if ( ! ring->desc ) { + rc = -ENOMEM; + goto err_alloc_desc; + } + memset ( ring->desc, 0, len ); + + /* Initialise transfer descriptors */ + for ( i = 0 ; i < EHCI_RING_COUNT ; i++ ) { + desc = &ring->desc[i]; + if ( ( rc = ehci_ctrl_reachable ( ehci, desc ) ) != 0 ) { + DBGC ( ehci, "EHCI %s descriptor unreachable\n", + ehci->name ); + goto err_unreachable_desc; + } + next = &ring->desc[ ( i + 1 ) % EHCI_RING_COUNT ]; + link = virt_to_phys ( next ); + desc->next = cpu_to_le32 ( link ); + desc->alt = cpu_to_le32 ( link ); + } + + /* Initialise queue head */ + link = virt_to_phys ( &ring->desc[0] ); + ring->head->cache.next = cpu_to_le32 ( link ); + + return 0; + + err_unreachable_desc: + free_dma ( ring->desc, len ); + err_alloc_desc: + err_unreachable_queue: + free_dma ( ring->head, sizeof ( *ring->head ) ); + err_alloc_queue: + free ( ring->iobuf ); + err_alloc_iobuf: + return rc; +} + +/** + * Free transfer descriptor ring + * + * @v ring Transfer descriptor ring + */ +static void ehci_ring_free ( struct ehci_ring *ring ) { + unsigned int i; + + /* Sanity checks */ + assert ( ehci_ring_fill ( ring ) == 0 ); + for ( i = 0 ; i < EHCI_RING_COUNT ; i++ ) + assert ( ring->iobuf[i] == NULL ); + + /* Free transfer descriptors */ + free_dma ( ring->desc, ( EHCI_RING_COUNT * sizeof ( ring->desc[0] ) ) ); + + /* Free queue head */ + free_dma ( ring->head, sizeof ( *ring->head ) ); + + /* Free I/O buffers */ + free ( ring->iobuf ); +} + +/** + * Enqueue transfer descriptors + * + * @v ehci EHCI device + * @v ring Transfer descriptor ring + * @v iobuf I/O buffer + * @v xfers Transfers + * @v count Number of transfers + * @ret rc Return status code + */ +static int ehci_enqueue ( struct ehci_device *ehci, struct ehci_ring *ring, + struct io_buffer *iobuf, + const struct ehci_transfer *xfer, + unsigned int count ) { + struct ehci_transfer_descriptor *desc; + physaddr_t phys; + void *data; + size_t len; + size_t offset; + size_t frag_len; + unsigned int toggle; + unsigned int index; + unsigned int i; + + /* Sanity check */ + assert ( iobuf != NULL ); + assert ( count > 0 ); + + /* Fail if ring does not have sufficient space */ + if ( ehci_ring_remaining ( ring ) < count ) + return -ENOBUFS; + + /* Fail if any portion is unreachable */ + for ( i = 0 ; i < count ; i++ ) { + phys = ( virt_to_phys ( xfer[i].data ) + xfer[i].len - 1 ); + if ( ( phys > 0xffffffffUL ) && ( ! ehci->addr64 ) ) + return -ENOTSUP; + } + + /* Enqueue each transfer, recording the I/O buffer with the last */ + for ( ; count ; ring->prod++, xfer++ ) { + + /* Populate descriptor header */ + index = ( ring->prod % EHCI_RING_COUNT ); + desc = &ring->desc[index]; + toggle = ( xfer->flags & EHCI_FL_TOGGLE ); + assert ( xfer->len <= EHCI_LEN_MASK ); + assert ( EHCI_FL_TOGGLE == EHCI_LEN_TOGGLE ); + desc->len = cpu_to_le16 ( xfer->len | toggle ); + desc->flags = ( xfer->flags | EHCI_FL_CERR_MAX ); + + /* Populate buffer pointers */ + data = xfer->data; + len = xfer->len; + for ( i = 0 ; len ; i++ ) { + + /* Calculate length of this fragment */ + phys = virt_to_phys ( data ); + offset = ( phys & ( EHCI_PAGE_ALIGN - 1 ) ); + frag_len = ( EHCI_PAGE_ALIGN - offset ); + if ( frag_len > len ) + frag_len = len; + + /* Sanity checks */ + assert ( ( i == 0 ) || ( offset == 0 ) ); + assert ( i < ( sizeof ( desc->low ) / + sizeof ( desc->low[0] ) ) ); + + /* Populate buffer pointer */ + desc->low[i] = cpu_to_le32 ( phys ); + if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) { + desc->high[i] = + cpu_to_le32 ( ((uint64_t) phys) >> 32 ); + } + + /* Move to next fragment */ + data += frag_len; + len -= frag_len; + } + + /* Ensure everything is valid before activating descriptor */ + wmb(); + desc->status = EHCI_STATUS_ACTIVE; + + /* Record I/O buffer against last ring index */ + if ( --count == 0 ) + ring->iobuf[index] = iobuf; + } + + return 0; +} + +/** + * Dequeue a transfer descriptor + * + * @v ring Transfer descriptor ring + * @ret iobuf I/O buffer (or NULL) + */ +static struct io_buffer * ehci_dequeue ( struct ehci_ring *ring ) { + struct ehci_transfer_descriptor *desc; + struct io_buffer *iobuf; + unsigned int index = ( ring->cons % EHCI_RING_COUNT ); + + /* Sanity check */ + assert ( ehci_ring_fill ( ring ) > 0 ); + + /* Mark descriptor as inactive (and not halted) */ + desc = &ring->desc[index]; + desc->status = 0; + + /* Retrieve I/O buffer */ + iobuf = ring->iobuf[index]; + ring->iobuf[index] = NULL; + + /* Update consumer counter */ + ring->cons++; + + return iobuf; +} + +/****************************************************************************** + * + * Schedule management + * + ****************************************************************************** + */ + +/** + * Get link value for a queue head + * + * @v queue Queue head + * @ret link Link value + */ +static inline uint32_t ehci_link_qh ( struct ehci_queue_head *queue ) { + + return ( virt_to_phys ( queue ) | EHCI_LINK_TYPE_QH ); +} + +/** + * (Re)build asynchronous schedule + * + * @v ehci EHCI device + */ +static void ehci_async_schedule ( struct ehci_device *ehci ) { + struct ehci_endpoint *endpoint; + struct ehci_queue_head *queue; + uint32_t link; + + /* Build schedule in reverse order of execution. Provided + * that we only ever add or remove single endpoints, this can + * safely run concurrently with hardware execution of the + * schedule. + */ + link = ehci_link_qh ( ehci->head ); + list_for_each_entry_reverse ( endpoint, &ehci->async, schedule ) { + queue = endpoint->ring.head; + queue->link = cpu_to_le32 ( link ); + wmb(); + link = ehci_link_qh ( queue ); + } + ehci->head->link = cpu_to_le32 ( link ); + wmb(); +} + +/** + * Add endpoint to asynchronous schedule + * + * @v endpoint Endpoint + */ +static void ehci_async_add ( struct ehci_endpoint *endpoint ) { + struct ehci_device *ehci = endpoint->ehci; + + /* Add to end of schedule */ + list_add_tail ( &endpoint->schedule, &ehci->async ); + + /* Rebuild schedule */ + ehci_async_schedule ( ehci ); +} + +/** + * Remove endpoint from asynchronous schedule + * + * @v endpoint Endpoint + * @ret rc Return status code + */ +static int ehci_async_del ( struct ehci_endpoint *endpoint ) { + struct ehci_device *ehci = endpoint->ehci; + uint32_t usbcmd; + uint32_t usbsts; + unsigned int i; + + /* Remove from schedule */ + list_check_contains_entry ( endpoint, &ehci->async, schedule ); + list_del ( &endpoint->schedule ); + + /* Rebuild schedule */ + ehci_async_schedule ( ehci ); + + /* Request notification when asynchronous schedule advances */ + usbcmd = readl ( ehci->op + EHCI_OP_USBCMD ); + usbcmd |= EHCI_USBCMD_ASYNC_ADVANCE; + writel ( usbcmd, ehci->op + EHCI_OP_USBCMD ); + + /* Wait for asynchronous schedule to advance */ + for ( i = 0 ; i < EHCI_ASYNC_ADVANCE_MAX_WAIT_MS ; i++ ) { + + /* Check for asynchronous schedule advancing */ + usbsts = readl ( ehci->op + EHCI_OP_USBSTS ); + if ( usbsts & EHCI_USBSTS_ASYNC_ADVANCE ) { + usbsts &= ~EHCI_USBSTS_CHANGE; + usbsts |= EHCI_USBSTS_ASYNC_ADVANCE; + writel ( usbsts, ehci->op + EHCI_OP_USBSTS ); + return 0; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* Bad things will probably happen now */ + DBGC ( ehci, "EHCI %s timed out waiting for asynchronous schedule " + "to advance\n", ehci->name ); + return -ETIMEDOUT; +} + +/** + * (Re)build periodic schedule + * + * @v ehci EHCI device + */ +static void ehci_periodic_schedule ( struct ehci_device *ehci ) { + struct ehci_endpoint *endpoint; + struct ehci_queue_head *queue; + uint32_t link; + unsigned int frames; + unsigned int max_interval; + unsigned int i; + + /* Build schedule in reverse order of execution. Provided + * that we only ever add or remove single endpoints, this can + * safely run concurrently with hardware execution of the + * schedule. + */ + DBGCP ( ehci, "EHCI %s periodic schedule: ", ehci->name ); + link = EHCI_LINK_TERMINATE; + list_for_each_entry_reverse ( endpoint, &ehci->periodic, schedule ) { + queue = endpoint->ring.head; + queue->link = cpu_to_le32 ( link ); + wmb(); + DBGCP ( ehci, "%s%d", + ( ( link == EHCI_LINK_TERMINATE ) ? "" : "<-" ), + endpoint->ep->interval ); + link = ehci_link_qh ( queue ); + } + DBGCP ( ehci, "\n" ); + + /* Populate periodic frame list */ + DBGCP ( ehci, "EHCI %s periodic frame list:", ehci->name ); + frames = EHCI_PERIODIC_FRAMES ( ehci->flsize ); + for ( i = 0 ; i < frames ; i++ ) { + + /* Calculate maximum interval (in microframes) which + * may appear as part of this frame list. + */ + if ( i == 0 ) { + /* Start of list: include all endpoints */ + max_interval = -1U; + } else { + /* Calculate highest power-of-two frame interval */ + max_interval = ( 1 << ( ffs ( i ) - 1 ) ); + /* Convert to microframes */ + max_interval <<= 3; + /* Round up to nearest 2^n-1 */ + max_interval = ( ( max_interval << 1 ) - 1 ); + } + + /* Find first endpoint in schedule satisfying this + * maximum interval constraint. + */ + link = EHCI_LINK_TERMINATE; + list_for_each_entry ( endpoint, &ehci->periodic, schedule ) { + if ( endpoint->ep->interval <= max_interval ) { + queue = endpoint->ring.head; + link = ehci_link_qh ( queue ); + DBGCP ( ehci, " %d:%d", + i, endpoint->ep->interval ); + break; + } + } + ehci->frame[i].link = cpu_to_le32 ( link ); + } + wmb(); + DBGCP ( ehci, "\n" ); +} + +/** + * Add endpoint to periodic schedule + * + * @v endpoint Endpoint + */ +static void ehci_periodic_add ( struct ehci_endpoint *endpoint ) { + struct ehci_device *ehci = endpoint->ehci; + struct ehci_endpoint *before; + unsigned int interval = endpoint->ep->interval; + + /* Find first endpoint with a smaller interval */ + list_for_each_entry ( before, &ehci->periodic, schedule ) { + if ( before->ep->interval < interval ) + break; + } + list_add_tail ( &endpoint->schedule, &before->schedule ); + + /* Rebuild schedule */ + ehci_periodic_schedule ( ehci ); +} + +/** + * Remove endpoint from periodic schedule + * + * @v endpoint Endpoint + * @ret rc Return status code + */ +static int ehci_periodic_del ( struct ehci_endpoint *endpoint ) { + struct ehci_device *ehci = endpoint->ehci; + + /* Remove from schedule */ + list_check_contains_entry ( endpoint, &ehci->periodic, schedule ); + list_del ( &endpoint->schedule ); + + /* Rebuild schedule */ + ehci_periodic_schedule ( ehci ); + + /* Delay for a whole USB frame (with a 100% safety margin) */ + mdelay ( 2 ); + + return 0; +} + +/** + * Add endpoint to appropriate schedule + * + * @v endpoint Endpoint + */ +static void ehci_schedule_add ( struct ehci_endpoint *endpoint ) { + struct usb_endpoint *ep = endpoint->ep; + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + + if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { + ehci_periodic_add ( endpoint ); + } else { + ehci_async_add ( endpoint ); + } +} + +/** + * Remove endpoint from appropriate schedule + * + * @v endpoint Endpoint + * @ret rc Return status code + */ +static int ehci_schedule_del ( struct ehci_endpoint *endpoint ) { + struct usb_endpoint *ep = endpoint->ep; + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + + if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { + return ehci_periodic_del ( endpoint ); + } else { + return ehci_async_del ( endpoint ); + } +} + +/****************************************************************************** + * + * Endpoint operations + * + ****************************************************************************** + */ + +/** + * Determine endpoint characteristics + * + * @v ep USB endpoint + * @ret chr Endpoint characteristics + */ +static uint32_t ehci_endpoint_characteristics ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + uint32_t chr; + + /* Determine basic characteristics */ + chr = ( EHCI_CHR_ADDRESS ( usb->address ) | + EHCI_CHR_ENDPOINT ( ep->address ) | + EHCI_CHR_MAX_LEN ( ep->mtu ) ); + + /* Control endpoints require manual control of the data toggle */ + if ( attr == USB_ENDPOINT_ATTR_CONTROL ) + chr |= EHCI_CHR_TOGGLE; + + /* Determine endpoint speed */ + if ( usb->port->speed == USB_SPEED_HIGH ) { + chr |= EHCI_CHR_EPS_HIGH; + } else { + if ( usb->port->speed == USB_SPEED_FULL ) { + chr |= EHCI_CHR_EPS_FULL; + } else { + chr |= EHCI_CHR_EPS_LOW; + } + if ( attr == USB_ENDPOINT_ATTR_CONTROL ) + chr |= EHCI_CHR_CONTROL; + } + + return chr; +} + +/** + * Determine endpoint capabilities + * + * @v ep USB endpoint + * @ret cap Endpoint capabilities + */ +static uint32_t ehci_endpoint_capabilities ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + struct usb_port *tt = usb_transaction_translator ( usb ); + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + uint32_t cap; + unsigned int i; + + /* Determine basic capabilities */ + cap = EHCI_CAP_MULT ( ep->burst + 1 ); + + /* Determine interrupt schedule mask, if applicable */ + if ( ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) && + ( ( ep->interval != 0 ) /* avoid infinite loop */ ) ) { + for ( i = 0 ; i < 8 /* microframes per frame */ ; + i += ep->interval ) { + cap |= EHCI_CAP_INTR_SCHED ( i ); + } + } + + /* Set transaction translator hub address and port, if applicable */ + if ( tt ) { + assert ( tt->hub->usb ); + cap |= ( EHCI_CAP_TT_HUB ( tt->hub->usb->address ) | + EHCI_CAP_TT_PORT ( tt->address ) ); + if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) + cap |= EHCI_CAP_SPLIT_SCHED_DEFAULT; + } + + return cap; +} + +/** + * Update endpoint characteristics and capabilities + * + * @v ep USB endpoint + */ +static void ehci_endpoint_update ( struct usb_endpoint *ep ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_queue_head *head; + + /* Update queue characteristics and capabilities */ + head = endpoint->ring.head; + head->chr = cpu_to_le32 ( ehci_endpoint_characteristics ( ep ) ); + head->cap = cpu_to_le32 ( ehci_endpoint_capabilities ( ep ) ); +} + +/** + * Open endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int ehci_endpoint_open ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + struct ehci_device *ehci = usb_get_hostdata ( usb ); + struct ehci_endpoint *endpoint; + int rc; + + /* Allocate and initialise structure */ + endpoint = zalloc ( sizeof ( *endpoint ) ); + if ( ! endpoint ) { + rc = -ENOMEM; + goto err_alloc; + } + endpoint->ehci = ehci; + endpoint->ep = ep; + usb_endpoint_set_hostdata ( ep, endpoint ); + + /* Initialise descriptor ring */ + if ( ( rc = ehci_ring_alloc ( ehci, &endpoint->ring ) ) != 0 ) + goto err_ring_alloc; + + /* Update queue characteristics and capabilities */ + ehci_endpoint_update ( ep ); + + /* Add to list of endpoints */ + list_add_tail ( &endpoint->list, &ehci->endpoints ); + + /* Add to schedule */ + ehci_schedule_add ( endpoint ); + + return 0; + + ehci_ring_free ( &endpoint->ring ); + err_ring_alloc: + free ( endpoint ); + err_alloc: + return rc; +} + +/** + * Close endpoint + * + * @v ep USB endpoint + */ +static void ehci_endpoint_close ( struct usb_endpoint *ep ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_device *ehci = endpoint->ehci; + struct usb_device *usb = ep->usb; + struct io_buffer *iobuf; + int rc; + + /* Remove from schedule */ + if ( ( rc = ehci_schedule_del ( endpoint ) ) != 0 ) { + /* No way to prevent hardware from continuing to + * access the memory, so leak it. + */ + DBGC ( ehci, "EHCI %s %s could not unschedule: %s\n", + usb->name, usb_endpoint_name ( ep ), strerror ( rc ) ); + return; + } + + /* Cancel any incomplete transfers */ + while ( ehci_ring_fill ( &endpoint->ring ) ) { + iobuf = ehci_dequeue ( &endpoint->ring ); + if ( iobuf ) + usb_complete_err ( ep, iobuf, -ECANCELED ); + } + + /* Remove from list of endpoints */ + list_del ( &endpoint->list ); + + /* Free descriptor ring */ + ehci_ring_free ( &endpoint->ring ); + + /* Free endpoint */ + free ( endpoint ); +} + +/** + * Reset endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int ehci_endpoint_reset ( struct usb_endpoint *ep ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_ring *ring = &endpoint->ring; + struct ehci_transfer_descriptor *cache = &ring->head->cache; + uint32_t link; + + /* Sanity checks */ + assert ( ! ( cache->status & EHCI_STATUS_ACTIVE ) ); + assert ( cache->status & EHCI_STATUS_HALTED ); + + /* Reset residual count */ + ring->residual = 0; + + /* Reset data toggle */ + cache->len = 0; + + /* Prepare to restart at next unconsumed descriptor */ + link = virt_to_phys ( &ring->desc[ ring->cons % EHCI_RING_COUNT ] ); + cache->next = cpu_to_le32 ( link ); + + /* Restart ring */ + wmb(); + cache->status = 0; + + return 0; +} + +/** + * Update MTU + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int ehci_endpoint_mtu ( struct usb_endpoint *ep ) { + + /* Update endpoint characteristics and capabilities */ + ehci_endpoint_update ( ep ); + + return 0; +} + +/** + * Enqueue message transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ehci_endpoint_message ( struct usb_endpoint *ep, + struct io_buffer *iobuf ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_device *ehci = endpoint->ehci; + struct usb_setup_packet *packet; + unsigned int input; + struct ehci_transfer xfers[3]; + struct ehci_transfer *xfer = xfers; + size_t len; + int rc; + + /* Construct setup stage */ + assert ( iob_len ( iobuf ) >= sizeof ( *packet ) ); + packet = iobuf->data; + iob_pull ( iobuf, sizeof ( *packet ) ); + xfer->data = packet; + xfer->len = sizeof ( *packet ); + xfer->flags = EHCI_FL_PID_SETUP; + xfer++; + + /* Construct data stage, if applicable */ + len = iob_len ( iobuf ); + input = ( packet->request & cpu_to_le16 ( USB_DIR_IN ) ); + if ( len ) { + xfer->data = iobuf->data; + xfer->len = len; + xfer->flags = ( EHCI_FL_TOGGLE | + ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ) ); + xfer++; + } + + /* Construct status stage */ + xfer->data = NULL; + xfer->len = 0; + xfer->flags = ( EHCI_FL_TOGGLE | EHCI_FL_IOC | + ( ( len && input ) ? EHCI_FL_PID_OUT : EHCI_FL_PID_IN)); + xfer++; + + /* Enqueue transfer */ + if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers, + ( xfer - xfers ) ) ) != 0 ) + return rc; + + return 0; +} + +/** + * 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 ehci_endpoint_stream ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int terminate ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_device *ehci = endpoint->ehci; + unsigned int input = ( ep->address & USB_DIR_IN ); + struct ehci_transfer xfers[2]; + struct ehci_transfer *xfer = xfers; + size_t len = iob_len ( iobuf ); + int rc; + + /* Create transfer */ + xfer->data = iobuf->data; + xfer->len = len; + xfer->flags = ( EHCI_FL_IOC | + ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ) ); + xfer++; + if ( terminate && ( ( len & ( ep->mtu - 1 ) ) == 0 ) ) { + xfer->data = NULL; + xfer->len = 0; + assert ( ! input ); + xfer->flags = ( EHCI_FL_IOC | EHCI_FL_PID_OUT ); + xfer++; + } + + /* Enqueue transfer */ + if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers, + ( xfer - xfers ) ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Poll for completions + * + * @v endpoint Endpoint + */ +static void ehci_endpoint_poll ( struct ehci_endpoint *endpoint ) { + struct ehci_device *ehci = endpoint->ehci; + struct ehci_ring *ring = &endpoint->ring; + struct ehci_transfer_descriptor *desc; + struct usb_endpoint *ep = endpoint->ep; + struct usb_device *usb = ep->usb; + struct io_buffer *iobuf; + unsigned int index; + unsigned int status; + int rc; + + /* Consume all completed descriptors */ + while ( ehci_ring_fill ( &endpoint->ring ) ) { + + /* Stop if we reach an uncompleted descriptor */ + rmb(); + index = ( ring->cons % EHCI_RING_COUNT ); + desc = &ring->desc[index]; + status = desc->status; + if ( status & EHCI_STATUS_ACTIVE ) + break; + + /* Consume this descriptor */ + iobuf = ehci_dequeue ( ring ); + + /* If we have encountered an error, then consume all + * remaining descriptors in this transaction, report + * the error to the USB core, and stop further + * processing. + */ + if ( status & EHCI_STATUS_HALTED ) { + rc = -EIO_STATUS ( status ); + DBGC ( ehci, "EHCI %s %s completion %d failed (status " + "%02x): %s\n", usb->name, + usb_endpoint_name ( ep ), index, status, + strerror ( rc ) ); + while ( ! iobuf ) + iobuf = ehci_dequeue ( ring ); + usb_complete_err ( endpoint->ep, iobuf, rc ); + return; + } + + /* Accumulate residual data count */ + ring->residual += ( le16_to_cpu ( desc->len ) & EHCI_LEN_MASK ); + + /* If this is not the end of a transaction (i.e. has + * no I/O buffer), then continue to next descriptor. + */ + if ( ! iobuf ) + continue; + + /* Update I/O buffer length */ + iob_unput ( iobuf, ring->residual ); + ring->residual = 0; + + /* Report completion to USB core */ + usb_complete ( endpoint->ep, iobuf ); + } +} + +/****************************************************************************** + * + * Device operations + * + ****************************************************************************** + */ + +/** + * Open device + * + * @v usb USB device + * @ret rc Return status code + */ +static int ehci_device_open ( struct usb_device *usb ) { + struct ehci_device *ehci = usb_bus_get_hostdata ( usb->port->hub->bus ); + + usb_set_hostdata ( usb, ehci ); + return 0; +} + +/** + * Close device + * + * @v usb USB device + */ +static void ehci_device_close ( struct usb_device *usb ) { + struct ehci_device *ehci = usb_get_hostdata ( usb ); + struct usb_bus *bus = ehci->bus; + + /* Free device address, if assigned */ + if ( usb->address ) + usb_free_address ( bus, usb->address ); +} + +/** + * Assign device address + * + * @v usb USB device + * @ret rc Return status code + */ +static int ehci_device_address ( struct usb_device *usb ) { + struct ehci_device *ehci = usb_get_hostdata ( usb ); + struct usb_bus *bus = ehci->bus; + struct usb_endpoint *ep0 = usb_endpoint ( usb, USB_EP0_ADDRESS ); + int address; + int rc; + + /* Sanity checks */ + assert ( usb->address == 0 ); + assert ( ep0 != NULL ); + + /* Allocate device address */ + address = usb_alloc_address ( bus ); + if ( address < 0 ) { + rc = address; + DBGC ( ehci, "EHCI %s could not allocate address: %s\n", + usb->name, strerror ( rc ) ); + goto err_alloc_address; + } + + /* Set address */ + if ( ( rc = usb_set_address ( usb, address ) ) != 0 ) + goto err_set_address; + + /* Update device address */ + usb->address = address; + + /* Update control endpoint characteristics and capabilities */ + ehci_endpoint_update ( ep0 ); + + return 0; + + err_set_address: + usb_free_address ( bus, address ); + err_alloc_address: + return rc; +} + +/****************************************************************************** + * + * Hub operations + * + ****************************************************************************** + */ + +/** + * Open hub + * + * @v hub USB hub + * @ret rc Return status code + */ +static int ehci_hub_open ( struct usb_hub *hub __unused ) { + + /* Nothing to do */ + return 0; +} + +/** + * Close hub + * + * @v hub USB hub + */ +static void ehci_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 ehci_root_open ( struct usb_hub *hub ) { + struct usb_bus *bus = hub->bus; + struct ehci_device *ehci = usb_bus_get_hostdata ( bus ); + uint32_t portsc; + unsigned int i; + + /* Route all ports to EHCI controller */ + writel ( EHCI_CONFIGFLAG_CF, ehci->op + EHCI_OP_CONFIGFLAG ); + + /* Enable power to all ports */ + for ( i = 1 ; i <= ehci->ports ; i++ ) { + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( i ) ); + portsc &= ~EHCI_PORTSC_CHANGE; + portsc |= EHCI_PORTSC_PP; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( i ) ); + } + + /* Wait 20ms after potentially enabling power to a port */ + mdelay ( EHCI_PORT_POWER_DELAY_MS ); + + /* Record hub driver private data */ + usb_hub_set_drvdata ( hub, ehci ); + + return 0; +} + +/** + * Close root hub + * + * @v hub USB hub + */ +static void ehci_root_close ( struct usb_hub *hub ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + + /* Route all ports back to companion controllers */ + writel ( 0, ehci->op + EHCI_OP_CONFIGFLAG ); + + /* Clear hub driver private data */ + usb_hub_set_drvdata ( hub, NULL ); +} + +/** + * Enable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int ehci_root_enable ( struct usb_hub *hub, struct usb_port *port ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + unsigned int line; + unsigned int i; + + /* Check for a low-speed device */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + line = EHCI_PORTSC_LINE_STATUS ( portsc ); + if ( line == EHCI_PORTSC_LINE_STATUS_LOW ) { + DBGC ( ehci, "EHCI %s-%d detected low-speed device: " + "disowning\n", ehci->name, port->address ); + goto disown; + } + + /* Reset port */ + portsc &= ~( EHCI_PORTSC_PED | EHCI_PORTSC_CHANGE ); + portsc |= EHCI_PORTSC_PR; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + mdelay ( USB_RESET_DELAY_MS ); + portsc &= ~EHCI_PORTSC_PR; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + + /* Wait for reset to complete */ + for ( i = 0 ; i < EHCI_PORT_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check port status */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + if ( ! ( portsc & EHCI_PORTSC_PR ) ) { + if ( portsc & EHCI_PORTSC_PED ) + return 0; + DBGC ( ehci, "EHCI %s-%d not enabled after reset: " + "disowning\n", ehci->name, port->address ); + goto disown; + } + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( ehci, "EHCI %s-%d timed out waiting for port to reset\n", + ehci->name, port->address ); + return -ETIMEDOUT; + + disown: + /* Disown port */ + portsc &= ~EHCI_PORTSC_CHANGE; + portsc |= EHCI_PORTSC_OWNER; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + + /* Delay to allow child companion controllers to settle */ + mdelay ( EHCI_DISOWN_DELAY_MS ); + + /* Poll child companion controllers */ + ehci_poll_companions ( ehci ); + + return -ENODEV; +} + +/** + * Disable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int ehci_root_disable ( struct usb_hub *hub, struct usb_port *port ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + + /* Disable port */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + portsc &= ~( EHCI_PORTSC_PED | EHCI_PORTSC_CHANGE ); + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + + return 0; +} + +/** + * Update root hub port speed + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int ehci_root_speed ( struct usb_hub *hub, struct usb_port *port ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + unsigned int speed; + unsigned int line; + int ccs; + int csc; + int ped; + + /* Read port status */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + DBGC2 ( ehci, "EHCI %s-%d status is %08x\n", + ehci->name, port->address, portsc ); + ccs = ( portsc & EHCI_PORTSC_CCS ); + csc = ( portsc & EHCI_PORTSC_CSC ); + ped = ( portsc & EHCI_PORTSC_PED ); + line = EHCI_PORTSC_LINE_STATUS ( portsc ); + + /* Record disconnections and clear changes */ + port->disconnected |= csc; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + + /* Determine port speed */ + if ( ! ccs ) { + /* Port not connected */ + speed = USB_SPEED_NONE; + } else if ( line == EHCI_PORTSC_LINE_STATUS_LOW ) { + /* Detected as low-speed */ + speed = USB_SPEED_LOW; + } else if ( ped ) { + /* Port already enabled: must be high-speed */ + speed = USB_SPEED_HIGH; + } else { + /* Not low-speed and not yet enabled. Could be either + * full-speed or high-speed; we can't yet tell. + */ + speed = USB_SPEED_FULL; + } + port->speed = speed; + 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 ehci_root_clear_tt ( struct usb_hub *hub, struct usb_port *port, + struct usb_endpoint *ep ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + + /* Should never be called; this is a root hub */ + DBGC ( ehci, "EHCI %s-%d nonsensical CLEAR_TT for %s %s\n", ehci->name, + port->address, ep->usb->name, usb_endpoint_name ( ep ) ); + + return -ENOTSUP; +} + +/** + * Poll for port status changes + * + * @v hub USB hub + * @v port USB port + */ +static void ehci_root_poll ( struct usb_hub *hub, struct usb_port *port ) { + struct ehci_device *ehci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + uint32_t change; + + /* Do nothing unless something has changed */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + change = ( portsc & EHCI_PORTSC_CHANGE ); + if ( ! change ) + return; + + /* Record disconnections and clear changes */ + port->disconnected |= ( portsc & EHCI_PORTSC_CSC ); + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + + /* Report port status change */ + usb_port_changed ( port ); +} + +/****************************************************************************** + * + * Bus operations + * + ****************************************************************************** + */ + +/** + * Open USB bus + * + * @v bus USB bus + * @ret rc Return status code + */ +static int ehci_bus_open ( struct usb_bus *bus ) { + struct ehci_device *ehci = usb_bus_get_hostdata ( bus ); + unsigned int frames; + size_t len; + int rc; + + /* Sanity checks */ + assert ( list_empty ( &ehci->async ) ); + assert ( list_empty ( &ehci->periodic ) ); + + /* Allocate and initialise asynchronous queue head */ + ehci->head = malloc_dma ( sizeof ( *ehci->head ), + ehci_align ( sizeof ( *ehci->head ) ) ); + if ( ! ehci->head ) { + rc = -ENOMEM; + goto err_alloc_head; + } + memset ( ehci->head, 0, sizeof ( *ehci->head ) ); + ehci->head->chr = cpu_to_le32 ( EHCI_CHR_HEAD ); + ehci->head->cache.next = cpu_to_le32 ( EHCI_LINK_TERMINATE ); + ehci->head->cache.status = EHCI_STATUS_HALTED; + ehci_async_schedule ( ehci ); + writel ( virt_to_phys ( ehci->head ), + ehci->op + EHCI_OP_ASYNCLISTADDR ); + + /* Use async queue head to determine control data structure segment */ + ehci->ctrldssegment = + ( ( ( uint64_t ) virt_to_phys ( ehci->head ) ) >> 32 ); + if ( ehci->addr64 ) { + writel ( ehci->ctrldssegment, ehci->op + EHCI_OP_CTRLDSSEGMENT); + } else if ( ehci->ctrldssegment ) { + DBGC ( ehci, "EHCI %s CTRLDSSEGMENT not supported\n", + ehci->name ); + rc = -ENOTSUP; + goto err_ctrldssegment; + } + + /* Allocate periodic frame list */ + frames = EHCI_PERIODIC_FRAMES ( ehci->flsize ); + len = ( frames * sizeof ( ehci->frame[0] ) ); + ehci->frame = malloc_dma ( len, EHCI_PAGE_ALIGN ); + if ( ! ehci->frame ) { + rc = -ENOMEM; + goto err_alloc_frame; + } + if ( ( rc = ehci_ctrl_reachable ( ehci, ehci->frame ) ) != 0 ) { + DBGC ( ehci, "EHCI %s frame list unreachable\n", ehci->name ); + goto err_unreachable_frame; + } + ehci_periodic_schedule ( ehci ); + writel ( virt_to_phys ( ehci->frame ), + ehci->op + EHCI_OP_PERIODICLISTBASE ); + + /* Start controller */ + ehci_run ( ehci ); + + return 0; + + ehci_stop ( ehci ); + err_unreachable_frame: + free_dma ( ehci->frame, len ); + err_alloc_frame: + err_ctrldssegment: + free_dma ( ehci->head, sizeof ( *ehci->head ) ); + err_alloc_head: + return rc; +} + +/** + * Close USB bus + * + * @v bus USB bus + */ +static void ehci_bus_close ( struct usb_bus *bus ) { + struct ehci_device *ehci = usb_bus_get_hostdata ( bus ); + unsigned int frames = EHCI_PERIODIC_FRAMES ( ehci->flsize ); + + /* Sanity checks */ + assert ( list_empty ( &ehci->async ) ); + assert ( list_empty ( &ehci->periodic ) ); + + /* Stop controller */ + ehci_stop ( ehci ); + + /* Free periodic frame list */ + free_dma ( ehci->frame, ( frames * sizeof ( ehci->frame[0] ) ) ); + + /* Free asynchronous schedule */ + free_dma ( ehci->head, sizeof ( *ehci->head ) ); +} + +/** + * Poll USB bus + * + * @v bus USB bus + */ +static void ehci_bus_poll ( struct usb_bus *bus ) { + struct ehci_device *ehci = usb_bus_get_hostdata ( bus ); + struct usb_hub *hub = bus->hub; + struct ehci_endpoint *endpoint; + unsigned int i; + uint32_t usbsts; + uint32_t change; + + /* Do nothing unless something has changed */ + usbsts = readl ( ehci->op + EHCI_OP_USBSTS ); + assert ( usbsts & EHCI_USBSTS_ASYNC ); + assert ( usbsts & EHCI_USBSTS_PERIODIC ); + assert ( ! ( usbsts & EHCI_USBSTS_HCH ) ); + change = ( usbsts & EHCI_USBSTS_CHANGE ); + if ( ! change ) + return; + + /* Acknowledge changes */ + writel ( usbsts, ehci->op + EHCI_OP_USBSTS ); + + /* Process completions, if applicable */ + if ( change & ( EHCI_USBSTS_USBINT | EHCI_USBSTS_USBERRINT ) ) { + + /* Iterate over all endpoints looking for completed + * descriptors. 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, &ehci->endpoints, list ) + ehci_endpoint_poll ( endpoint ); + } + + /* Process port status changes, if applicable */ + if ( change & EHCI_USBSTS_PORT ) { + + /* Iterate over all ports looking for status changes */ + for ( i = 1 ; i <= ehci->ports ; i++ ) + ehci_root_poll ( hub, usb_port ( hub, i ) ); + } + + /* Report fatal errors */ + if ( change & EHCI_USBSTS_SYSERR ) + DBGC ( ehci, "EHCI %s host system error\n", ehci->name ); +} + +/****************************************************************************** + * + * PCI interface + * + ****************************************************************************** + */ + +/** USB host controller operations */ +static struct usb_host_operations ehci_operations = { + .endpoint = { + .open = ehci_endpoint_open, + .close = ehci_endpoint_close, + .reset = ehci_endpoint_reset, + .mtu = ehci_endpoint_mtu, + .message = ehci_endpoint_message, + .stream = ehci_endpoint_stream, + }, + .device = { + .open = ehci_device_open, + .close = ehci_device_close, + .address = ehci_device_address, + }, + .bus = { + .open = ehci_bus_open, + .close = ehci_bus_close, + .poll = ehci_bus_poll, + }, + .hub = { + .open = ehci_hub_open, + .close = ehci_hub_close, + }, + .root = { + .open = ehci_root_open, + .close = ehci_root_close, + .enable = ehci_root_enable, + .disable = ehci_root_disable, + .speed = ehci_root_speed, + .clear_tt = ehci_root_clear_tt, + }, +}; + +/** + * Probe PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +static int ehci_probe ( struct pci_device *pci ) { + struct ehci_device *ehci; + struct usb_port *port; + unsigned long bar_start; + size_t bar_size; + unsigned int i; + int rc; + + /* Allocate and initialise structure */ + ehci = zalloc ( sizeof ( *ehci ) ); + if ( ! ehci ) { + rc = -ENOMEM; + goto err_alloc; + } + ehci->name = pci->dev.name; + INIT_LIST_HEAD ( &ehci->endpoints ); + INIT_LIST_HEAD ( &ehci->async ); + INIT_LIST_HEAD ( &ehci->periodic ); + + /* Fix up PCI device */ + adjust_pci_device ( pci ); + + /* Map registers */ + bar_start = pci_bar_start ( pci, EHCI_BAR ); + bar_size = pci_bar_size ( pci, EHCI_BAR ); + ehci->regs = ioremap ( bar_start, bar_size ); + if ( ! ehci->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Initialise EHCI device */ + ehci_init ( ehci, ehci->regs ); + + /* Initialise USB legacy support and claim ownership */ + ehci_legacy_init ( ehci, pci ); + ehci_legacy_claim ( ehci, pci ); + + /* Reset device */ + if ( ( rc = ehci_reset ( ehci ) ) != 0 ) + goto err_reset; + + /* Allocate USB bus */ + ehci->bus = alloc_usb_bus ( &pci->dev, ehci->ports, EHCI_MTU, + &ehci_operations ); + if ( ! ehci->bus ) { + rc = -ENOMEM; + goto err_alloc_bus; + } + usb_bus_set_hostdata ( ehci->bus, ehci ); + usb_hub_set_drvdata ( ehci->bus->hub, ehci ); + + /* Set port protocols */ + for ( i = 1 ; i <= ehci->ports ; i++ ) { + port = usb_port ( ehci->bus->hub, i ); + port->protocol = USB_PROTO_2_0; + } + + /* Register USB bus */ + if ( ( rc = register_usb_bus ( ehci->bus ) ) != 0 ) + goto err_register; + + pci_set_drvdata ( pci, ehci ); + return 0; + + unregister_usb_bus ( ehci->bus ); + err_register: + free_usb_bus ( ehci->bus ); + err_alloc_bus: + ehci_reset ( ehci ); + err_reset: + ehci_legacy_release ( ehci, pci ); + iounmap ( ehci->regs ); + err_ioremap: + free ( ehci ); + err_alloc: + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void ehci_remove ( struct pci_device *pci ) { + struct ehci_device *ehci = pci_get_drvdata ( pci ); + struct usb_bus *bus = ehci->bus; + + unregister_usb_bus ( bus ); + assert ( list_empty ( &ehci->async ) ); + assert ( list_empty ( &ehci->periodic ) ); + free_usb_bus ( bus ); + ehci_reset ( ehci ); + ehci_legacy_release ( ehci, pci ); + iounmap ( ehci->regs ); + free ( ehci ); +} + +/** EHCI PCI device IDs */ +static struct pci_device_id ehci_ids[] = { + PCI_ROM ( 0xffff, 0xffff, "ehci", "EHCI", 0 ), +}; + +/** EHCI PCI driver */ +struct pci_driver ehci_driver __pci_driver = { + .ids = ehci_ids, + .id_count = ( sizeof ( ehci_ids ) / sizeof ( ehci_ids[0] ) ), + .class = PCI_CLASS_ID ( PCI_CLASS_SERIAL, PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_EHCI ), + .probe = ehci_probe, + .remove = ehci_remove, +}; + +/** + * Prepare for exit + * + * @v booting System is shutting down for OS boot + */ +static void ehci_shutdown ( int booting ) { + /* If we are shutting down to boot an OS, then prevent the + * release of ownership back to BIOS. + */ + ehci_legacy_prevent_release = booting; +} + +/** Startup/shutdown function */ +struct startup_fn ehci_startup __startup_fn ( STARTUP_LATE ) = { + .shutdown = ehci_shutdown, +}; diff --git a/qemu/roms/ipxe/src/drivers/usb/ehci.h b/qemu/roms/ipxe/src/drivers/usb/ehci.h new file mode 100644 index 000000000..42e282e92 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/ehci.h @@ -0,0 +1,544 @@ +#ifndef _IPXE_EHCI_H +#define _IPXE_EHCI_H + +/** @file + * + * USB Enhanced Host Controller Interface (EHCI) driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/pci.h> +#include <ipxe/usb.h> + +/** Minimum alignment required for data structures + * + * With the exception of the periodic frame list (which is + * page-aligned), data structures used by EHCI generally require + * 32-byte alignment and must not cross a 4kB page boundary. We + * simplify this requirement by aligning each structure on its own + * size, with a minimum of a 32 byte alignment. + */ +#define EHCI_MIN_ALIGN 32 + +/** Maximum transfer size + * + * EHCI allows for transfers of up to 20kB with page-alignment, or + * 16kB with arbitrary alignment. + */ +#define EHCI_MTU 16384 + +/** Page-alignment required for some data structures */ +#define EHCI_PAGE_ALIGN 4096 + +/** EHCI PCI BAR */ +#define EHCI_BAR PCI_BASE_ADDRESS_0 + +/** Capability register length */ +#define EHCI_CAP_CAPLENGTH 0x00 + +/** Host controller interface version number */ +#define EHCI_CAP_HCIVERSION 0x02 + +/** Structural parameters */ +#define EHCI_CAP_HCSPARAMS 0x04 + +/** Number of ports */ +#define EHCI_HCSPARAMS_PORTS(params) ( ( (params) >> 0 ) & 0x0f ) + +/** Capability parameters */ +#define EHCI_CAP_HCCPARAMS 0x08 + +/** 64-bit addressing capability */ +#define EHCI_HCCPARAMS_ADDR64(params) ( ( (params) >> 0 ) & 0x1 ) + +/** Programmable frame list flag */ +#define EHCI_HCCPARAMS_FLSIZE(params) ( ( (params) >> 1 ) & 0x1 ) + +/** EHCI extended capabilities pointer */ +#define EHCI_HCCPARAMS_EECP(params) ( ( ( (params) >> 8 ) & 0xff ) ) + +/** EHCI extended capability ID */ +#define EHCI_EECP_ID(eecp) ( ( (eecp) >> 0 ) & 0xff ) + +/** Next EHCI extended capability pointer */ +#define EHCI_EECP_NEXT(eecp) ( ( ( (eecp) >> 8 ) & 0xff ) ) + +/** USB legacy support extended capability */ +#define EHCI_EECP_ID_LEGACY 1 + +/** USB legacy support BIOS owned semaphore */ +#define EHCI_USBLEGSUP_BIOS 0x02 + +/** USB legacy support BIOS ownership flag */ +#define EHCI_USBLEGSUP_BIOS_OWNED 0x01 + +/** USB legacy support OS owned semaphore */ +#define EHCI_USBLEGSUP_OS 0x03 + +/** USB legacy support OS ownership flag */ +#define EHCI_USBLEGSUP_OS_OWNED 0x01 + +/** USB legacy support control/status */ +#define EHCI_USBLEGSUP_CTLSTS 0x04 + +/** USB command register */ +#define EHCI_OP_USBCMD 0x00 + +/** Run/stop */ +#define EHCI_USBCMD_RUN 0x00000001UL + +/** Host controller reset */ +#define EHCI_USBCMD_HCRST 0x00000002UL + +/** Frame list size */ +#define EHCI_USBCMD_FLSIZE(flsize) ( (flsize) << 2 ) + +/** Frame list size mask */ +#define EHCI_USBCMD_FLSIZE_MASK EHCI_USBCMD_FLSIZE ( 3 ) + +/** Default frame list size */ +#define EHCI_FLSIZE_DEFAULT 0 + +/** Smallest allowed frame list size */ +#define EHCI_FLSIZE_SMALL 2 + +/** Number of elements in frame list */ +#define EHCI_PERIODIC_FRAMES(flsize) ( 1024 >> (flsize) ) + +/** Periodic schedule enable */ +#define EHCI_USBCMD_PERIODIC 0x00000010UL + +/** Asynchronous schedule enable */ +#define EHCI_USBCMD_ASYNC 0x00000020UL + +/** Asyncchronous schedule advance doorbell */ +#define EHCI_USBCMD_ASYNC_ADVANCE 0x000040UL + +/** USB status register */ +#define EHCI_OP_USBSTS 0x04 + +/** USB interrupt */ +#define EHCI_USBSTS_USBINT 0x00000001UL + +/** USB error interrupt */ +#define EHCI_USBSTS_USBERRINT 0x00000002UL + +/** Port change detect */ +#define EHCI_USBSTS_PORT 0x00000004UL + +/** Frame list rollover */ +#define EHCI_USBSTS_ROLLOVER 0x00000008UL + +/** Host system error */ +#define EHCI_USBSTS_SYSERR 0x00000010UL + +/** Asynchronous schedule advanced */ +#define EHCI_USBSTS_ASYNC_ADVANCE 0x00000020UL + +/** Periodic schedule enabled */ +#define EHCI_USBSTS_PERIODIC 0x00004000UL + +/** Asynchronous schedule enabled */ +#define EHCI_USBSTS_ASYNC 0x00008000UL + +/** Host controller halted */ +#define EHCI_USBSTS_HCH 0x00001000UL + +/** USB status change mask */ +#define EHCI_USBSTS_CHANGE \ + ( EHCI_USBSTS_USBINT | EHCI_USBSTS_USBERRINT | \ + EHCI_USBSTS_PORT | EHCI_USBSTS_ROLLOVER | \ + EHCI_USBSTS_SYSERR | EHCI_USBSTS_ASYNC_ADVANCE ) + +/** USB interrupt enable register */ +#define EHCI_OP_USBINTR 0x08 + +/** Frame index register */ +#define EHCI_OP_FRINDEX 0x0c + +/** Control data structure segment register */ +#define EHCI_OP_CTRLDSSEGMENT 0x10 + +/** Periodic frame list base address register */ +#define EHCI_OP_PERIODICLISTBASE 0x14 + +/** Current asynchronous list address register */ +#define EHCI_OP_ASYNCLISTADDR 0x18 + +/** Configure flag register */ +#define EHCI_OP_CONFIGFLAG 0x40 + +/** Configure flag */ +#define EHCI_CONFIGFLAG_CF 0x00000001UL + +/** Port status and control register */ +#define EHCI_OP_PORTSC(port) ( 0x40 + ( (port) << 2 ) ) + +/** Current connect status */ +#define EHCI_PORTSC_CCS 0x00000001UL + +/** Connect status change */ +#define EHCI_PORTSC_CSC 0x00000002UL + +/** Port enabled */ +#define EHCI_PORTSC_PED 0x00000004UL + +/** Port enabled/disabled change */ +#define EHCI_PORTSC_PEC 0x00000008UL + +/** Over-current change */ +#define EHCI_PORTSC_OCC 0x00000020UL + +/** Port reset */ +#define EHCI_PORTSC_PR 0x00000100UL + +/** Line status */ +#define EHCI_PORTSC_LINE_STATUS(portsc) ( ( (portsc) >> 10 ) & 0x3 ) + +/** Line status: low-speed device */ +#define EHCI_PORTSC_LINE_STATUS_LOW 0x1 + +/** Port power */ +#define EHCI_PORTSC_PP 0x00001000UL + +/** Port owner */ +#define EHCI_PORTSC_OWNER 0x00002000UL + +/** Port status change mask */ +#define EHCI_PORTSC_CHANGE \ + ( EHCI_PORTSC_CSC | EHCI_PORTSC_PEC | EHCI_PORTSC_OCC ) + +/** List terminator */ +#define EHCI_LINK_TERMINATE 0x00000001UL + +/** Frame list type */ +#define EHCI_LINK_TYPE(type) ( (type) << 1 ) + +/** Queue head type */ +#define EHCI_LINK_TYPE_QH EHCI_LINK_TYPE ( 1 ) + +/** A periodic frame list entry */ +struct ehci_periodic_frame { + /** First queue head */ + uint32_t link; +} __attribute__ (( packed )); + +/** A transfer descriptor */ +struct ehci_transfer_descriptor { + /** Next transfer descriptor */ + uint32_t next; + /** Alternate next transfer descriptor */ + uint32_t alt; + /** Status */ + uint8_t status; + /** Flags */ + uint8_t flags; + /** Transfer length */ + uint16_t len; + /** Buffer pointers (low 32 bits) */ + uint32_t low[5]; + /** Extended buffer pointers (high 32 bits) */ + uint32_t high[5]; + /** Reserved */ + uint8_t reserved[12]; +} __attribute__ (( packed )); + +/** Transaction error */ +#define EHCI_STATUS_XACT_ERR 0x08 + +/** Babble detected */ +#define EHCI_STATUS_BABBLE 0x10 + +/** Data buffer error */ +#define EHCI_STATUS_BUFFER 0x20 + +/** Halted */ +#define EHCI_STATUS_HALTED 0x40 + +/** Active */ +#define EHCI_STATUS_ACTIVE 0x80 + +/** PID code */ +#define EHCI_FL_PID(code) ( (code) << 0 ) + +/** OUT token */ +#define EHCI_FL_PID_OUT EHCI_FL_PID ( 0 ) + +/** IN token */ +#define EHCI_FL_PID_IN EHCI_FL_PID ( 1 ) + +/** SETUP token */ +#define EHCI_FL_PID_SETUP EHCI_FL_PID ( 2 ) + +/** Error counter */ +#define EHCI_FL_CERR( count ) ( (count) << 2 ) + +/** Error counter maximum value */ +#define EHCI_FL_CERR_MAX EHCI_FL_CERR ( 3 ) + +/** Interrupt on completion */ +#define EHCI_FL_IOC 0x80 + +/** Length mask */ +#define EHCI_LEN_MASK 0x7fff + +/** Data toggle */ +#define EHCI_LEN_TOGGLE 0x8000 + +/** A queue head */ +struct ehci_queue_head { + /** Horizontal link pointer */ + uint32_t link; + /** Endpoint characteristics */ + uint32_t chr; + /** Endpoint capabilities */ + uint32_t cap; + /** Current transfer descriptor */ + uint32_t current; + /** Transfer descriptor cache */ + struct ehci_transfer_descriptor cache; +} __attribute__ (( packed )); + +/** Device address */ +#define EHCI_CHR_ADDRESS( address ) ( (address) << 0 ) + +/** Endpoint number */ +#define EHCI_CHR_ENDPOINT( address ) ( ( (address) & 0xf ) << 8 ) + +/** Endpoint speed */ +#define EHCI_CHR_EPS( eps ) ( (eps) << 12 ) + +/** Full-speed endpoint */ +#define EHCI_CHR_EPS_FULL EHCI_CHR_EPS ( 0 ) + +/** Low-speed endpoint */ +#define EHCI_CHR_EPS_LOW EHCI_CHR_EPS ( 1 ) + +/** High-speed endpoint */ +#define EHCI_CHR_EPS_HIGH EHCI_CHR_EPS ( 2 ) + +/** Explicit data toggles */ +#define EHCI_CHR_TOGGLE 0x00004000UL + +/** Head of reclamation list flag */ +#define EHCI_CHR_HEAD 0x00008000UL + +/** Maximum packet length */ +#define EHCI_CHR_MAX_LEN( len ) ( (len) << 16 ) + +/** Control endpoint flag */ +#define EHCI_CHR_CONTROL 0x08000000UL + +/** Interrupt schedule mask */ +#define EHCI_CAP_INTR_SCHED( uframe ) ( 1 << ( (uframe) + 0 ) ) + +/** Split completion schedule mask */ +#define EHCI_CAP_SPLIT_SCHED( uframe ) ( 1 << ( (uframe) + 8 ) ) + +/** Default split completion schedule mask + * + * We schedule all split starts in microframe 0, on the assumption + * that we will never have to deal with more than sixteen actively + * interrupting devices via the same transaction translator. We + * schedule split completions for all remaining microframes after + * microframe 1 (in which the low-speed or full-speed transaction is + * assumed to execute). This is a very crude approximation designed + * to avoid the need for calculating exactly when low-speed and + * full-speed transactions will execute. Since we only ever deal with + * interrupt endpoints (rather than isochronous endpoints), the volume + * of periodic traffic is extremely low, and this approximation should + * remain valid. + */ +#define EHCI_CAP_SPLIT_SCHED_DEFAULT \ + ( EHCI_CAP_SPLIT_SCHED ( 2 ) | EHCI_CAP_SPLIT_SCHED ( 3 ) | \ + EHCI_CAP_SPLIT_SCHED ( 4 ) | EHCI_CAP_SPLIT_SCHED ( 5 ) | \ + EHCI_CAP_SPLIT_SCHED ( 6 ) | EHCI_CAP_SPLIT_SCHED ( 7 ) ) + +/** Transaction translator hub address */ +#define EHCI_CAP_TT_HUB( address ) ( (address) << 16 ) + +/** Transaction translator port number */ +#define EHCI_CAP_TT_PORT( port ) ( (port) << 23 ) + +/** High-bandwidth pipe multiplier */ +#define EHCI_CAP_MULT( mult ) ( (mult) << 30 ) + +/** A transfer descriptor ring */ +struct ehci_ring { + /** Producer counter */ + unsigned int prod; + /** Consumer counter */ + unsigned int cons; + + /** Residual untransferred data */ + size_t residual; + + /** I/O buffers */ + struct io_buffer **iobuf; + + /** Queue head */ + struct ehci_queue_head *head; + /** Transfer descriptors */ + struct ehci_transfer_descriptor *desc; +}; + +/** Number of transfer descriptors in a ring + * + * This is a policy decision. + */ +#define EHCI_RING_COUNT 64 + +/** + * Calculate space used in transfer descriptor ring + * + * @v ring Transfer descriptor ring + * @ret fill Number of entries used + */ +static inline __attribute__ (( always_inline )) unsigned int +ehci_ring_fill ( struct ehci_ring *ring ) { + unsigned int fill; + + fill = ( ring->prod - ring->cons ); + assert ( fill <= EHCI_RING_COUNT ); + return fill; +} + +/** + * Calculate space remaining in transfer descriptor ring + * + * @v ring Transfer descriptor ring + * @ret remaining Number of entries remaining + */ +static inline __attribute__ (( always_inline )) unsigned int +ehci_ring_remaining ( struct ehci_ring *ring ) { + unsigned int fill = ehci_ring_fill ( ring ); + + return ( EHCI_RING_COUNT - fill ); +} + +/** Time to delay after enabling power to a port + * + * This is not mandated by EHCI; we use the value given for xHCI. + */ +#define EHCI_PORT_POWER_DELAY_MS 20 + +/** Time to delay after releasing ownership of a port + * + * This is a policy decision. + */ +#define EHCI_DISOWN_DELAY_MS 100 + +/** Maximum time to wait for BIOS to release ownership + * + * This is a policy decision. + */ +#define EHCI_USBLEGSUP_MAX_WAIT_MS 100 + +/** Maximum time to wait for asynchronous schedule to advance + * + * This is a policy decision. + */ +#define EHCI_ASYNC_ADVANCE_MAX_WAIT_MS 100 + +/** Maximum time to wait for host controller to stop + * + * This is a policy decision. + */ +#define EHCI_STOP_MAX_WAIT_MS 100 + +/** Maximum time to wait for reset to complete + * + * This is a policy decision. + */ +#define EHCI_RESET_MAX_WAIT_MS 500 + +/** Maximum time to wait for a port reset to complete + * + * This is a policy decision. + */ +#define EHCI_PORT_RESET_MAX_WAIT_MS 500 + +/** An EHCI transfer */ +struct ehci_transfer { + /** Data buffer */ + void *data; + /** Length */ + size_t len; + /** Flags + * + * This is the bitwise OR of zero or more EHCI_FL_XXX values. + * The low 8 bits are copied to the flags byte within the + * transfer descriptor; the remaining bits hold flags + * meaningful only to our driver code. + */ + unsigned int flags; +}; + +/** Set initial data toggle */ +#define EHCI_FL_TOGGLE 0x8000 + +/** An EHCI device */ +struct ehci_device { + /** Registers */ + void *regs; + /** Name */ + const char *name; + + /** Capability registers */ + void *cap; + /** Operational registers */ + void *op; + + /** Number of ports */ + unsigned int ports; + /** 64-bit addressing capability */ + int addr64; + /** Frame list size */ + unsigned int flsize; + /** EHCI extended capabilities offset */ + unsigned int eecp; + + /** USB legacy support capability (if present and enabled) */ + unsigned int legacy; + + /** Control data structure segment */ + uint32_t ctrldssegment; + /** Asynchronous queue head */ + struct ehci_queue_head *head; + /** Periodic frame list */ + struct ehci_periodic_frame *frame; + + /** List of all endpoints */ + struct list_head endpoints; + /** Asynchronous schedule */ + struct list_head async; + /** Periodic schedule + * + * Listed in decreasing order of endpoint interval. + */ + struct list_head periodic; + + /** USB bus */ + struct usb_bus *bus; +}; + +/** An EHCI endpoint */ +struct ehci_endpoint { + /** EHCI device */ + struct ehci_device *ehci; + /** USB endpoint */ + struct usb_endpoint *ep; + /** List of all endpoints */ + struct list_head list; + /** Endpoint schedule */ + struct list_head schedule; + + /** Transfer descriptor ring */ + struct ehci_ring ring; +}; + +extern unsigned int ehci_companion ( struct pci_device *pci ); + +#endif /* _IPXE_EHCI_H */ diff --git a/qemu/roms/ipxe/src/drivers/usb/uhci.c b/qemu/roms/ipxe/src/drivers/usb/uhci.c new file mode 100644 index 000000000..b6bb92560 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/uhci.c @@ -0,0 +1,1577 @@ +/* + * 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 <strings.h> +#include <unistd.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/malloc.h> +#include <ipxe/pci.h> +#include <ipxe/usb.h> +#include "ehci.h" +#include "uhci.h" + +/** @file + * + * USB Universal Host Controller Interface (UHCI) driver + * + */ + +/****************************************************************************** + * + * Register access + * + ****************************************************************************** + */ + +/** + * Check that address is reachable + * + * @v addr Address + * @v len Length + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline)) int +uhci_reachable ( void *addr, size_t len ) { + physaddr_t phys = virt_to_phys ( addr ); + + /* Always reachable in a 32-bit build */ + if ( sizeof ( physaddr_t ) <= sizeof ( uint32_t ) ) + return 0; + + /* Reachable if below 4GB */ + if ( ( ( phys + len - 1 ) & ~0xffffffffULL ) == 0 ) + return 0; + + return -ENOTSUP; +} + +/****************************************************************************** + * + * Run / stop / reset + * + ****************************************************************************** + */ + +/** + * Start UHCI device + * + * @v uhci UHCI device + */ +static void uhci_run ( struct uhci_device *uhci ) { + uint16_t usbcmd; + + /* Set run/stop bit */ + usbcmd = inw ( uhci->regs + UHCI_USBCMD ); + usbcmd |= ( UHCI_USBCMD_RUN | UHCI_USBCMD_MAX64 ); + outw ( usbcmd, uhci->regs + UHCI_USBCMD ); +} + +/** + * Stop UHCI device + * + * @v uhci UHCI device + * @ret rc Return status code + */ +static int uhci_stop ( struct uhci_device *uhci ) { + uint16_t usbcmd; + uint16_t usbsts; + unsigned int i; + + /* Clear run/stop bit */ + usbcmd = inw ( uhci->regs + UHCI_USBCMD ); + usbcmd &= ~UHCI_USBCMD_RUN; + outw ( usbcmd, uhci->regs + UHCI_USBCMD ); + + /* Wait for device to stop */ + for ( i = 0 ; i < UHCI_STOP_MAX_WAIT_MS ; i++ ) { + + /* Check if device is stopped */ + usbsts = inw ( uhci->regs + UHCI_USBSTS ); + if ( usbsts & UHCI_USBSTS_HCHALTED ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( uhci, "UHCI %s timed out waiting for stop\n", uhci->name ); + return -ETIMEDOUT; +} + +/** + * Reset UHCI device + * + * @v uhci UHCI device + * @ret rc Return status code + */ +static int uhci_reset ( struct uhci_device *uhci ) { + uint16_t usbcmd; + unsigned int i; + int rc; + + /* The UHCI specification states that resetting a running + * device may result in undefined behaviour, so try stopping + * it first. + */ + if ( ( rc = uhci_stop ( uhci ) ) != 0 ) { + /* Ignore errors and attempt to reset the device anyway */ + } + + /* Reset device */ + outw ( UHCI_USBCMD_HCRESET, uhci->regs + UHCI_USBCMD ); + + /* Wait for reset to complete */ + for ( i = 0 ; i < UHCI_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check if reset is complete */ + usbcmd = inw ( uhci->regs + UHCI_USBCMD ); + if ( ! ( usbcmd & UHCI_USBCMD_HCRESET ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( uhci, "UHCI %s timed out waiting for reset\n", uhci->name ); + return -ETIMEDOUT; +} + +/****************************************************************************** + * + * Transfer descriptor rings + * + ****************************************************************************** + */ + +/** + * Allocate transfer ring + * + * @v ring Transfer ring + * @ret rc Return status code + */ +static int uhci_ring_alloc ( struct uhci_ring *ring ) { + int rc; + + /* Initialise structure */ + memset ( ring, 0, sizeof ( *ring ) ); + + /* Allocate queue head */ + ring->head = malloc_dma ( sizeof ( *ring->head ), UHCI_ALIGN ); + if ( ! ring->head ) { + rc = -ENOMEM; + goto err_alloc; + } + if ( ( rc = uhci_reachable ( ring->head, + sizeof ( *ring->head ) ) ) != 0 ) + goto err_unreachable; + + /* Initialise queue head */ + ring->head->current = cpu_to_le32 ( UHCI_LINK_TERMINATE ); + + return 0; + + err_unreachable: + free_dma ( ring->head, sizeof ( *ring->head ) ); + err_alloc: + return rc; +} + +/** + * Free transfer ring + * + * @v ring Transfer ring + */ +static void uhci_ring_free ( struct uhci_ring *ring ) { + unsigned int i; + + /* Sanity checks */ + assert ( uhci_ring_fill ( ring ) == 0 ); + for ( i = 0 ; i < UHCI_RING_COUNT ; i++ ) + assert ( ring->xfer[i] == NULL ); + + /* Free queue head */ + free_dma ( ring->head, sizeof ( *ring->head ) ); +} + +/** + * Enqueue new transfer + * + * @v ring Transfer ring + * @v iobuf I/O buffer + * @v count Number of descriptors + * @ret rc Return status code + */ +static int uhci_enqueue ( struct uhci_ring *ring, struct io_buffer *iobuf, + unsigned int count ) { + struct uhci_transfer *xfer; + struct uhci_transfer *end; + struct uhci_transfer_descriptor *desc; + unsigned int index = ( ring->prod % UHCI_RING_COUNT ); + uint32_t link; + size_t len; + int rc; + + /* Sanity check */ + assert ( count > 0 ); + assert ( iobuf != NULL ); + + /* Check for space in ring */ + if ( ! uhci_ring_remaining ( ring ) ) { + rc = -ENOBUFS; + goto err_ring_full; + } + + /* Check for reachability of I/O buffer */ + if ( ( rc = uhci_reachable ( iobuf->data, iob_len ( iobuf ) ) ) != 0 ) + goto err_unreachable_iobuf; + + /* Allocate transfer */ + xfer = malloc ( sizeof ( *xfer ) ); + if ( ! xfer ) { + rc = -ENOMEM; + goto err_alloc_xfer; + } + + /* Initialise transfer */ + xfer->prod = 0; + xfer->cons = 0; + xfer->len = 0; + xfer->iobuf = iobuf; + + /* Allocate transfer descriptors */ + len = ( count * sizeof ( xfer->desc[0] ) ); + xfer->desc = malloc_dma ( len, UHCI_ALIGN ); + if ( ! xfer->desc ) { + rc = -ENOMEM; + goto err_alloc_desc; + } + if ( ( rc = uhci_reachable ( xfer->desc, len ) ) != 0 ) + goto err_unreachable_desc; + + /* Initialise transfer descriptors */ + memset ( xfer->desc, 0, len ); + desc = xfer->desc; + for ( ; --count ; desc++ ) { + link = ( virt_to_phys ( desc + 1 ) | UHCI_LINK_DEPTH_FIRST ); + desc->link = cpu_to_le32 ( link ); + desc->flags = ring->flags; + } + desc->link = cpu_to_le32 ( UHCI_LINK_TERMINATE ); + desc->flags = ( ring->flags | UHCI_FL_IOC ); + + /* Add to ring */ + wmb(); + link = virt_to_phys ( xfer->desc ); + if ( uhci_ring_fill ( ring ) > 0 ) { + end = ring->end; + end->desc[ end->prod - 1 ].link = cpu_to_le32 ( link ); + } else { + ring->head->current = cpu_to_le32 ( link ); + } + assert ( ring->xfer[index] == NULL ); + ring->xfer[index] = xfer; + ring->end = xfer; + ring->prod++; + + return 0; + + err_unreachable_desc: + free_dma ( xfer->desc, len ); + err_alloc_desc: + free ( xfer ); + err_alloc_xfer: + err_unreachable_iobuf: + err_ring_full: + return rc; +} + +/** + * Describe transfer + * + * @v ring Transfer ring + * @v data Data + * @v len Length of data + * @v pid Packet ID + */ +static void uhci_describe ( struct uhci_ring *ring, void *data, + size_t len, uint8_t pid ) { + struct uhci_transfer *xfer = ring->end; + struct uhci_transfer_descriptor *desc; + size_t frag_len; + uint32_t control; + + do { + /* Calculate fragment length */ + frag_len = len; + if ( frag_len > ring->mtu ) + frag_len = ring->mtu; + + /* Populate descriptor */ + desc = &xfer->desc[xfer->prod++]; + if ( pid == USB_PID_IN ) + desc->flags |= UHCI_FL_SPD; + control = ( ring->control | UHCI_CONTROL_PID ( pid ) | + UHCI_CONTROL_LEN ( frag_len ) ); + desc->control = cpu_to_le32 ( control ); + if ( data ) + desc->data = virt_to_phys ( data ); + wmb(); + desc->status = UHCI_STATUS_ACTIVE; + + /* Update data toggle */ + ring->control ^= UHCI_CONTROL_TOGGLE; + + /* Move to next descriptor */ + data += frag_len; + len -= frag_len; + + } while ( len ); +} + +/** + * Dequeue transfer + * + * @v ring Transfer ring + * @ret iobuf I/O buffer + */ +static struct io_buffer * uhci_dequeue ( struct uhci_ring *ring ) { + unsigned int index = ( ring->cons % UHCI_RING_COUNT ); + struct io_buffer *iobuf; + struct uhci_transfer *xfer; + size_t len; + + /* Sanity checks */ + assert ( uhci_ring_fill ( ring ) > 0 ); + + /* Consume transfer */ + xfer = ring->xfer[index]; + assert ( xfer != NULL ); + assert ( xfer->desc != NULL ); + iobuf = xfer->iobuf; + assert ( iobuf != NULL ); + ring->xfer[index] = NULL; + ring->cons++; + + /* Free transfer descriptors */ + len = ( xfer->prod * sizeof ( xfer->desc[0] ) ); + free_dma ( xfer->desc, len ); + + /* Free transfer */ + free ( xfer ); + + return iobuf; +} + +/** + * Restart ring + * + * @v ring Transfer ring + * @v toggle Expected data toggle for next descriptor + */ +static void uhci_restart ( struct uhci_ring *ring, uint32_t toggle ) { + struct uhci_transfer *xfer; + struct uhci_transfer_descriptor *desc; + struct uhci_transfer_descriptor *first; + uint32_t link; + unsigned int i; + unsigned int j; + + /* Sanity check */ + assert ( ring->head->current == cpu_to_le32 ( UHCI_LINK_TERMINATE ) ); + + /* If ring is empty, then just update the data toggle for the + * next descriptor. + */ + if ( uhci_ring_fill ( ring ) == 0 ) { + ring->control &= ~UHCI_CONTROL_TOGGLE; + ring->control |= toggle; + return; + } + + /* If expected toggle does not match the toggle in the first + * unconsumed descriptor, then invert all toggles. + */ + xfer = ring->xfer[ ring->cons % UHCI_RING_COUNT ]; + assert ( xfer != NULL ); + assert ( xfer->cons == 0 ); + first = &xfer->desc[0]; + if ( ( le32_to_cpu ( first->control ) ^ toggle ) & UHCI_CONTROL_TOGGLE){ + + /* Invert toggle on all unconsumed transfer descriptors */ + for ( i = ring->cons ; i != ring->prod ; i++ ) { + xfer = ring->xfer[ i % UHCI_RING_COUNT ]; + assert ( xfer != NULL ); + assert ( xfer->cons == 0 ); + for ( j = 0 ; j < xfer->prod ; j++ ) { + desc = &xfer->desc[j]; + desc->control ^= + cpu_to_le32 ( UHCI_CONTROL_TOGGLE ); + } + } + + /* Invert toggle for next descriptor to be enqueued */ + ring->control ^= UHCI_CONTROL_TOGGLE; + } + + /* Restart ring at first unconsumed transfer */ + link = virt_to_phys ( first ); + wmb(); + ring->head->current = cpu_to_le32 ( link ); +} + +/****************************************************************************** + * + * Schedule management + * + ****************************************************************************** + */ + +/** + * Get link value for a queue head + * + * @v queue Queue head + * @ret link Link value + */ +static inline uint32_t uhci_link_qh ( struct uhci_queue_head *queue ) { + + return ( virt_to_phys ( queue ) | UHCI_LINK_TYPE_QH ); +} + +/** + * (Re)build asynchronous schedule + * + * @v uhci UHCI device + */ +static void uhci_async_schedule ( struct uhci_device *uhci ) { + struct uhci_endpoint *endpoint; + struct uhci_queue_head *queue; + uint32_t end; + uint32_t link; + + /* Build schedule in reverse order of execution. Provided + * that we only ever add or remove single endpoints, this can + * safely run concurrently with hardware execution of the + * schedule. + */ + link = end = uhci_link_qh ( uhci->head ); + list_for_each_entry_reverse ( endpoint, &uhci->async, schedule ) { + queue = endpoint->ring.head; + queue->link = cpu_to_le32 ( link ); + wmb(); + link = uhci_link_qh ( queue ); + } + if ( link == end ) + link = UHCI_LINK_TERMINATE; + uhci->head->link = cpu_to_le32 ( link ); + wmb(); +} + +/** + * Add endpoint to asynchronous schedule + * + * @v endpoint Endpoint + */ +static void uhci_async_add ( struct uhci_endpoint *endpoint ) { + struct uhci_device *uhci = endpoint->uhci; + + /* Add to end of schedule */ + list_add_tail ( &endpoint->schedule, &uhci->async ); + + /* Rebuild schedule */ + uhci_async_schedule ( uhci ); +} + +/** + * Remove endpoint from asynchronous schedule + * + * @v endpoint Endpoint + */ +static void uhci_async_del ( struct uhci_endpoint *endpoint ) { + struct uhci_device *uhci = endpoint->uhci; + + /* Remove from schedule */ + list_check_contains_entry ( endpoint, &uhci->async, schedule ); + list_del ( &endpoint->schedule ); + + /* Rebuild schedule */ + uhci_async_schedule ( uhci ); + + /* Delay for a whole USB frame (with a 100% safety margin) */ + mdelay ( 2 ); +} + +/** + * (Re)build periodic schedule + * + * @v uhci UHCI device + */ +static void uhci_periodic_schedule ( struct uhci_device *uhci ) { + struct uhci_endpoint *endpoint; + struct uhci_queue_head *queue; + uint32_t link; + uint32_t end; + unsigned int max_interval; + unsigned int i; + + /* Build schedule in reverse order of execution. Provided + * that we only ever add or remove single endpoints, this can + * safely run concurrently with hardware execution of the + * schedule. + */ + DBGCP ( uhci, "UHCI %s periodic schedule: ", uhci->name ); + link = end = uhci_link_qh ( uhci->head ); + list_for_each_entry_reverse ( endpoint, &uhci->periodic, schedule ) { + queue = endpoint->ring.head; + queue->link = cpu_to_le32 ( link ); + wmb(); + DBGCP ( uhci, "%s%d", ( ( link == end ) ? "" : "<-" ), + endpoint->ep->interval ); + link = uhci_link_qh ( queue ); + } + DBGCP ( uhci, "\n" ); + + /* Populate periodic frame list */ + DBGCP ( uhci, "UHCI %s periodic frame list:", uhci->name ); + for ( i = 0 ; i < UHCI_FRAMES ; i++ ) { + + /* Calculate maximum interval (in microframes) which + * may appear as part of this frame list. + */ + if ( i == 0 ) { + /* Start of list: include all endpoints */ + max_interval = -1U; + } else { + /* Calculate highest power-of-two frame interval */ + max_interval = ( 1 << ( ffs ( i ) - 1 ) ); + /* Convert to microframes */ + max_interval <<= 3; + /* Round up to nearest 2^n-1 */ + max_interval = ( ( max_interval << 1 ) - 1 ); + } + + /* Find first endpoint in schedule satisfying this + * maximum interval constraint. + */ + link = uhci_link_qh ( uhci->head ); + list_for_each_entry ( endpoint, &uhci->periodic, schedule ) { + if ( endpoint->ep->interval <= max_interval ) { + queue = endpoint->ring.head; + link = uhci_link_qh ( queue ); + DBGCP ( uhci, " %d:%d", + i, endpoint->ep->interval ); + break; + } + } + uhci->frame->link[i] = cpu_to_le32 ( link ); + } + wmb(); + DBGCP ( uhci, "\n" ); +} + +/** + * Add endpoint to periodic schedule + * + * @v endpoint Endpoint + */ +static void uhci_periodic_add ( struct uhci_endpoint *endpoint ) { + struct uhci_device *uhci = endpoint->uhci; + struct uhci_endpoint *before; + unsigned int interval = endpoint->ep->interval; + + /* Find first endpoint with a smaller interval */ + list_for_each_entry ( before, &uhci->periodic, schedule ) { + if ( before->ep->interval < interval ) + break; + } + list_add_tail ( &endpoint->schedule, &before->schedule ); + + /* Rebuild schedule */ + uhci_periodic_schedule ( uhci ); +} + +/** + * Remove endpoint from periodic schedule + * + * @v endpoint Endpoint + */ +static void uhci_periodic_del ( struct uhci_endpoint *endpoint ) { + struct uhci_device *uhci = endpoint->uhci; + + /* Remove from schedule */ + list_check_contains_entry ( endpoint, &uhci->periodic, schedule ); + list_del ( &endpoint->schedule ); + + /* Rebuild schedule */ + uhci_periodic_schedule ( uhci ); + + /* Delay for a whole USB frame (with a 100% safety margin) */ + mdelay ( 2 ); +} + +/** + * Add endpoint to appropriate schedule + * + * @v endpoint Endpoint + */ +static void uhci_schedule_add ( struct uhci_endpoint *endpoint ) { + struct usb_endpoint *ep = endpoint->ep; + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + + if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { + uhci_periodic_add ( endpoint ); + } else { + uhci_async_add ( endpoint ); + } +} + +/** + * Remove endpoint from appropriate schedule + * + * @v endpoint Endpoint + */ +static void uhci_schedule_del ( struct uhci_endpoint *endpoint ) { + struct usb_endpoint *ep = endpoint->ep; + unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + + if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { + uhci_periodic_del ( endpoint ); + } else { + uhci_async_del ( endpoint ); + } +} + +/****************************************************************************** + * + * Endpoint operations + * + ****************************************************************************** + */ + +/** + * Open endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int uhci_endpoint_open ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + struct uhci_device *uhci = usb_get_hostdata ( usb ); + struct uhci_endpoint *endpoint; + int rc; + + /* Allocate and initialise structure */ + endpoint = zalloc ( sizeof ( *endpoint ) ); + if ( ! endpoint ) { + rc = -ENOMEM; + goto err_alloc; + } + endpoint->uhci = uhci; + endpoint->ep = ep; + usb_endpoint_set_hostdata ( ep, endpoint ); + + /* Initialise descriptor ring */ + if ( ( rc = uhci_ring_alloc ( &endpoint->ring ) ) != 0 ) + goto err_ring_alloc; + endpoint->ring.mtu = ep->mtu; + endpoint->ring.flags = UHCI_FL_CERR_MAX; + if ( usb->port->speed < USB_SPEED_FULL ) + endpoint->ring.flags |= UHCI_FL_LS; + endpoint->ring.control = ( UHCI_CONTROL_DEVICE ( usb->address ) | + UHCI_CONTROL_ENDPOINT ( ep->address ) ); + + /* Add to list of endpoints */ + list_add_tail ( &endpoint->list, &uhci->endpoints ); + + /* Add to schedule */ + uhci_schedule_add ( endpoint ); + + return 0; + + uhci_ring_free ( &endpoint->ring ); + err_ring_alloc: + free ( endpoint ); + err_alloc: + return rc; +} + +/** + * Close endpoint + * + * @v ep USB endpoint + */ +static void uhci_endpoint_close ( struct usb_endpoint *ep ) { + struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct io_buffer *iobuf; + + /* Remove from schedule */ + uhci_schedule_del ( endpoint ); + + /* Cancel any incomplete transfers */ + while ( uhci_ring_fill ( &endpoint->ring ) ) { + iobuf = uhci_dequeue ( &endpoint->ring ); + if ( iobuf ) + usb_complete_err ( ep, iobuf, -ECANCELED ); + } + + /* Remove from list of endpoints */ + list_del ( &endpoint->list ); + + /* Free descriptor ring */ + uhci_ring_free ( &endpoint->ring ); + + /* Free endpoint */ + free ( endpoint ); +} + +/** + * Reset endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int uhci_endpoint_reset ( struct usb_endpoint *ep ) { + struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct uhci_ring *ring = &endpoint->ring; + + /* Restart ring */ + uhci_restart ( ring, 0 ); + + return 0; +} + +/** + * Update MTU + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int uhci_endpoint_mtu ( struct usb_endpoint *ep ) { + struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + + /* Update endpoint MTU */ + endpoint->ring.mtu = ep->mtu; + + return 0; +} + +/** + * Enqueue message transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int uhci_endpoint_message ( struct usb_endpoint *ep, + struct io_buffer *iobuf ) { + struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct uhci_ring *ring = &endpoint->ring; + struct usb_setup_packet *packet; + unsigned int count; + size_t len; + int input; + int rc; + + /* Calculate number of descriptors */ + assert ( iob_len ( iobuf ) >= sizeof ( *packet ) ); + len = ( iob_len ( iobuf ) - sizeof ( *packet ) ); + count = ( 1 /* setup stage */ + + ( ( len + ring->mtu - 1 ) / ring->mtu ) /* data stage */ + + 1 /* status stage */ ); + + /* Enqueue transfer */ + if ( ( rc = uhci_enqueue ( ring, iobuf, count ) ) != 0 ) + return rc; + + /* Describe setup stage */ + packet = iobuf->data; + ring->control &= ~UHCI_CONTROL_TOGGLE; + uhci_describe ( ring, packet, sizeof ( *packet ), USB_PID_SETUP ); + iob_pull ( iobuf, sizeof ( *packet ) ); + + /* Describe data stage, if applicable */ + assert ( ring->control & UHCI_CONTROL_TOGGLE ); + input = ( packet->request & cpu_to_le16 ( USB_DIR_IN ) ); + if ( len ) { + uhci_describe ( ring, iobuf->data, len, + ( input ? USB_PID_IN : USB_PID_OUT ) ); + } + + /* Describe status stage */ + ring->control |= UHCI_CONTROL_TOGGLE; + uhci_describe ( ring, NULL, 0, + ( ( len && input ) ? USB_PID_OUT : USB_PID_IN ) ); + + /* Sanity check */ + assert ( ring->end->prod == count ); + + return 0; +} + +/** + * 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 uhci_endpoint_stream ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int terminate ) { + struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct uhci_ring *ring = &endpoint->ring; + unsigned int count; + size_t len; + int input; + int zlp; + int rc; + + /* Calculate number of descriptors */ + len = iob_len ( iobuf ); + zlp = ( terminate && ( ( len & ( ring->mtu - 1 ) ) == 0 ) ); + count = ( ( ( len + ring->mtu - 1 ) / ring->mtu ) + ( zlp ? 1 : 0 ) ); + + /* Enqueue transfer */ + if ( ( rc = uhci_enqueue ( ring, iobuf, count ) ) != 0 ) + return rc; + + /* Describe data packet */ + input = ( ep->address & USB_DIR_IN ); + uhci_describe ( ring, iobuf->data, len, + ( input ? USB_PID_IN : USB_PID_OUT ) ); + + /* Describe zero-length packet, if applicable */ + if ( zlp ) + uhci_describe ( ring, NULL, 0, USB_PID_OUT ); + + /* Sanity check */ + assert ( ring->end->prod == count ); + + return 0; +} + +/** + * Check if transfer is a message transfer + * + * @v xfer UHCI transfer + * @ret is_message Transfer is a message transfer + */ +static inline int uhci_is_message ( struct uhci_transfer *xfer ) { + struct uhci_transfer_descriptor *desc = &xfer->desc[0]; + + return ( ( desc->control & cpu_to_le32 ( UHCI_CONTROL_PID_MASK ) ) == + cpu_to_le32 ( UHCI_CONTROL_PID ( USB_PID_SETUP ) ) ); +} + +/** + * Poll for completions + * + * @v endpoint Endpoint + */ +static void uhci_endpoint_poll ( struct uhci_endpoint *endpoint ) { + struct uhci_ring *ring = &endpoint->ring; + struct uhci_device *uhci = endpoint->uhci; + struct usb_endpoint *ep = endpoint->ep; + struct usb_device *usb = ep->usb; + struct uhci_transfer *xfer; + struct uhci_transfer_descriptor *desc; + struct io_buffer *iobuf; + unsigned int index; + uint32_t link; + uint32_t toggle; + uint32_t control; + uint16_t actual; + size_t len; + + /* Consume all completed descriptors */ + while ( uhci_ring_fill ( ring ) ) { + + /* Stop if we reach an uncompleted descriptor */ + index = ( ring->cons % UHCI_RING_COUNT ); + xfer = ring->xfer[index]; + assert ( xfer != NULL ); + assert ( xfer->cons < xfer->prod ); + desc = &xfer->desc[xfer->cons]; + rmb(); + if ( desc->status & UHCI_STATUS_ACTIVE ) + break; + control = le32_to_cpu ( desc->control ); + actual = le16_to_cpu ( desc->actual ); + + /* Update data length, if applicable */ + if ( UHCI_DATA_PACKET ( control ) ) + xfer->len += UHCI_ACTUAL_LEN ( actual ); + + /* If we have encountered an error, then deactivate + * the queue head (to prevent further hardware + * accesses to this transfer), consume the transfer, + * and report the error to the USB core. + */ + if ( desc->status & UHCI_STATUS_STALLED ) { + DBGC ( uhci, "UHCI %s %s completion %d.%d failed " + "(status %02x)\n", usb->name, + usb_endpoint_name ( ep ), index, + xfer->cons, desc->status ); + link = UHCI_LINK_TERMINATE; + ring->head->current = cpu_to_le32 ( link ); + wmb(); + iobuf = uhci_dequeue ( ring ); + usb_complete_err ( ep, iobuf, -EIO ); + break; + } + + /* Consume this descriptor */ + xfer->cons++; + + /* Check for short packets */ + if ( UHCI_SHORT_PACKET ( control, actual ) ) { + + /* Sanity checks */ + assert ( desc->flags & UHCI_FL_SPD ); + link = virt_to_phys ( desc ); + assert ( ( le32_to_cpu ( ring->head->current ) & + ~( UHCI_ALIGN - 1 ) ) == link ); + + /* If this is a message transfer, then restart + * at the status stage. + */ + if ( uhci_is_message ( xfer ) ) { + xfer->cons = ( xfer->prod - 1 ); + link = virt_to_phys ( &xfer->desc[xfer->cons] ); + ring->head->current = cpu_to_le32 ( link ); + break; + } + + /* Otherwise, this is a stream transfer. + * First, prevent further hardware access to + * this transfer. + */ + link = UHCI_LINK_TERMINATE; + ring->head->current = cpu_to_le32 ( link ); + wmb(); + + /* Determine expected data toggle for next descriptor */ + toggle = ( ( control ^ UHCI_CONTROL_TOGGLE ) & + UHCI_CONTROL_TOGGLE ); + + /* Consume this transfer */ + len = xfer->len; + iobuf = uhci_dequeue ( ring ); + + /* Update packet length */ + assert ( len <= iob_len ( iobuf ) ); + iob_unput ( iobuf, ( iob_len ( iobuf ) - len ) ); + + /* Restart ring */ + uhci_restart ( ring, toggle ); + + } else if ( xfer->cons == xfer->prod ) { + + /* Completed a transfer: consume it */ + len = xfer->len; + iobuf = uhci_dequeue ( ring ); + assert ( len == iob_len ( iobuf ) ); + + } else { + + /* Not a short packet and not yet complete: + * continue processing. + */ + continue; + } + + /* Report completion to USB core */ + usb_complete ( ep, iobuf ); + } +} + +/****************************************************************************** + * + * Device operations + * + ****************************************************************************** + */ + +/** + * Open device + * + * @v usb USB device + * @ret rc Return status code + */ +static int uhci_device_open ( struct usb_device *usb ) { + struct uhci_device *uhci = usb_bus_get_hostdata ( usb->port->hub->bus ); + + usb_set_hostdata ( usb, uhci ); + return 0; +} + +/** + * Close device + * + * @v usb USB device + */ +static void uhci_device_close ( struct usb_device *usb ) { + struct uhci_device *uhci = usb_get_hostdata ( usb ); + struct usb_bus *bus = uhci->bus; + + /* Free device address, if assigned */ + if ( usb->address ) + usb_free_address ( bus, usb->address ); +} + +/** + * Assign device address + * + * @v usb USB device + * @ret rc Return status code + */ +static int uhci_device_address ( struct usb_device *usb ) { + struct uhci_device *uhci = usb_get_hostdata ( usb ); + struct usb_bus *bus = uhci->bus; + struct usb_endpoint *ep0 = usb_endpoint ( usb, USB_EP0_ADDRESS ); + struct uhci_endpoint *endpoint0 = usb_endpoint_get_hostdata ( ep0 ); + int address; + int rc; + + /* Sanity checks */ + assert ( usb->address == 0 ); + assert ( ep0 != NULL ); + + /* Allocate device address */ + address = usb_alloc_address ( bus ); + if ( address < 0 ) { + rc = address; + DBGC ( uhci, "UHCI %s could not allocate address: %s\n", + usb->name, strerror ( rc ) ); + goto err_alloc_address; + } + + /* Set address */ + if ( ( rc = usb_set_address ( usb, address ) ) != 0 ) + goto err_set_address; + + /* Update device address */ + usb->address = address; + endpoint0->ring.control |= UHCI_CONTROL_DEVICE ( address ); + + return 0; + + err_set_address: + usb_free_address ( bus, address ); + err_alloc_address: + return rc; +} + +/****************************************************************************** + * + * Hub operations + * + ****************************************************************************** + */ + +/** + * Open hub + * + * @v hub USB hub + * @ret rc Return status code + */ +static int uhci_hub_open ( struct usb_hub *hub __unused ) { + + /* Nothing to do */ + return 0; +} + +/** + * Close hub + * + * @v hub USB hub + */ +static void uhci_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 uhci_root_open ( struct usb_hub *hub ) { + struct usb_bus *bus = hub->bus; + struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); + + /* Record hub driver private data */ + usb_hub_set_drvdata ( hub, uhci ); + + return 0; +} + +/** + * Close root hub + * + * @v hub USB hub + */ +static void uhci_root_close ( struct usb_hub *hub ) { + + /* Clear hub driver private data */ + usb_hub_set_drvdata ( hub, NULL ); +} + +/** + * Enable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int uhci_root_enable ( struct usb_hub *hub, struct usb_port *port ) { + struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); + uint16_t portsc; + unsigned int i; + + /* Reset port */ + portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); + portsc |= UHCI_PORTSC_PR; + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + mdelay ( USB_RESET_DELAY_MS ); + portsc &= ~UHCI_PORTSC_PR; + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + mdelay ( USB_RESET_RECOVER_DELAY_MS ); + + /* Enable port */ + portsc |= UHCI_PORTSC_PED; + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + mdelay ( USB_RESET_RECOVER_DELAY_MS ); + + /* Wait for port to become enabled */ + for ( i = 0 ; i < UHCI_PORT_ENABLE_MAX_WAIT_MS ; i++ ) { + + /* Check port status */ + portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); + if ( portsc & UHCI_PORTSC_PED ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( uhci, "UHCI %s-%d timed out waiting for port to enable " + "(status %04x)\n", uhci->name, port->address, portsc ); + return -ETIMEDOUT; +} + +/** + * Disable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int uhci_root_disable ( struct usb_hub *hub, struct usb_port *port ) { + struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); + uint16_t portsc; + + /* Disable port */ + portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); + portsc &= ~UHCI_PORTSC_PED; + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + + return 0; +} + +/** + * Update root hub port speed + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int uhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) { + struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); + struct pci_device pci; + uint16_t portsc; + unsigned int speed; + + /* Read port status */ + portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); + if ( ! ( portsc & UHCI_PORTSC_CCS ) ) { + /* Port not connected */ + speed = USB_SPEED_NONE; + } else if ( uhci->companion && + ! find_usb_bus_by_location ( BUS_TYPE_PCI, + uhci->companion ) ) { + /* Defer connection detection until companion + * controller has been enumerated. + */ + pci_init ( &pci, uhci->companion ); + DBGC ( uhci, "UHCI %s-%d deferring for companion " PCI_FMT "\n", + uhci->name, port->address, PCI_ARGS ( &pci ) ); + speed = USB_SPEED_NONE; + } else if ( portsc & UHCI_PORTSC_LS ) { + /* Low-speed device */ + speed = USB_SPEED_LOW; + } else { + /* Full-speed device */ + speed = USB_SPEED_FULL; + } + port->speed = speed; + + /* Record disconnections and clear changes */ + port->disconnected |= ( portsc & UHCI_PORTSC_CSC ); + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + + 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 uhci_root_clear_tt ( struct usb_hub *hub, struct usb_port *port, + struct usb_endpoint *ep ) { + struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); + + /* Should never be called; this is a root hub */ + DBGC ( uhci, "UHCI %s-%d nonsensical CLEAR_TT for %s %s\n", uhci->name, + port->address, ep->usb->name, usb_endpoint_name ( ep ) ); + + return -ENOTSUP; +} + +/** + * Poll for port status changes + * + * @v hub USB hub + * @v port USB port + */ +static void uhci_root_poll ( struct usb_hub *hub, struct usb_port *port ) { + struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); + uint16_t portsc; + uint16_t change; + + /* Do nothing unless something has changed */ + portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); + change = ( portsc & UHCI_PORTSC_CHANGE ); + if ( ! change ) + return; + + /* Record disconnections and clear changes */ + port->disconnected |= ( portsc & UHCI_PORTSC_CSC ); + outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); + + /* Report port status change */ + usb_port_changed ( port ); +} + +/****************************************************************************** + * + * Bus operations + * + ****************************************************************************** + */ + +/** + * Open USB bus + * + * @v bus USB bus + * @ret rc Return status code + */ +static int uhci_bus_open ( struct usb_bus *bus ) { + struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); + int rc; + + /* Sanity checks */ + assert ( list_empty ( &uhci->async ) ); + assert ( list_empty ( &uhci->periodic ) ); + + /* Allocate and initialise asynchronous queue head */ + uhci->head = malloc_dma ( sizeof ( *uhci->head ), UHCI_ALIGN ); + if ( ! uhci->head ) { + rc = -ENOMEM; + goto err_alloc_head; + } + if ( ( rc = uhci_reachable ( uhci->head, sizeof ( *uhci->head ) ) ) !=0) + goto err_unreachable_head; + memset ( uhci->head, 0, sizeof ( *uhci->head ) ); + uhci->head->current = cpu_to_le32 ( UHCI_LINK_TERMINATE ); + uhci_async_schedule ( uhci ); + + /* Allocate periodic frame list */ + uhci->frame = malloc_dma ( sizeof ( *uhci->frame ), + sizeof ( *uhci->frame ) ); + if ( ! uhci->frame ) { + rc = -ENOMEM; + goto err_alloc_frame; + } + if ( ( rc = uhci_reachable ( uhci->frame, + sizeof ( *uhci->frame ) ) ) != 0 ) + goto err_unreachable_frame; + uhci_periodic_schedule ( uhci ); + outl ( virt_to_phys ( uhci->frame ), uhci->regs + UHCI_FLBASEADD ); + + /* Start controller */ + uhci_run ( uhci ); + + return 0; + + uhci_stop ( uhci ); + err_unreachable_frame: + free_dma ( uhci->frame, sizeof ( *uhci->frame ) ); + err_alloc_frame: + err_unreachable_head: + free_dma ( uhci->head, sizeof ( *uhci->head ) ); + err_alloc_head: + return rc; +} + +/** + * Close USB bus + * + * @v bus USB bus + */ +static void uhci_bus_close ( struct usb_bus *bus ) { + struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); + + /* Sanity checks */ + assert ( list_empty ( &uhci->async ) ); + assert ( list_empty ( &uhci->periodic ) ); + + /* Stop controller */ + uhci_stop ( uhci ); + + /* Free periodic frame list */ + free_dma ( uhci->frame, sizeof ( *uhci->frame ) ); + + /* Free asynchronous schedule */ + free_dma ( uhci->head, sizeof ( *uhci->head ) ); +} + +/** + * Poll USB bus + * + * @v bus USB bus + */ +static void uhci_bus_poll ( struct usb_bus *bus ) { + struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); + struct usb_hub *hub = bus->hub; + struct uhci_endpoint *endpoint; + unsigned int i; + + /* UHCI defers interrupts (including short packet detection) + * until the end of the frame. This can result in bulk IN + * endpoints remaining halted for much of the time, waiting + * for software action to reset the data toggles. We + * therefore ignore USBSTS and unconditionally poll all + * endpoints for completed transfer descriptors. + * + * As with EHCI, 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, &uhci->endpoints, list ) + uhci_endpoint_poll ( endpoint ); + + /* UHCI provides no single bit to indicate that a port status + * change has occurred. We therefore unconditionally iterate + * over all ports looking for status changes. + */ + for ( i = 1 ; i <= UHCI_PORTS ; i++ ) + uhci_root_poll ( hub, usb_port ( hub, i ) ); +} + +/****************************************************************************** + * + * PCI interface + * + ****************************************************************************** + */ + +/** USB host controller operations */ +static struct usb_host_operations uhci_operations = { + .endpoint = { + .open = uhci_endpoint_open, + .close = uhci_endpoint_close, + .reset = uhci_endpoint_reset, + .mtu = uhci_endpoint_mtu, + .message = uhci_endpoint_message, + .stream = uhci_endpoint_stream, + }, + .device = { + .open = uhci_device_open, + .close = uhci_device_close, + .address = uhci_device_address, + }, + .bus = { + .open = uhci_bus_open, + .close = uhci_bus_close, + .poll = uhci_bus_poll, + }, + .hub = { + .open = uhci_hub_open, + .close = uhci_hub_close, + }, + .root = { + .open = uhci_root_open, + .close = uhci_root_close, + .enable = uhci_root_enable, + .disable = uhci_root_disable, + .speed = uhci_root_speed, + .clear_tt = uhci_root_clear_tt, + }, +}; + +/** + * Locate EHCI companion controller (when no EHCI support is present) + * + * @v pci PCI device + * @ret busdevfn EHCI companion controller bus:dev.fn (if any) + */ +__weak unsigned int ehci_companion ( struct pci_device *pci __unused ) { + return 0; +} + +/** + * Probe PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +static int uhci_probe ( struct pci_device *pci ) { + struct uhci_device *uhci; + struct usb_port *port; + unsigned int i; + int rc; + + /* Allocate and initialise structure */ + uhci = zalloc ( sizeof ( *uhci ) ); + if ( ! uhci ) { + rc = -ENOMEM; + goto err_alloc; + } + uhci->name = pci->dev.name; + INIT_LIST_HEAD ( &uhci->endpoints ); + INIT_LIST_HEAD ( &uhci->async ); + INIT_LIST_HEAD ( &uhci->periodic ); + + /* Fix up PCI device */ + adjust_pci_device ( pci ); + + /* Identify EHCI companion controller, if any */ + uhci->companion = ehci_companion ( pci ); + + /* Claim ownership from BIOS. (There is no release mechanism + * for UHCI.) + */ + pci_write_config_word ( pci, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT ); + + /* Map registers */ + uhci->regs = pci->ioaddr; + if ( ! uhci->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Reset device */ + if ( ( rc = uhci_reset ( uhci ) ) != 0 ) + goto err_reset; + + /* Allocate USB bus */ + uhci->bus = alloc_usb_bus ( &pci->dev, UHCI_PORTS, UHCI_MTU, + &uhci_operations ); + if ( ! uhci->bus ) { + rc = -ENOMEM; + goto err_alloc_bus; + } + usb_bus_set_hostdata ( uhci->bus, uhci ); + usb_hub_set_drvdata ( uhci->bus->hub, uhci ); + + /* Set port protocols */ + for ( i = 1 ; i <= UHCI_PORTS ; i++ ) { + port = usb_port ( uhci->bus->hub, i ); + port->protocol = USB_PROTO_2_0; + } + + /* Register USB bus */ + if ( ( rc = register_usb_bus ( uhci->bus ) ) != 0 ) + goto err_register; + + pci_set_drvdata ( pci, uhci ); + return 0; + + unregister_usb_bus ( uhci->bus ); + err_register: + free_usb_bus ( uhci->bus ); + err_alloc_bus: + uhci_reset ( uhci ); + err_reset: + err_ioremap: + free ( uhci ); + err_alloc: + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void uhci_remove ( struct pci_device *pci ) { + struct uhci_device *uhci = pci_get_drvdata ( pci ); + struct usb_bus *bus = uhci->bus; + + unregister_usb_bus ( bus ); + assert ( list_empty ( &uhci->async ) ); + assert ( list_empty ( &uhci->periodic ) ); + free_usb_bus ( bus ); + uhci_reset ( uhci ); + free ( uhci ); +} + +/** UHCI PCI device IDs */ +static struct pci_device_id uhci_ids[] = { + PCI_ROM ( 0xffff, 0xffff, "uhci", "UHCI", 0 ), +}; + +/** UHCI PCI driver */ +struct pci_driver uhci_driver __pci_driver = { + .ids = uhci_ids, + .id_count = ( sizeof ( uhci_ids ) / sizeof ( uhci_ids[0] ) ), + .class = PCI_CLASS_ID ( PCI_CLASS_SERIAL, PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_UHCI ), + .probe = uhci_probe, + .remove = uhci_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/usb/uhci.h b/qemu/roms/ipxe/src/drivers/usb/uhci.h new file mode 100644 index 000000000..ba4c28f7e --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/uhci.h @@ -0,0 +1,350 @@ +#ifndef _IPXE_UHCI_H +#define _IPXE_UHCI_H + +/** @file + * + * USB Universal Host Controller Interface (UHCI) driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <assert.h> +#include <ipxe/pci.h> +#include <ipxe/usb.h> + +/** Minimum alignment required for data structures + * + * With the exception of the frame list (which is page-aligned), data + * structures used by UHCI generally require 16-byte alignment. + */ +#define UHCI_ALIGN 16 + +/** Number of ports */ +#define UHCI_PORTS 2 + +/** Maximum transfer size */ +#define UHCI_MTU 1280 + +/** I/O BAR size */ +#define UHCI_BAR_SIZE 0x14 + +/** USB command register */ +#define UHCI_USBCMD 0x00 + +/** Max packet is 64 bytes */ +#define UHCI_USBCMD_MAX64 0x0080 + +/** Host controller reset */ +#define UHCI_USBCMD_HCRESET 0x0002 + +/** Run/stop */ +#define UHCI_USBCMD_RUN 0x0001 + +/** USB status register */ +#define UHCI_USBSTS 0x02 + +/** Host controller halted */ +#define UHCI_USBSTS_HCHALTED 0x0020 + +/** USB interrupt */ +#define UHCI_USBSTS_USBINT 0x0001 + +/** Frame list base address register */ +#define UHCI_FLBASEADD 0x08 + +/** Port status and control register */ +#define UHCI_PORTSC(port) ( 0x0e + ( (port) << 1 ) ) + +/** Port reset */ +#define UHCI_PORTSC_PR 0x0200 + +/** Low-speed device attached */ +#define UHCI_PORTSC_LS 0x0100 + +/** Port enabled/disabled change */ +#define UHCI_PORTSC_PEC 0x0008 + +/** Port enabled */ +#define UHCI_PORTSC_PED 0x0004 + +/** Connect status change */ +#define UHCI_PORTSC_CSC 0x0002 + +/** Current connect status */ +#define UHCI_PORTSC_CCS 0x0001 + +/** Port status change mask */ +#define UHCI_PORTSC_CHANGE ( UHCI_PORTSC_CSC | UHCI_PORTSC_PEC ) + +/** Depth-first processing */ +#define UHCI_LINK_DEPTH_FIRST 0x00000004UL + +/** Queue head type */ +#define UHCI_LINK_TYPE_QH 0x00000002UL + +/** List terminator */ +#define UHCI_LINK_TERMINATE 0x00000001UL + +/** Number of frames in frame list */ +#define UHCI_FRAMES 1024 + +/** A frame list */ +struct uhci_frame_list { + /** Link pointer */ + uint32_t link[UHCI_FRAMES]; +} __attribute__ (( packed )); + +/** A transfer descriptor */ +struct uhci_transfer_descriptor { + /** Link pointer */ + uint32_t link; + /** Actual length */ + uint16_t actual; + /** Status */ + uint8_t status; + /** Flags */ + uint8_t flags; + /** Control */ + uint32_t control; + /** Buffer pointer */ + uint32_t data; +} __attribute__ (( packed )); + +/** Length mask */ +#define UHCI_LEN_MASK 0x7ff + +/** Actual length */ +#define UHCI_ACTUAL_LEN( actual ) ( ( (actual) + 1 ) & UHCI_LEN_MASK ) + +/** Active */ +#define UHCI_STATUS_ACTIVE 0x80 + +/** Stalled */ +#define UHCI_STATUS_STALLED 0x40 + +/** Data buffer error */ +#define UHCI_STATUS_BUFFER 0x20 + +/** Babble detected */ +#define UHCI_STATUS_BABBLE 0x10 + +/** NAK received */ +#define UHCI_STATUS_NAK 0x08 + +/** CRC/timeout error */ +#define UHCI_STATUS_CRC_TIMEOUT 0x04 + +/** Bitstuff error */ +#define UHCI_STATUS_BITSTUFF 0x02 + +/** Short packet detect */ +#define UHCI_FL_SPD 0x20 + +/** Error counter */ +#define UHCI_FL_CERR( count ) ( (count) << 3 ) + +/** Error counter maximum value */ +#define UHCI_FL_CERR_MAX UHCI_FL_CERR ( 3 ) + +/** Low speed device */ +#define UHCI_FL_LS 0x04 + +/** Interrupt on completion */ +#define UHCI_FL_IOC 0x01 + +/** Packet ID */ +#define UHCI_CONTROL_PID( pid ) ( (pid) << 0 ) + +/** Packet ID mask */ +#define UHCI_CONTROL_PID_MASK UHCI_CONTROL_PID ( 0xff ) + +/** Device address */ +#define UHCI_CONTROL_DEVICE( address ) ( (address) << 8 ) + +/** Endpoint address */ +#define UHCI_CONTROL_ENDPOINT( address ) ( (address) << 15 ) + +/** Data toggle */ +#define UHCI_CONTROL_TOGGLE ( 1 << 19 ) + +/** Data length */ +#define UHCI_CONTROL_LEN( len ) ( ( ( (len) - 1 ) & UHCI_LEN_MASK ) << 21 ) + +/** Check for data packet + * + * This check is based on the fact that only USB_PID_SETUP has bit 2 + * set. + */ +#define UHCI_DATA_PACKET( control ) ( ! ( control & 0x04 ) ) + +/** Check for short packet */ +#define UHCI_SHORT_PACKET( control, actual ) \ + ( ( ( (control) >> 21 ) ^ (actual) ) & UHCI_LEN_MASK ) + +/** USB legacy support register (in PCI configuration space) */ +#define UHCI_USBLEGSUP 0xc0 + +/** USB legacy support default value */ +#define UHCI_USBLEGSUP_DEFAULT 0x2000 + +/** A queue head */ +struct uhci_queue_head { + /** Horizontal link pointer */ + uint32_t link; + /** Current transfer descriptor */ + uint32_t current; +} __attribute__ (( packed )); + +/** A single UHCI transfer + * + * UHCI hardware is extremely simple, and requires software to build + * the entire packet schedule (including manually handling all of the + * data toggles). The hardware requires at least 16 bytes of transfer + * descriptors per 64 bytes of transmitted/received data. We allocate + * the transfer descriptors at the time that the transfer is enqueued, + * to avoid the need to allocate unreasonably large blocks when the + * endpoint is opened. + */ +struct uhci_transfer { + /** Producer counter */ + unsigned int prod; + /** Consumer counter */ + unsigned int cons; + /** Completed data length */ + size_t len; + + /** Transfer descriptors */ + struct uhci_transfer_descriptor *desc; + + /** I/O buffer */ + struct io_buffer *iobuf; +}; + +/** Number of transfer descriptors in a ring + * + * This is a policy decision. + */ +#define UHCI_RING_COUNT 16 + +/** A transfer ring */ +struct uhci_ring { + /** Producer counter */ + unsigned int prod; + /** Consumer counter */ + unsigned int cons; + + /** Maximum packet length */ + size_t mtu; + /** Base flags + * + * This incorporates the CERR and LS bits + */ + uint8_t flags; + /** Base control word + * + * This incorporates the device address, the endpoint address, + * and the data toggle for the next descriptor to be enqueued. + */ + uint32_t control; + + /** Transfers */ + struct uhci_transfer *xfer[UHCI_RING_COUNT]; + /** End of transfer ring (if non-empty) */ + struct uhci_transfer *end; + + /** Queue head */ + struct uhci_queue_head *head; +}; + +/** + * Calculate space used in transfer ring + * + * @v ring Transfer ring + * @ret fill Number of entries used + */ +static inline __attribute__ (( always_inline )) unsigned int +uhci_ring_fill ( struct uhci_ring *ring ) { + unsigned int fill; + + fill = ( ring->prod - ring->cons ); + assert ( fill <= UHCI_RING_COUNT ); + return fill; +} + +/** + * Calculate space remaining in transfer ring + * + * @v ring Transfer ring + * @ret remaining Number of entries remaining + */ +static inline __attribute__ (( always_inline )) unsigned int +uhci_ring_remaining ( struct uhci_ring *ring ) { + unsigned int fill = uhci_ring_fill ( ring ); + + return ( UHCI_RING_COUNT - fill ); +} + +/** Maximum time to wait for host controller to stop + * + * This is a policy decision. + */ +#define UHCI_STOP_MAX_WAIT_MS 100 + +/** Maximum time to wait for reset to complete + * + * This is a policy decision. + */ +#define UHCI_RESET_MAX_WAIT_MS 500 + +/** Maximum time to wait for a port to be enabled + * + * This is a policy decision. + */ +#define UHCI_PORT_ENABLE_MAX_WAIT_MS 500 + +/** A UHCI device */ +struct uhci_device { + /** Registers */ + unsigned long regs; + /** Name */ + const char *name; + + /** EHCI companion controller bus:dev.fn address (if any) */ + unsigned int companion; + + /** Asynchronous queue head */ + struct uhci_queue_head *head; + /** Frame list */ + struct uhci_frame_list *frame; + + /** List of all endpoints */ + struct list_head endpoints; + /** Asynchronous schedule */ + struct list_head async; + /** Periodic schedule + * + * Listed in decreasing order of endpoint interval. + */ + struct list_head periodic; + + /** USB bus */ + struct usb_bus *bus; +}; + +/** A UHCI endpoint */ +struct uhci_endpoint { + /** UHCI device */ + struct uhci_device *uhci; + /** USB endpoint */ + struct usb_endpoint *ep; + /** List of all endpoints */ + struct list_head list; + /** Endpoint schedule */ + struct list_head schedule; + + /** Transfer ring */ + struct uhci_ring ring; +}; + +#endif /* _IPXE_UHCI_H */ diff --git a/qemu/roms/ipxe/src/drivers/usb/usbhid.c b/qemu/roms/ipxe/src/drivers/usb/usbhid.c new file mode 100644 index 000000000..c74535a05 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbhid.c @@ -0,0 +1,151 @@ +/* + * 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 <string.h> +#include <errno.h> +#include <ipxe/usb.h> +#include <ipxe/usbhid.h> + +/** @file + * + * USB human interface devices (HID) + * + */ + +/** + * Open USB human interface device + * + * @v hid USB human interface device + * @ret rc Return status code + */ +int usbhid_open ( struct usb_hid *hid ) { + int rc; + + /* Open interrupt IN endpoint */ + if ( ( rc = usb_endpoint_open ( &hid->in ) ) != 0 ) { + DBGC ( hid, "HID %s could not open interrupt IN: %s\n", + hid->func->name, strerror ( rc ) ); + goto err_open_in; + } + + /* Refill interrupt IN endpoint */ + if ( ( rc = usb_refill ( &hid->in ) ) != 0 ) { + DBGC ( hid, "HID %s could not refill interrupt IN: %s\n", + hid->func->name, strerror ( rc ) ); + goto err_refill_in; + } + + /* Open interrupt OUT endpoint, if applicable */ + if ( hid->out.usb && + ( ( rc = usb_endpoint_open ( &hid->out ) ) != 0 ) ) { + DBGC ( hid, "HID %s could not open interrupt OUT: %s\n", + hid->func->name, strerror ( rc ) ); + goto err_open_out; + } + + return 0; + + usb_endpoint_close ( &hid->out ); + err_open_out: + err_refill_in: + usb_endpoint_close ( &hid->in ); + err_open_in: + return rc; +} + +/** + * Close USB human interface device + * + * @v hid USB human interface device + */ +void usbhid_close ( struct usb_hid *hid ) { + + /* Close interrupt OUT endpoint, if applicable */ + if ( hid->out.usb ) + usb_endpoint_close ( &hid->out ); + + /* Close interrupt IN endpoint */ + usb_endpoint_close ( &hid->in ); +} + +/** + * Refill USB human interface device endpoints + * + * @v hid USB human interface device + * @ret rc Return status code + */ +int usbhid_refill ( struct usb_hid *hid ) { + int rc; + + /* Refill interrupt IN endpoint */ + if ( ( rc = usb_refill ( &hid->in ) ) != 0 ) + return rc; + + /* Refill interrupt OUT endpoint, if applicable */ + if ( hid->out.usb && ( ( rc = usb_refill ( &hid->out ) ) != 0 ) ) + return rc; + + return 0; +} + +/** + * Describe USB human interface device + * + * @v hid USB human interface device + * @v config Configuration descriptor + * @ret rc Return status code + */ +int usbhid_describe ( struct usb_hid *hid, + struct usb_configuration_descriptor *config ) { + struct usb_interface_descriptor *desc; + int rc; + + /* Locate interface descriptor */ + desc = usb_interface_descriptor ( config, hid->func->interface[0], 0 ); + if ( ! desc ) { + DBGC ( hid, "HID %s has no interface descriptor\n", + hid->func->name ); + return -EINVAL; + } + + /* Describe interrupt IN endpoint */ + if ( ( rc = usb_endpoint_described ( &hid->in, config, desc, + USB_INTERRUPT_IN, 0 ) ) != 0 ) { + DBGC ( hid, "HID %s could not describe interrupt IN: %s\n", + hid->func->name, strerror ( rc ) ); + return rc; + } + + /* Describe interrupt OUT endpoint, if applicable */ + if ( hid->out.usb && + ( ( rc = usb_endpoint_described ( &hid->out, config, desc, + USB_INTERRUPT_OUT, 0 ) ) != 0 )){ + DBGC ( hid, "HID %s could not describe interrupt OUT: %s\n", + hid->func->name, strerror ( rc ) ); + return rc; + } + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/usb/usbhub.c b/qemu/roms/ipxe/src/drivers/usb/usbhub.c new file mode 100644 index 000000000..bf2a20005 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbhub.c @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2014 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 <unistd.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/usb.h> +#include "usbhub.h" + +/** @file + * + * USB hub driver + * + */ + +/** + * Refill interrupt ring + * + * @v hubdev Hub device + */ +static void hub_refill ( struct usb_hub_device *hubdev ) { + int rc; + + /* Refill interrupt endpoint */ + if ( ( rc = usb_refill ( &hubdev->intr ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not refill interrupt: %s\n", + hubdev->name, strerror ( rc ) ); + /* Continue attempting to refill */ + return; + } + + /* Stop refill process */ + process_del ( &hubdev->refill ); +} + +/** Refill process descriptor */ +static struct process_descriptor hub_refill_desc = + PROC_DESC ( struct usb_hub_device, refill, hub_refill ); + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void hub_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct usb_hub_device *hubdev = + container_of ( ep, struct usb_hub_device, intr ); + struct usb_hub *hub = hubdev->hub; + uint8_t *data = iobuf->data; + unsigned int bits = ( 8 * iob_len ( iobuf ) ); + unsigned int i; + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto done; + + /* Ignore packets with errors */ + if ( rc != 0 ) { + DBGC ( hubdev, "HUB %s interrupt failed: %s\n", + hubdev->name, strerror ( rc ) ); + DBGC_HDA ( hubdev, 0, iobuf->data, iob_len ( iobuf ) ); + goto done; + } + + /* Report any port status changes */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + + /* Sanity check */ + if ( i > bits ) { + DBGC ( hubdev, "HUB %s underlength interrupt:\n", + hubdev->name ); + DBGC_HDA ( hubdev, 0, iobuf->data, iob_len ( iobuf ) ); + goto done; + } + + /* Report port status change if applicable */ + if ( data[ i / 8 ] & ( 1 << ( i % 8 ) ) ) { + DBGC2 ( hubdev, "HUB %s port %d status changed\n", + hubdev->name, i ); + usb_port_changed ( usb_port ( hub, i ) ); + } + } + + done: + /* Start refill process */ + process_add ( &hubdev->refill ); +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations usb_hub_intr_operations = { + .complete = hub_complete, +}; + +/** + * Open hub + * + * @v hub USB hub + * @ret rc Return status code + */ +static int hub_open ( struct usb_hub *hub ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + struct usb_device *usb = hubdev->usb; + unsigned int i; + int rc; + + /* Ensure ports are powered */ + for ( i = 1 ; i <= hub->ports ; i++ ) { + if ( ( rc = usb_hub_set_port_feature ( usb, i, + USB_HUB_PORT_POWER, + 0 ) ) != 0 ) { + DBGC ( hubdev, "HUB %s port %d could not apply power: " + "%s\n", hubdev->name, i, strerror ( rc ) ); + goto err_power; + } + } + + /* Open interrupt endpoint */ + if ( ( rc = usb_endpoint_open ( &hubdev->intr ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not register interrupt: %s\n", + hubdev->name, strerror ( rc ) ); + goto err_open; + } + + /* Start refill process */ + process_add ( &hubdev->refill ); + + /* Refill interrupt ring */ + hub_refill ( hubdev ); + + return 0; + + usb_endpoint_close ( &hubdev->intr ); + err_open: + err_power: + return rc; +} + +/** + * Close hub + * + * @v hub USB hub + */ +static void hub_close ( struct usb_hub *hub ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + + /* Close interrupt endpoint */ + usb_endpoint_close ( &hubdev->intr ); + + /* Stop refill process */ + process_del ( &hubdev->refill ); +} + +/** + * Enable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int hub_enable ( struct usb_hub *hub, struct usb_port *port ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + struct usb_device *usb = hubdev->usb; + struct usb_hub_port_status status; + unsigned int current; + unsigned int i; + int rc; + + /* Initiate reset if applicable */ + if ( ( hub->protocol < USB_PROTO_3_0 ) && + ( ( rc = usb_hub_set_port_feature ( usb, port->address, + USB_HUB_PORT_RESET, 0 ) )!=0)){ + DBGC ( hubdev, "HUB %s port %d could not initiate reset: %s\n", + hubdev->name, port->address, strerror ( rc ) ); + return rc; + } + + /* Wait for port to become enabled */ + for ( i = 0 ; i < USB_HUB_ENABLE_MAX_WAIT_MS ; i++ ) { + + /* Check for port being enabled */ + if ( ( rc = usb_hub_get_port_status ( usb, port->address, + &status ) ) != 0 ) { + DBGC ( hubdev, "HUB %s port %d could not get status: " + "%s\n", hubdev->name, port->address, + strerror ( rc ) ); + return rc; + } + current = le16_to_cpu ( status.current ); + if ( current & ( 1 << USB_HUB_PORT_ENABLE ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( hubdev, "HUB %s port %d timed out waiting for enable\n", + hubdev->name, port->address ); + return -ETIMEDOUT; +} + +/** + * Disable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int hub_disable ( struct usb_hub *hub, struct usb_port *port ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + struct usb_device *usb = hubdev->usb; + int rc; + + /* Disable port */ + if ( ( rc = usb_hub_clear_port_feature ( usb, port->address, + USB_HUB_PORT_ENABLE, 0 ) )!=0){ + DBGC ( hubdev, "HUB %s port %d could not disable: %s\n", + hubdev->name, port->address, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Clear port status change bits + * + * @v hubdev USB hub device + * @v port Port number + * @v changed Port status change bits + * @ret rc Return status code + */ +static int hub_clear_changes ( struct usb_hub_device *hubdev, + unsigned int port, uint16_t changed ) { + struct usb_device *usb = hubdev->usb; + unsigned int bit; + unsigned int feature; + int rc; + + /* Clear each set bit */ + for ( bit = 0 ; bit < 16 ; bit++ ) { + + /* Skip unset bits */ + if ( ! ( changed & ( 1 << bit ) ) ) + continue; + + /* Skip unused features */ + feature = USB_HUB_C_FEATURE ( bit ); + if ( ! ( hubdev->features & ( 1 << feature ) ) ) + continue; + + /* Clear bit */ + if ( ( rc = usb_hub_clear_port_feature ( usb, port, + feature, 0 ) ) != 0 ) { + DBGC ( hubdev, "HUB %s port %d could not clear feature " + "%d: %s\n", hubdev->name, port, feature, + strerror ( rc ) ); + return rc; + } + } + + return 0; +} + +/** + * Update port speed + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int hub_speed ( struct usb_hub *hub, struct usb_port *port ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + struct usb_device *usb = hubdev->usb; + struct usb_hub_port_status status; + unsigned int current; + unsigned int changed; + int rc; + + /* Get port status */ + if ( ( rc = usb_hub_get_port_status ( usb, port->address, + &status ) ) != 0 ) { + DBGC ( hubdev, "HUB %s port %d could not get status: %s\n", + hubdev->name, port->address, strerror ( rc ) ); + return rc; + } + current = le16_to_cpu ( status.current ); + changed = le16_to_cpu ( status.changed ); + DBGC2 ( hubdev, "HUB %s port %d status is %04x:%04x\n", + hubdev->name, port->address, changed, current ); + + /* Update port speed */ + if ( current & ( 1 << USB_HUB_PORT_CONNECTION ) ) { + if ( hub->protocol >= USB_PROTO_3_0 ) { + port->speed = USB_SPEED_SUPER; + } else if ( current & ( 1 << USB_HUB_PORT_LOW_SPEED ) ) { + port->speed = USB_SPEED_LOW; + } else if ( current & ( 1 << USB_HUB_PORT_HIGH_SPEED ) ) { + port->speed = USB_SPEED_HIGH; + } else { + port->speed = USB_SPEED_FULL; + } + } else { + port->speed = USB_SPEED_NONE; + } + + /* Record disconnections */ + port->disconnected |= ( changed & ( 1 << USB_HUB_PORT_CONNECTION ) ); + + /* Clear port status change bits */ + if ( ( rc = hub_clear_changes ( hubdev, port->address, changed ) ) != 0) + return rc; + + 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 hub_clear_tt ( struct usb_hub *hub, struct usb_port *port, + struct usb_endpoint *ep ) { + struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub ); + struct usb_device *usb = hubdev->usb; + int rc; + + /* Clear transaction translator buffer. All hubs must support + * single-TT operation; we simplify our code by supporting + * only this configuration. + */ + if ( ( rc = usb_hub_clear_tt_buffer ( usb, ep->usb->address, + ep->address, ep->attributes, + USB_HUB_TT_SINGLE ) ) != 0 ) { + DBGC ( hubdev, "HUB %s port %d could not clear TT buffer: %s\n", + hubdev->name, port->address, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** USB hub operations */ +static struct usb_hub_driver_operations hub_operations = { + .open = hub_open, + .close = hub_close, + .enable = hub_enable, + .disable = hub_disable, + .speed = hub_speed, + .clear_tt = hub_clear_tt, +}; + +/** + * Probe USB hub + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int hub_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct usb_bus *bus = usb->port->hub->bus; + struct usb_hub_device *hubdev; + struct usb_interface_descriptor *interface; + union usb_hub_descriptor desc; + unsigned int depth; + unsigned int ports; + int enhanced; + int rc; + + /* Allocate and initialise structure */ + hubdev = zalloc ( sizeof ( *hubdev ) ); + if ( ! hubdev ) { + rc = -ENOMEM; + goto err_alloc; + } + enhanced = ( usb->port->protocol >= USB_PROTO_3_0 ); + hubdev->name = func->name; + hubdev->usb = usb; + hubdev->features = + ( enhanced ? USB_HUB_FEATURES_ENHANCED : USB_HUB_FEATURES ); + usb_endpoint_init ( &hubdev->intr, usb, &usb_hub_intr_operations ); + usb_refill_init ( &hubdev->intr, 0, USB_HUB_INTR_FILL ); + process_init_stopped ( &hubdev->refill, &hub_refill_desc, NULL ); + + /* Locate hub interface descriptor */ + interface = usb_interface_descriptor ( config, func->interface[0], 0 ); + if ( ! interface ) { + DBGC ( hubdev, "HUB %s has no interface descriptor\n", + hubdev->name ); + rc = -EINVAL; + goto err_interface; + } + + /* Locate interrupt endpoint descriptor */ + if ( ( rc = usb_endpoint_described ( &hubdev->intr, config, interface, + USB_INTERRUPT_IN, 0 ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not describe interrupt endpoint: " + "%s\n", hubdev->name, strerror ( rc ) ); + goto err_endpoint; + } + + /* Set hub depth */ + depth = usb_depth ( usb ); + if ( enhanced ) { + if ( ( rc = usb_hub_set_hub_depth ( usb, depth ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not set hub depth to %d: " + "%s\n", hubdev->name, depth, strerror ( rc ) ); + goto err_set_hub_depth; + } + } + + /* Get hub descriptor */ + if ( ( rc = usb_hub_get_descriptor ( usb, enhanced, &desc ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not get hub descriptor: %s\n", + hubdev->name, strerror ( rc ) ); + goto err_hub_descriptor; + } + ports = desc.basic.ports; + DBGC ( hubdev, "HUB %s has %d ports at depth %d%s\n", hubdev->name, + ports, depth, ( enhanced ? " (enhanced)" : "" ) ); + + /* Allocate hub */ + hubdev->hub = alloc_usb_hub ( bus, usb, ports, &hub_operations ); + if ( ! hubdev->hub ) { + rc = -ENOMEM; + goto err_alloc_hub; + } + usb_hub_set_drvdata ( hubdev->hub, hubdev ); + + /* Register hub */ + if ( ( rc = register_usb_hub ( hubdev->hub ) ) != 0 ) { + DBGC ( hubdev, "HUB %s could not register: %s\n", + hubdev->name, strerror ( rc ) ); + goto err_register_hub; + } + + usb_func_set_drvdata ( func, hubdev ); + return 0; + + unregister_usb_hub ( hubdev->hub ); + err_register_hub: + free_usb_hub ( hubdev->hub ); + err_alloc_hub: + err_hub_descriptor: + err_set_hub_depth: + err_endpoint: + err_interface: + free ( hubdev ); + err_alloc: + return rc; +} + +/** + * Remove USB hub + * + * @v func USB function + * @ret rc Return status code + */ +static void hub_remove ( struct usb_function *func ) { + struct usb_hub_device *hubdev = usb_func_get_drvdata ( func ); + struct usb_hub *hub = hubdev->hub; + struct usb_device *usb = hubdev->usb; + struct usb_port *port; + unsigned int i; + + /* If hub has been unplugged, mark all ports as unplugged */ + if ( usb->port->speed == USB_SPEED_NONE ) { + for ( i = 1 ; i <= hub->ports ; i++ ) { + port = usb_port ( hub, i ); + port->speed = USB_SPEED_NONE; + } + } + + /* Unregister hub */ + unregister_usb_hub ( hubdev->hub ); + assert ( ! process_running ( &hubdev->refill ) ); + + /* Free hub */ + free_usb_hub ( hubdev->hub ); + + /* Free hub device */ + free ( hubdev ); +} + +/** USB hub device IDs */ +static struct usb_device_id hub_ids[] = { + { + .name = "hub-1", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + .class = { + .class = USB_CLASS_HUB, + .subclass = 0, + .protocol = 0, + }, + }, + { + .name = "hub-2", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + .class = { + .class = USB_CLASS_HUB, + .subclass = 0, + .protocol = 1, + }, + }, +}; + +/** USB hub driver */ +struct usb_driver usb_hub_driver __usb_driver = { + .ids = hub_ids, + .id_count = ( sizeof ( hub_ids ) / sizeof ( hub_ids[0] ) ), + .probe = hub_probe, + .remove = hub_remove, +}; diff --git a/qemu/roms/ipxe/src/drivers/usb/usbhub.h b/qemu/roms/ipxe/src/drivers/usb/usbhub.h new file mode 100644 index 000000000..d7d8f9610 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbhub.h @@ -0,0 +1,279 @@ +#ifndef _USBHUB_H +#define _USBHUB_H + +/** @file + * + * USB hubs + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/usb.h> +#include <ipxe/list.h> +#include <ipxe/process.h> + +/** Request recipient is a port */ +#define USB_HUB_RECIP_PORT ( 3 << 0 ) + +/** A basic USB hub descriptor */ +struct usb_hub_descriptor_basic { + /** Descriptor header */ + struct usb_descriptor_header header; + /** Number of ports */ + uint8_t ports; + /** Characteristics */ + uint16_t characteristics; + /** Power-on delay (in 2ms intervals */ + uint8_t delay; + /** Controller current (in mA) */ + uint8_t current; +} __attribute__ (( packed )); + +/** A basic USB hub descriptor */ +#define USB_HUB_DESCRIPTOR 41 + +/** An enhanced USB hub descriptor */ +struct usb_hub_descriptor_enhanced { + /** Basic USB hub descriptor */ + struct usb_hub_descriptor_basic basic; + /** Header decode latency */ + uint8_t latency; + /** Maximum delay */ + uint16_t delay; + /** Removable device bitmask */ + uint16_t removable; +} __attribute__ (( packed )); + +/** An enhanced USB hub descriptor */ +#define USB_HUB_DESCRIPTOR_ENHANCED 42 + +/** A USB hub descriptor */ +union usb_hub_descriptor { + /** Descriptor header */ + struct usb_descriptor_header header; + /** Basic hub descriptor */ + struct usb_hub_descriptor_basic basic; + /** Enhanced hub descriptor */ + struct usb_hub_descriptor_enhanced enhanced; +} __attribute__ (( packed )); + +/** Port status */ +struct usb_hub_port_status { + /** Current status */ + uint16_t current; + /** Changed status */ + uint16_t changed; +} __attribute__ (( packed )); + +/** Current connect status feature */ +#define USB_HUB_PORT_CONNECTION 0 + +/** Port enabled/disabled feature */ +#define USB_HUB_PORT_ENABLE 1 + +/** Port reset feature */ +#define USB_HUB_PORT_RESET 4 + +/** Port power feature */ +#define USB_HUB_PORT_POWER 8 + +/** Low-speed device attached */ +#define USB_HUB_PORT_LOW_SPEED 9 + +/** High-speed device attached */ +#define USB_HUB_PORT_HIGH_SPEED 10 + +/** Connect status changed */ +#define USB_HUB_C_PORT_CONNECTION 16 + +/** Port enable/disable changed */ +#define USB_HUB_C_PORT_ENABLE 17 + +/** Suspend changed */ +#define USB_HUB_C_PORT_SUSPEND 18 + +/** Over-current indicator changed */ +#define USB_HUB_C_PORT_OVER_CURRENT 19 + +/** Reset changed */ +#define USB_HUB_C_PORT_RESET 20 + +/** Link state changed */ +#define USB_HUB_C_PORT_LINK_STATE 25 + +/** Configuration error */ +#define USB_HUB_C_PORT_CONFIG_ERROR 26 + +/** Calculate feature from change bit number */ +#define USB_HUB_C_FEATURE( bit ) ( 16 + (bit) ) + +/** USB features */ +#define USB_HUB_FEATURES \ + ( ( 1 << USB_HUB_C_PORT_CONNECTION ) | \ + ( 1 << USB_HUB_C_PORT_ENABLE ) | \ + ( 1 << USB_HUB_C_PORT_SUSPEND ) | \ + ( 1 << USB_HUB_C_PORT_OVER_CURRENT ) | \ + ( 1 << USB_HUB_C_PORT_RESET ) ) + +/** USB features for enhanced hubs */ +#define USB_HUB_FEATURES_ENHANCED \ + ( ( 1 << USB_HUB_C_PORT_CONNECTION ) | \ + ( 1 << USB_HUB_C_PORT_OVER_CURRENT ) | \ + ( 1 << USB_HUB_C_PORT_RESET ) | \ + ( 1 << USB_HUB_C_PORT_LINK_STATE ) | \ + ( 1 << USB_HUB_C_PORT_CONFIG_ERROR ) ) + +/** Set hub depth */ +#define USB_HUB_SET_HUB_DEPTH \ + ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE | \ + USB_REQUEST_TYPE ( 12 ) ) + +/** Clear transaction translator buffer */ +#define USB_HUB_CLEAR_TT_BUFFER \ + ( USB_DIR_OUT | USB_TYPE_CLASS | USB_HUB_RECIP_PORT | \ + USB_REQUEST_TYPE ( 8 ) ) + +/** + * Get hub descriptor + * + * @v usb USB device + * @v enhanced Hub is an enhanced hub + * @v data Hub descriptor to fill in + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_get_descriptor ( struct usb_device *usb, int enhanced, + union usb_hub_descriptor *data ) { + unsigned int desc; + size_t len; + + /* Determine descriptor type and length */ + desc = ( enhanced ? USB_HUB_DESCRIPTOR_ENHANCED : USB_HUB_DESCRIPTOR ); + len = ( enhanced ? sizeof ( data->enhanced ) : sizeof ( data->basic ) ); + + return usb_get_descriptor ( usb, USB_TYPE_CLASS, desc, 0, 0, + &data->header, len ); +} + +/** + * Get port status + * + * @v usb USB device + * @v port Port address + * @v status Port status descriptor to fill in + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_get_port_status ( struct usb_device *usb, unsigned int port, + struct usb_hub_port_status *status ) { + + return usb_get_status ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ), + port, status, sizeof ( *status ) ); +} + +/** + * Clear port feature + * + * @v usb USB device + * @v port Port address + * @v feature Feature to clear + * @v index Index (when clearing a port indicator) + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_clear_port_feature ( struct usb_device *usb, unsigned int port, + unsigned int feature, unsigned int index ) { + + return usb_clear_feature ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ), + feature, ( ( index << 8 ) | port ) ); +} + +/** + * Set port feature + * + * @v usb USB device + * @v port Port address + * @v feature Feature to clear + * @v index Index (when clearing a port indicator) + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_set_port_feature ( struct usb_device *usb, unsigned int port, + unsigned int feature, unsigned int index ) { + + return usb_set_feature ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ), + feature, ( ( index << 8 ) | port ) ); +} + +/** + * Set hub depth + * + * @v usb USB device + * @v depth Hub depth + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_set_hub_depth ( struct usb_device *usb, unsigned int depth ) { + + return usb_control ( usb, USB_HUB_SET_HUB_DEPTH, depth, 0, NULL, 0 ); +} + +/** + * Clear transaction translator buffer + * + * @v usb USB device + * @v device Device address + * @v endpoint Endpoint address + * @v attributes Endpoint attributes + * @v tt_port Transaction translator port (or 1 for single-TT hubs) + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +usb_hub_clear_tt_buffer ( struct usb_device *usb, unsigned int device, + unsigned int endpoint, unsigned int attributes, + unsigned int tt_port ) { + unsigned int value; + + /* Calculate value */ + value = ( ( ( endpoint & USB_ENDPOINT_MAX ) << 0 ) | ( device << 4 ) | + ( ( attributes & USB_ENDPOINT_ATTR_TYPE_MASK ) << 11 ) | + ( ( endpoint & USB_ENDPOINT_IN ) << 8 ) ); + + return usb_control ( usb, USB_HUB_CLEAR_TT_BUFFER, value, + tt_port, NULL, 0 ); +} + +/** Transaction translator port value for single-TT hubs */ +#define USB_HUB_TT_SINGLE 1 + +/** A USB hub device */ +struct usb_hub_device { + /** Name */ + const char *name; + /** USB device */ + struct usb_device *usb; + /** USB hub */ + struct usb_hub *hub; + /** Features */ + unsigned int features; + + /** Interrupt endpoint */ + struct usb_endpoint intr; + /** Interrupt endpoint refill process */ + struct process refill; +}; + +/** Interrupt ring fill level + * + * This is a policy decision. + */ +#define USB_HUB_INTR_FILL 4 + +/** Maximum time to wait for port to become enabled + * + * This is a policy decision. + */ +#define USB_HUB_ENABLE_MAX_WAIT_MS 100 + +#endif /* _USBHUB_H */ diff --git a/qemu/roms/ipxe/src/drivers/usb/usbkbd.c b/qemu/roms/ipxe/src/drivers/usb/usbkbd.c new file mode 100644 index 000000000..ea94f2e63 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbkbd.c @@ -0,0 +1,509 @@ +/* + * 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/console.h> +#include <ipxe/keys.h> +#include <ipxe/usb.h> +#include "usbkbd.h" + +/** @file + * + * USB keyboard driver + * + */ + +/** List of USB keyboards */ +static LIST_HEAD ( usb_keyboards ); + +/****************************************************************************** + * + * Keyboard map + * + ****************************************************************************** + */ + +/** + * Map USB keycode to iPXE key + * + * @v keycode Keycode + * @v modifiers Modifiers + * @ret key iPXE key + * + * Key codes are defined in the USB HID Usage Tables Keyboard/Keypad + * page. + */ +static unsigned int usbkbd_map ( unsigned int keycode, + unsigned int modifiers ) { + unsigned int key; + + if ( keycode < USBKBD_KEY_A ) { + /* Not keys */ + key = 0; + } else if ( keycode <= USBKBD_KEY_Z ) { + /* Alphabetic keys */ + key = ( keycode - USBKBD_KEY_A + 'a' ); + if ( modifiers & USBKBD_CTRL ) { + key -= ( 'a' - CTRL_A ); + } else if ( modifiers & USBKBD_SHIFT ) { + key -= ( 'a' - 'A' ); + } + } else if ( keycode <= USBKBD_KEY_0 ) { + /* Numeric key row */ + if ( modifiers & USBKBD_SHIFT ) { + key = "!@#$%^&*()" [ keycode - USBKBD_KEY_1 ]; + } else { + key = ( ( ( keycode - USBKBD_KEY_1 + 1 ) % 10 ) + '0' ); + } + } else if ( keycode <= USBKBD_KEY_SPACE ) { + /* Unmodifiable keys */ + static const uint8_t unmodifable[] = + { LF, ESC, BACKSPACE, TAB, ' ' }; + key = unmodifable[ keycode - USBKBD_KEY_ENTER ]; + } else if ( keycode <= USBKBD_KEY_SLASH ) { + /* Punctuation keys */ + if ( modifiers & USBKBD_SHIFT ) { + key = "_+{}|~:\"~<>?" [ keycode - USBKBD_KEY_MINUS ]; + } else { + key = "-=[]\\#;'`,./" [ keycode - USBKBD_KEY_MINUS ]; + } + } else if ( keycode <= USBKBD_KEY_UP ) { + /* Special keys */ + static const uint16_t special[] = { + 0, 0, 0, 0, 0, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, + KEY_F10, KEY_F11, KEY_F12, 0, 0, 0, KEY_IC, KEY_HOME, + KEY_PPAGE, KEY_DC, KEY_END, KEY_NPAGE, KEY_RIGHT, + KEY_LEFT, KEY_DOWN, KEY_UP + }; + key = special[ keycode - USBKBD_KEY_CAPSLOCK ]; + } else { + key = 0; + } + + return key; +} + +/****************************************************************************** + * + * Keyboard buffer + * + ****************************************************************************** + */ + +/** + * Insert keypress into keyboard buffer + * + * @v kbd USB keyboard + * @v keycode Keycode + * @v modifiers Modifiers + */ +static void usbkbd_produce ( struct usb_keyboard *kbd, unsigned int keycode, + unsigned int modifiers ) { + unsigned int key; + + /* Map to iPXE key */ + key = usbkbd_map ( keycode, modifiers ); + + /* Do nothing if this keycode has no corresponding iPXE key */ + if ( ! key ) { + DBGC ( kbd, "KBD %s has no key for keycode %#02x:%#02x\n", + kbd->name, modifiers, keycode ); + return; + } + + /* Check for buffer overrun */ + if ( usbkbd_fill ( kbd ) >= USBKBD_BUFSIZE ) { + DBGC ( kbd, "KBD %s buffer overrun (key %#02x)\n", + kbd->name, key ); + return; + } + + /* Insert into buffer */ + kbd->key[ ( kbd->prod++ ) % USBKBD_BUFSIZE ] = key; + DBGC2 ( kbd, "KBD %s key %#02x produced\n", kbd->name, key ); +} + +/** + * Consume character from keyboard buffer + * + * @v kbd USB keyboard + * @ret character Character + */ +static unsigned int usbkbd_consume ( struct usb_keyboard *kbd ) { + static char buf[] = "\x1b[xx~"; + char *tmp = &buf[2]; + unsigned int key; + unsigned int character; + unsigned int ansi_n; + unsigned int len; + + /* Sanity check */ + assert ( usbkbd_fill ( kbd ) > 0 ); + + /* Get current keypress */ + key = kbd->key[ kbd->cons % USBKBD_BUFSIZE ]; + + /* If this is a straightforward key, just consume and return it */ + if ( key < KEY_MIN ) { + kbd->cons++; + DBGC2 ( kbd, "KBD %s key %#02x consumed\n", kbd->name, key ); + return key; + } + + /* Construct ANSI sequence */ + ansi_n = KEY_ANSI_N ( key ); + if ( ansi_n ) + tmp += sprintf ( tmp, "%d", ansi_n ); + *(tmp++) = KEY_ANSI_TERMINATOR ( key ); + *tmp = '\0'; + len = ( tmp - buf ); + assert ( len < sizeof ( buf ) ); + if ( kbd->subcons == 0 ) { + DBGC2 ( kbd, "KBD %s key %#02x consumed as ^[%s\n", + kbd->name, key, &buf[1] ); + } + + /* Extract character from ANSI sequence */ + assert ( kbd->subcons < len ); + character = buf[ kbd->subcons++ ]; + + /* Consume key if applicable */ + if ( kbd->subcons == len ) { + kbd->cons++; + kbd->subcons = 0; + } + + return character; +} + +/****************************************************************************** + * + * Keyboard report + * + ****************************************************************************** + */ + +/** + * Check for presence of keycode in report + * + * @v report Keyboard report + * @v keycode Keycode (must be non-zero) + * @ret has_keycode Keycode is present in report + */ +static int usbkbd_has_keycode ( struct usb_keyboard_report *report, + unsigned int keycode ) { + unsigned int i; + + /* Check for keycode */ + for ( i = 0 ; i < ( sizeof ( report->keycode ) / + sizeof ( report->keycode[0] ) ) ; i++ ) { + if ( report->keycode[i] == keycode ) + return keycode; + } + + return 0; +} + +/** + * Handle keyboard report + * + * @v kbd USB keyboard + * @v new New keyboard report + */ +static void usbkbd_report ( struct usb_keyboard *kbd, + struct usb_keyboard_report *new ) { + struct usb_keyboard_report *old = &kbd->report; + unsigned int keycode; + unsigned int i; + + /* Check if current key has been released */ + if ( kbd->keycode && ! usbkbd_has_keycode ( new, kbd->keycode ) ) { + DBGC2 ( kbd, "KBD %s keycode %#02x released\n", + kbd->name, kbd->keycode ); + kbd->keycode = 0; + } + + /* Decrement auto-repeat hold-off timer, if applicable */ + if ( kbd->holdoff ) + kbd->holdoff--; + + /* Check if a new key has been pressed */ + for ( i = 0 ; i < ( sizeof ( new->keycode ) / + sizeof ( new->keycode[0] ) ) ; i++ ) { + + /* Ignore keys present in the previous report */ + keycode = new->keycode[i]; + if ( ( keycode == 0 ) || usbkbd_has_keycode ( old, keycode ) ) + continue; + DBGC2 ( kbd, "KBD %s keycode %#02x pressed\n", + kbd->name, keycode ); + + /* Insert keypress into keyboard buffer */ + usbkbd_produce ( kbd, keycode, new->modifiers ); + + /* Record as most recent keycode */ + kbd->keycode = keycode; + + /* Start auto-repeat hold-off timer */ + kbd->holdoff = USBKBD_HOLDOFF; + } + + /* Insert auto-repeated keypress into keyboard buffer, if applicable */ + if ( kbd->keycode && ! kbd->holdoff ) + usbkbd_produce ( kbd, kbd->keycode, new->modifiers ); + + /* Record report */ + memcpy ( old, new, sizeof ( *old ) ); +} + +/****************************************************************************** + * + * Interrupt endpoint + * + ****************************************************************************** + */ + +/** + * Complete interrupt transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @v rc Completion status code + */ +static void usbkbd_complete ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int rc ) { + struct usb_keyboard *kbd = container_of ( ep, struct usb_keyboard, + hid.in ); + struct usb_keyboard_report *report; + + /* Ignore packets cancelled when the endpoint closes */ + if ( ! ep->open ) + goto drop; + + /* Ignore packets with errors */ + if ( rc != 0 ) { + DBGC ( kbd, "KBD %s interrupt IN failed: %s\n", + kbd->name, strerror ( rc ) ); + goto drop; + } + + /* Ignore underlength packets */ + if ( iob_len ( iobuf ) < sizeof ( *report ) ) { + DBGC ( kbd, "KBD %s underlength report:\n", kbd->name ); + DBGC_HDA ( kbd, 0, iobuf->data, iob_len ( iobuf ) ); + goto drop; + } + report = iobuf->data; + + /* Handle keyboard report */ + usbkbd_report ( kbd, report ); + + drop: + /* Recycle I/O buffer */ + usb_recycle ( &kbd->hid.in, iobuf ); +} + +/** Interrupt endpoint operations */ +static struct usb_endpoint_driver_operations usbkbd_operations = { + .complete = usbkbd_complete, +}; + +/****************************************************************************** + * + * USB interface + * + ****************************************************************************** + */ + +/** + * Probe device + * + * @v func USB function + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int usbkbd_probe ( struct usb_function *func, + struct usb_configuration_descriptor *config ) { + struct usb_device *usb = func->usb; + struct usb_keyboard *kbd; + int rc; + + /* Allocate and initialise structure */ + kbd = zalloc ( sizeof ( *kbd ) ); + if ( ! kbd ) { + rc = -ENOMEM; + goto err_alloc; + } + kbd->name = func->name; + kbd->bus = usb->port->hub->bus; + usbhid_init ( &kbd->hid, func, &usbkbd_operations, NULL ); + usb_refill_init ( &kbd->hid.in, sizeof ( kbd->report ), + USBKBD_INTR_MAX_FILL ); + + /* Describe USB human interface device */ + if ( ( rc = usbhid_describe ( &kbd->hid, config ) ) != 0 ) { + DBGC ( kbd, "KBD %s could not describe: %s\n", + kbd->name, strerror ( rc ) ); + goto err_describe; + } + DBGC ( kbd, "KBD %s using %s (len %zd)\n", + kbd->name, usb_endpoint_name ( &kbd->hid.in ), kbd->hid.in.mtu ); + + /* Set boot protocol */ + if ( ( rc = usbhid_set_protocol ( usb, func->interface[0], + USBHID_PROTOCOL_BOOT ) ) != 0 ) { + DBGC ( kbd, "KBD %s could not set boot protocol: %s\n", + kbd->name, strerror ( rc ) ); + goto err_set_protocol; + } + + /* Set idle time */ + if ( ( rc = usbhid_set_idle ( usb, func->interface[0], 0, + USBKBD_IDLE_DURATION ) ) != 0 ) { + DBGC ( kbd, "KBD %s could not set idle time: %s\n", + kbd->name, strerror ( rc ) ); + goto err_set_idle; + } + + /* Open USB human interface device */ + if ( ( rc = usbhid_open ( &kbd->hid ) ) != 0 ) { + DBGC ( kbd, "KBD %s could not open: %s\n", + kbd->name, strerror ( rc ) ); + goto err_open; + } + + /* Add to list of USB keyboards */ + list_add_tail ( &kbd->list, &usb_keyboards ); + + usb_func_set_drvdata ( func, kbd ); + return 0; + + usbhid_close ( &kbd->hid ); + err_open: + err_set_idle: + err_set_protocol: + err_describe: + free ( kbd ); + err_alloc: + return rc; +} + +/** + * Remove device + * + * @v func USB function + */ +static void usbkbd_remove ( struct usb_function *func ) { + struct usb_keyboard *kbd = usb_func_get_drvdata ( func ); + + /* Remove from list of USB keyboards */ + list_del ( &kbd->list ); + + /* Close USB human interface device */ + usbhid_close ( &kbd->hid ); + + /* Free device */ + free ( kbd ); +} + +/** USB keyboard device IDs */ +static struct usb_device_id usbkbd_ids[] = { + { + .name = "kbd", + .vendor = USB_ANY_ID, + .product = USB_ANY_ID, + .class = { + .class = USB_CLASS_HID, + .subclass = USB_SUBCLASS_HID_BOOT, + .protocol = USBKBD_PROTOCOL, + }, + }, +}; + +/** USB keyboard driver */ +struct usb_driver usbkbd_driver __usb_driver = { + .ids = usbkbd_ids, + .id_count = ( sizeof ( usbkbd_ids ) / sizeof ( usbkbd_ids[0] ) ), + .probe = usbkbd_probe, + .remove = usbkbd_remove, +}; + +/****************************************************************************** + * + * Console interface + * + ****************************************************************************** + */ + +/** + * Read a character from the console + * + * @ret character Character read + */ +static int usbkbd_getchar ( void ) { + struct usb_keyboard *kbd; + + /* Consume first available key */ + list_for_each_entry ( kbd, &usb_keyboards, list ) { + if ( usbkbd_fill ( kbd ) ) + return usbkbd_consume ( kbd ); + } + + return 0; +} + +/** + * Check for available input + * + * @ret is_available Input is available + */ +static int usbkbd_iskey ( void ) { + struct usb_keyboard *kbd; + unsigned int fill; + + /* Poll all USB keyboards and refill endpoints */ + list_for_each_entry ( kbd, &usb_keyboards, list ) { + usb_poll ( kbd->bus ); + usb_refill ( &kbd->hid.in ); + } + + /* Check for a non-empty keyboard buffer */ + list_for_each_entry ( kbd, &usb_keyboards, list ) { + fill = usbkbd_fill ( kbd ); + if ( fill ) + return fill; + } + + return 0; +} + +/** USB keyboard console */ +struct console_driver usbkbd_console __console_driver = { + .getchar = usbkbd_getchar, + .iskey = usbkbd_iskey, +}; diff --git a/qemu/roms/ipxe/src/drivers/usb/usbkbd.h b/qemu/roms/ipxe/src/drivers/usb/usbkbd.h new file mode 100644 index 000000000..7eab24e46 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbkbd.h @@ -0,0 +1,154 @@ +#ifndef _USBKBD_H +#define _USBKBD_H + +/** @file + * + * USB keyboard driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <assert.h> +#include <ipxe/usb.h> +#include <ipxe/usbhid.h> + +/** Keyboard protocol */ +#define USBKBD_PROTOCOL 1 + +/** A USB keyboard report */ +struct usb_keyboard_report { + /** Modifier keys */ + uint8_t modifiers; + /** Reserved */ + uint8_t reserved; + /** Keycodes */ + uint8_t keycode[6]; +} __attribute__ (( packed )); + +/** USB modifier keys */ +enum usb_keyboard_modifier { + /** Left Ctrl key */ + USBKBD_CTRL_LEFT = 0x01, + /** Left Shift key */ + USBKBD_SHIFT_LEFT = 0x02, + /** Left Alt key */ + USBKBD_ALT_LEFT = 0x04, + /** Left GUI key */ + USBKBD_GUI_LEFT = 0x08, + /** Right Ctrl key */ + USBKBD_CTRL_RIGHT = 0x10, + /** Right Shift key */ + USBKBD_SHIFT_RIGHT = 0x20, + /** Right Alt key */ + USBKBD_ALT_RIGHT = 0x40, + /** Right GUI key */ + USBKBD_GUI_RIGHT = 0x80, +}; + +/** Either Ctrl key */ +#define USBKBD_CTRL ( USBKBD_CTRL_LEFT | USBKBD_CTRL_RIGHT ) + +/** Either Shift key */ +#define USBKBD_SHIFT ( USBKBD_SHIFT_LEFT | USBKBD_SHIFT_RIGHT ) + +/** Either Alt key */ +#define USBKBD_ALT ( USBKBD_ALT_LEFT | USBKBD_ALT_RIGHT ) + +/** Either GUI key */ +#define USBKBD_GUI ( USBKBD_GUI_LEFT | USBKBD_GUI_RIGHT ) + +/** USB keycodes */ +enum usb_keycode { + USBKBD_KEY_A = 0x04, + USBKBD_KEY_Z = 0x1d, + USBKBD_KEY_1 = 0x1e, + USBKBD_KEY_0 = 0x27, + USBKBD_KEY_ENTER = 0x28, + USBKBD_KEY_SPACE = 0x2c, + USBKBD_KEY_MINUS = 0x2d, + USBKBD_KEY_SLASH = 0x38, + USBKBD_KEY_CAPSLOCK = 0x39, + USBKBD_KEY_UP = 0x52, +}; + +/** Keyboard idle duration (in 4ms units) + * + * This is a policy decision. We choose to use an autorepeat rate of + * approximately 40ms. + */ +#define USBKBD_IDLE_DURATION 10 /* 10 x 4ms = 40ms */ + +/** Keyboard auto-repeat hold-off (in units of USBKBD_IDLE_DURATION) + * + * This is a policy decision. We choose to use an autorepeat delay of + * approximately 500ms. + */ +#define USBKBD_HOLDOFF 12 /* 12 x 40ms = 480ms */ + +/** Interrupt endpoint maximum fill level + * + * When idling, we are likely to poll the USB endpoint at only the + * 18.2Hz system timer tick rate. With a typical observed bInterval + * of 10ms (which will be rounded down to 8ms by the HCI drivers), + * this gives approximately 7 completions per poll. + */ +#define USBKBD_INTR_MAX_FILL 8 + +/** Keyboard buffer size + * + * Must be a power of two. + */ +#define USBKBD_BUFSIZE 8 + +/** A USB keyboard device */ +struct usb_keyboard { + /** Name */ + const char *name; + /** List of all USB keyboards */ + struct list_head list; + + /** USB bus */ + struct usb_bus *bus; + /** USB human interface device */ + struct usb_hid hid; + + /** Most recent keyboard report */ + struct usb_keyboard_report report; + /** Most recently pressed non-modifier key (if any) */ + unsigned int keycode; + /** Autorepeat hold-off time (in number of completions reported) */ + unsigned int holdoff; + + /** Keyboard buffer + * + * This stores iPXE key values. + */ + unsigned int key[USBKBD_BUFSIZE]; + /** Keyboard buffer producer counter */ + unsigned int prod; + /** Keyboard buffer consumer counter */ + unsigned int cons; + /** Keyboard buffer sub-consumer counter + * + * This represents the index within the ANSI escape sequence + * corresponding to an iPXE key value. + */ + unsigned int subcons; +}; + +/** + * Calculate keyboard buffer fill level + * + * @v kbd USB keyboard + * @ret fill Keyboard buffer fill level + */ +static inline __attribute__ (( always_inline )) unsigned int +usbkbd_fill ( struct usb_keyboard *kbd ) { + unsigned int fill = ( kbd->prod - kbd->cons ); + + assert ( fill <= USBKBD_BUFSIZE ); + return fill; +} + +#endif /* _USBKBD_H */ diff --git a/qemu/roms/ipxe/src/drivers/usb/usbnet.c b/qemu/roms/ipxe/src/drivers/usb/usbnet.c new file mode 100644 index 000000000..b92336d05 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/usbnet.c @@ -0,0 +1,284 @@ +/* + * 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 <string.h> +#include <errno.h> +#include <ipxe/usb.h> +#include <ipxe/usbnet.h> + +/** @file + * + * USB network devices + * + * USB network devices use a variety of packet formats and interface + * descriptors, but tend to have several features in common: + * + * - a single interrupt endpoint using the generic refill mechanism + * + * - a single bulk IN endpoint using the generic refill mechanism + * + * - a single bulk OUT endpoint + * + * - optional use of an alternate setting to enable the data interface + * + */ + +/** + * Open USB network device + * + * @v usbnet USB network device + * @ret rc Return status code + */ +int usbnet_open ( struct usbnet_device *usbnet ) { + struct usb_device *usb = usbnet->func->usb; + int rc; + + /* Open interrupt endpoint */ + if ( ( rc = usb_endpoint_open ( &usbnet->intr ) ) != 0 ) { + DBGC ( usbnet, "USBNET %s could not open interrupt: %s\n", + usbnet->func->name, strerror ( rc ) ); + goto err_open_intr; + } + + /* Refill interrupt endpoint */ + if ( ( rc = usb_refill ( &usbnet->intr ) ) != 0 ) { + DBGC ( usbnet, "USBNET %s could not refill interrupt: %s\n", + usbnet->func->name, strerror ( rc ) ); + goto err_refill_intr; + } + + /* Select alternate setting for data interface, if applicable */ + if ( usbnet->alternate && + ( ( rc = usb_set_interface ( usb, usbnet->data, + usbnet->alternate ) ) != 0 ) ) { + DBGC ( usbnet, "USBNET %s could not set alternate interface " + "%d: %s\n", usbnet->func->name, usbnet->alternate, + strerror ( rc ) ); + goto err_set_interface; + } + + /* Open bulk IN endpoint */ + if ( ( rc = usb_endpoint_open ( &usbnet->in ) ) != 0 ) { + DBGC ( usbnet, "USBNET %s could not open bulk IN: %s\n", + usbnet->func->name, strerror ( rc ) ); + goto err_open_in; + } + + /* Open bulk OUT endpoint */ + if ( ( rc = usb_endpoint_open ( &usbnet->out ) ) != 0 ) { + DBGC ( usbnet, "USBNET %s could not open bulk OUT: %s\n", + usbnet->func->name, strerror ( rc ) ); + goto err_open_out; + } + + /* Refill bulk IN endpoint */ + if ( ( rc = usb_refill ( &usbnet->in ) ) != 0 ) { + DBGC ( usbnet, "USBNET %s could not refill bulk IN: %s\n", + usbnet->func->name, strerror ( rc ) ); + goto err_refill_in; + } + + return 0; + + err_refill_in: + usb_endpoint_close ( &usbnet->out ); + err_open_out: + usb_endpoint_close ( &usbnet->in ); + err_open_in: + if ( usbnet->alternate ) + usb_set_interface ( usb, usbnet->data, 0 ); + err_set_interface: + err_refill_intr: + usb_endpoint_close ( &usbnet->intr ); + err_open_intr: + return rc; +} + +/** + * Close USB network device + * + * @v usbnet USB network device + */ +void usbnet_close ( struct usbnet_device *usbnet ) { + struct usb_device *usb = usbnet->func->usb; + + /* Close bulk OUT endpoint */ + usb_endpoint_close ( &usbnet->out ); + + /* Close bulk IN endpoint */ + usb_endpoint_close ( &usbnet->in ); + + /* Reset alternate setting for data interface, if applicable */ + if ( usbnet->alternate ) + usb_set_interface ( usb, usbnet->data, 0 ); + + /* Close interrupt endpoint */ + usb_endpoint_close ( &usbnet->intr ); +} + +/** + * Refill USB network device bulk IN and interrupt endpoints + * + * @v usbnet USB network device + * @ret rc Return status code + */ +int usbnet_refill ( struct usbnet_device *usbnet ) { + int rc; + + /* Refill bulk IN endpoint */ + if ( ( rc = usb_refill ( &usbnet->in ) ) != 0 ) + return rc; + + /* Refill interrupt endpoint */ + if ( ( rc = usb_refill ( &usbnet->intr ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Describe communications interface and interrupt endpoint + * + * @v usbnet USB network device + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int usbnet_comms_describe ( struct usbnet_device *usbnet, + struct usb_configuration_descriptor *config){ + struct usb_interface_descriptor *desc; + unsigned int comms; + unsigned int i; + int rc; + + /* Iterate over all available interfaces */ + for ( i = 0 ; i < usbnet->func->count ; i++ ) { + + /* Get interface number */ + comms = usbnet->func->interface[i]; + + /* Locate interface descriptor */ + desc = usb_interface_descriptor ( config, comms, 0 ); + if ( ! desc ) + continue; + + /* Describe interrupt endpoint */ + if ( ( rc = usb_endpoint_described ( &usbnet->intr, config, + desc, USB_INTERRUPT_IN, + 0 ) ) != 0 ) + continue; + + /* Record communications interface */ + usbnet->comms = comms; + DBGC ( usbnet, "USBNET %s found communications interface %d\n", + usbnet->func->name, comms ); + return 0; + } + + DBGC ( usbnet, "USBNET %s found no communications interface\n", + usbnet->func->name ); + return -ENOENT; +} + +/** + * Describe data interface and bulk endpoints + * + * @v usbnet USB network device + * @v config Configuration descriptor + * @ret rc Return status code + */ +static int usbnet_data_describe ( struct usbnet_device *usbnet, + struct usb_configuration_descriptor *config ){ + struct usb_interface_descriptor *desc; + unsigned int data; + unsigned int alt; + unsigned int i; + int rc; + + /* Iterate over all available interfaces */ + for ( i = 0 ; i < usbnet->func->count ; i++ ) { + + /* Get interface number */ + data = usbnet->func->interface[i]; + + /* Iterate over all existent alternate settings */ + for ( alt = 0 ; ; alt++ ) { + + /* Locate interface descriptor */ + desc = usb_interface_descriptor ( config, data, alt ); + if ( ! desc ) + break; + + /* Describe bulk IN endpoint */ + if ( ( rc = usb_endpoint_described ( &usbnet->in, + config, desc, + USB_BULK_IN, + 0 ) ) != 0 ) + continue; + + /* Describe bulk OUT endpoint */ + if ( ( rc = usb_endpoint_described ( &usbnet->out, + config, desc, + USB_BULK_OUT, + 0 ) ) != 0 ) + continue; + + /* Record data interface and alternate setting */ + usbnet->data = data; + usbnet->alternate = alt; + DBGC ( usbnet, "USBNET %s found data interface %d", + usbnet->func->name, data ); + if ( alt ) + DBGC ( usbnet, " using alternate %d", alt ); + DBGC ( usbnet, "\n" ); + return 0; + } + } + + DBGC ( usbnet, "USBNET %s found no data interface\n", + usbnet->func->name ); + return -ENOENT; +} + +/** + * Describe USB network device interfaces + * + * @v usbnet USB network device + * @v config Configuration descriptor + * @ret rc Return status code + */ +int usbnet_describe ( struct usbnet_device *usbnet, + struct usb_configuration_descriptor *config ) { + int rc; + + /* Describe communications interface */ + if ( ( rc = usbnet_comms_describe ( usbnet, config ) ) != 0 ) + return rc; + + /* Describe data interface */ + if ( ( rc = usbnet_data_describe ( usbnet, config ) ) != 0 ) + return rc; + + return 0; +} diff --git a/qemu/roms/ipxe/src/drivers/usb/xhci.c b/qemu/roms/ipxe/src/drivers/usb/xhci.c new file mode 100644 index 000000000..49e67316b --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/xhci.c @@ -0,0 +1,3321 @@ +/* + * Copyright (C) 2014 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 <unistd.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/malloc.h> +#include <ipxe/umalloc.h> +#include <ipxe/pci.h> +#include <ipxe/usb.h> +#include <ipxe/init.h> +#include <ipxe/profile.h> +#include "xhci.h" + +/** @file + * + * USB eXtensible Host Controller Interface (xHCI) driver + * + */ + +/** Message transfer profiler */ +static struct profiler xhci_message_profiler __profiler = + { .name = "xhci.message" }; + +/** Stream transfer profiler */ +static struct profiler xhci_stream_profiler __profiler = + { .name = "xhci.stream" }; + +/** Event ring profiler */ +static struct profiler xhci_event_profiler __profiler = + { .name = "xhci.event" }; + +/** Transfer event profiler */ +static struct profiler xhci_transfer_profiler __profiler = + { .name = "xhci.transfer" }; + +/* Disambiguate the various error causes */ +#define EIO_DATA \ + __einfo_error ( EINFO_EIO_DATA ) +#define EINFO_EIO_DATA \ + __einfo_uniqify ( EINFO_EIO, ( 2 - 0 ), \ + "Data buffer error" ) +#define EIO_BABBLE \ + __einfo_error ( EINFO_EIO_BABBLE ) +#define EINFO_EIO_BABBLE \ + __einfo_uniqify ( EINFO_EIO, ( 3 - 0 ), \ + "Babble detected" ) +#define EIO_USB \ + __einfo_error ( EINFO_EIO_USB ) +#define EINFO_EIO_USB \ + __einfo_uniqify ( EINFO_EIO, ( 4 - 0 ), \ + "USB transaction error" ) +#define EIO_TRB \ + __einfo_error ( EINFO_EIO_TRB ) +#define EINFO_EIO_TRB \ + __einfo_uniqify ( EINFO_EIO, ( 5 - 0 ), \ + "TRB error" ) +#define EIO_STALL \ + __einfo_error ( EINFO_EIO_STALL ) +#define EINFO_EIO_STALL \ + __einfo_uniqify ( EINFO_EIO, ( 6 - 0 ), \ + "Stall error" ) +#define EIO_RESOURCE \ + __einfo_error ( EINFO_EIO_RESOURCE ) +#define EINFO_EIO_RESOURCE \ + __einfo_uniqify ( EINFO_EIO, ( 7 - 0 ), \ + "Resource error" ) +#define EIO_BANDWIDTH \ + __einfo_error ( EINFO_EIO_BANDWIDTH ) +#define EINFO_EIO_BANDWIDTH \ + __einfo_uniqify ( EINFO_EIO, ( 8 - 0 ), \ + "Bandwidth error" ) +#define EIO_NO_SLOTS \ + __einfo_error ( EINFO_EIO_NO_SLOTS ) +#define EINFO_EIO_NO_SLOTS \ + __einfo_uniqify ( EINFO_EIO, ( 9 - 0 ), \ + "No slots available" ) +#define EIO_STREAM_TYPE \ + __einfo_error ( EINFO_EIO_STREAM_TYPE ) +#define EINFO_EIO_STREAM_TYPE \ + __einfo_uniqify ( EINFO_EIO, ( 10 - 0 ), \ + "Invalid stream type" ) +#define EIO_SLOT \ + __einfo_error ( EINFO_EIO_SLOT ) +#define EINFO_EIO_SLOT \ + __einfo_uniqify ( EINFO_EIO, ( 11 - 0 ), \ + "Slot not enabled" ) +#define EIO_ENDPOINT \ + __einfo_error ( EINFO_EIO_ENDPOINT ) +#define EINFO_EIO_ENDPOINT \ + __einfo_uniqify ( EINFO_EIO, ( 12 - 0 ), \ + "Endpoint not enabled" ) +#define EIO_SHORT \ + __einfo_error ( EINFO_EIO_SHORT ) +#define EINFO_EIO_SHORT \ + __einfo_uniqify ( EINFO_EIO, ( 13 - 0 ), \ + "Short packet" ) +#define EIO_UNDERRUN \ + __einfo_error ( EINFO_EIO_UNDERRUN ) +#define EINFO_EIO_UNDERRUN \ + __einfo_uniqify ( EINFO_EIO, ( 14 - 0 ), \ + "Ring underrun" ) +#define EIO_OVERRUN \ + __einfo_error ( EINFO_EIO_OVERRUN ) +#define EINFO_EIO_OVERRUN \ + __einfo_uniqify ( EINFO_EIO, ( 15 - 0 ), \ + "Ring overrun" ) +#define EIO_VF_RING_FULL \ + __einfo_error ( EINFO_EIO_VF_RING_FULL ) +#define EINFO_EIO_VF_RING_FULL \ + __einfo_uniqify ( EINFO_EIO, ( 16 - 0 ), \ + "Virtual function event ring full" ) +#define EIO_PARAMETER \ + __einfo_error ( EINFO_EIO_PARAMETER ) +#define EINFO_EIO_PARAMETER \ + __einfo_uniqify ( EINFO_EIO, ( 17 - 0 ), \ + "Parameter error" ) +#define EIO_BANDWIDTH_OVERRUN \ + __einfo_error ( EINFO_EIO_BANDWIDTH_OVERRUN ) +#define EINFO_EIO_BANDWIDTH_OVERRUN \ + __einfo_uniqify ( EINFO_EIO, ( 18 - 0 ), \ + "Bandwidth overrun" ) +#define EIO_CONTEXT \ + __einfo_error ( EINFO_EIO_CONTEXT ) +#define EINFO_EIO_CONTEXT \ + __einfo_uniqify ( EINFO_EIO, ( 19 - 0 ), \ + "Context state error" ) +#define EIO_NO_PING \ + __einfo_error ( EINFO_EIO_NO_PING ) +#define EINFO_EIO_NO_PING \ + __einfo_uniqify ( EINFO_EIO, ( 20 - 0 ), \ + "No ping response" ) +#define EIO_RING_FULL \ + __einfo_error ( EINFO_EIO_RING_FULL ) +#define EINFO_EIO_RING_FULL \ + __einfo_uniqify ( EINFO_EIO, ( 21 - 0 ), \ + "Event ring full" ) +#define EIO_INCOMPATIBLE \ + __einfo_error ( EINFO_EIO_INCOMPATIBLE ) +#define EINFO_EIO_INCOMPATIBLE \ + __einfo_uniqify ( EINFO_EIO, ( 22 - 0 ), \ + "Incompatible device" ) +#define EIO_MISSED \ + __einfo_error ( EINFO_EIO_MISSED ) +#define EINFO_EIO_MISSED \ + __einfo_uniqify ( EINFO_EIO, ( 23 - 0 ), \ + "Missed service error" ) +#define EIO_CMD_STOPPED \ + __einfo_error ( EINFO_EIO_CMD_STOPPED ) +#define EINFO_EIO_CMD_STOPPED \ + __einfo_uniqify ( EINFO_EIO, ( 24 - 0 ), \ + "Command ring stopped" ) +#define EIO_CMD_ABORTED \ + __einfo_error ( EINFO_EIO_CMD_ABORTED ) +#define EINFO_EIO_CMD_ABORTED \ + __einfo_uniqify ( EINFO_EIO, ( 25 - 0 ), \ + "Command aborted" ) +#define EIO_STOP \ + __einfo_error ( EINFO_EIO_STOP ) +#define EINFO_EIO_STOP \ + __einfo_uniqify ( EINFO_EIO, ( 26 - 0 ), \ + "Stopped" ) +#define EIO_STOP_LEN \ + __einfo_error ( EINFO_EIO_STOP_LEN ) +#define EINFO_EIO_STOP_LEN \ + __einfo_uniqify ( EINFO_EIO, ( 27 - 0 ), \ + "Stopped - length invalid" ) +#define EIO_STOP_SHORT \ + __einfo_error ( EINFO_EIO_STOP_SHORT ) +#define EINFO_EIO_STOP_SHORT \ + __einfo_uniqify ( EINFO_EIO, ( 28 - 0 ), \ + "Stopped - short packet" ) +#define EIO_LATENCY \ + __einfo_error ( EINFO_EIO_LATENCY ) +#define EINFO_EIO_LATENCY \ + __einfo_uniqify ( EINFO_EIO, ( 29 - 0 ), \ + "Maximum exit latency too large" ) +#define EIO_ISOCH \ + __einfo_error ( EINFO_EIO_ISOCH ) +#define EINFO_EIO_ISOCH \ + __einfo_uniqify ( EINFO_EIO, ( 31 - 0 ), \ + "Isochronous buffer overrun" ) +#define EPROTO_LOST \ + __einfo_error ( EINFO_EPROTO_LOST ) +#define EINFO_EPROTO_LOST \ + __einfo_uniqify ( EINFO_EPROTO, ( 32 - 32 ), \ + "Event lost" ) +#define EPROTO_UNDEFINED \ + __einfo_error ( EINFO_EPROTO_UNDEFINED ) +#define EINFO_EPROTO_UNDEFINED \ + __einfo_uniqify ( EINFO_EPROTO, ( 33 - 32 ), \ + "Undefined error" ) +#define EPROTO_STREAM_ID \ + __einfo_error ( EINFO_EPROTO_STREAM_ID ) +#define EINFO_EPROTO_STREAM_ID \ + __einfo_uniqify ( EINFO_EPROTO, ( 34 - 32 ), \ + "Invalid stream ID" ) +#define EPROTO_SECONDARY \ + __einfo_error ( EINFO_EPROTO_SECONDARY ) +#define EINFO_EPROTO_SECONDARY \ + __einfo_uniqify ( EINFO_EPROTO, ( 35 - 32 ), \ + "Secondary bandwidth error" ) +#define EPROTO_SPLIT \ + __einfo_error ( EINFO_EPROTO_SPLIT ) +#define EINFO_EPROTO_SPLIT \ + __einfo_uniqify ( EINFO_EPROTO, ( 36 - 32 ), \ + "Split transaction error" ) +#define ECODE(code) \ + ( ( (code) < 32 ) ? \ + EUNIQ ( EINFO_EIO, ( (code) & 31 ), EIO_DATA, EIO_BABBLE, \ + EIO_USB, EIO_TRB, EIO_STALL, EIO_RESOURCE, \ + EIO_BANDWIDTH, EIO_NO_SLOTS, EIO_STREAM_TYPE, \ + EIO_SLOT, EIO_ENDPOINT, EIO_SHORT, EIO_UNDERRUN, \ + EIO_OVERRUN, EIO_VF_RING_FULL, EIO_PARAMETER, \ + EIO_BANDWIDTH_OVERRUN, EIO_CONTEXT, EIO_NO_PING, \ + EIO_RING_FULL, EIO_INCOMPATIBLE, EIO_MISSED, \ + EIO_CMD_STOPPED, EIO_CMD_ABORTED, EIO_STOP, \ + EIO_STOP_LEN, EIO_STOP_SHORT, EIO_LATENCY, \ + EIO_ISOCH ) : \ + ( (code) < 64 ) ? \ + EUNIQ ( EINFO_EPROTO, ( (code) & 31 ), EPROTO_LOST, \ + EPROTO_UNDEFINED, EPROTO_STREAM_ID, \ + EPROTO_SECONDARY, EPROTO_SPLIT ) : \ + EFAULT ) + +/****************************************************************************** + * + * Register access + * + ****************************************************************************** + */ + +/** + * Initialise device + * + * @v xhci xHCI device + * @v regs MMIO registers + */ +static void xhci_init ( struct xhci_device *xhci, void *regs ) { + uint32_t hcsparams1; + uint32_t hcsparams2; + uint32_t hccparams1; + uint32_t pagesize; + size_t caplength; + size_t rtsoff; + size_t dboff; + + /* Locate capability, operational, runtime, and doorbell registers */ + xhci->cap = regs; + caplength = readb ( xhci->cap + XHCI_CAP_CAPLENGTH ); + rtsoff = readl ( xhci->cap + XHCI_CAP_RTSOFF ); + dboff = readl ( xhci->cap + XHCI_CAP_DBOFF ); + xhci->op = ( xhci->cap + caplength ); + xhci->run = ( xhci->cap + rtsoff ); + xhci->db = ( xhci->cap + dboff ); + DBGC2 ( xhci, "XHCI %s cap %08lx op %08lx run %08lx db %08lx\n", + xhci->name, virt_to_phys ( xhci->cap ), + virt_to_phys ( xhci->op ), virt_to_phys ( xhci->run ), + virt_to_phys ( xhci->db ) ); + + /* Read structural parameters 1 */ + hcsparams1 = readl ( xhci->cap + XHCI_CAP_HCSPARAMS1 ); + xhci->slots = XHCI_HCSPARAMS1_SLOTS ( hcsparams1 ); + xhci->intrs = XHCI_HCSPARAMS1_INTRS ( hcsparams1 ); + xhci->ports = XHCI_HCSPARAMS1_PORTS ( hcsparams1 ); + DBGC ( xhci, "XHCI %s has %d slots %d intrs %d ports\n", + xhci->name, xhci->slots, xhci->intrs, xhci->ports ); + + /* Read structural parameters 2 */ + hcsparams2 = readl ( xhci->cap + XHCI_CAP_HCSPARAMS2 ); + xhci->scratchpads = XHCI_HCSPARAMS2_SCRATCHPADS ( hcsparams2 ); + DBGC2 ( xhci, "XHCI %s needs %d scratchpads\n", + xhci->name, xhci->scratchpads ); + + /* Read capability parameters 1 */ + hccparams1 = readl ( xhci->cap + XHCI_CAP_HCCPARAMS1 ); + xhci->addr64 = XHCI_HCCPARAMS1_ADDR64 ( hccparams1 ); + xhci->csz_shift = XHCI_HCCPARAMS1_CSZ_SHIFT ( hccparams1 ); + xhci->xecp = XHCI_HCCPARAMS1_XECP ( hccparams1 ); + + /* Read page size */ + pagesize = readl ( xhci->op + XHCI_OP_PAGESIZE ); + xhci->pagesize = XHCI_PAGESIZE ( pagesize ); + assert ( xhci->pagesize != 0 ); + assert ( ( ( xhci->pagesize ) & ( xhci->pagesize - 1 ) ) == 0 ); + DBGC2 ( xhci, "XHCI %s page size %zd bytes\n", + xhci->name, xhci->pagesize ); +} + +/** + * Find extended capability + * + * @v xhci xHCI device + * @v id Capability ID + * @v offset Offset to previous extended capability instance, or zero + * @ret offset Offset to extended capability, or zero if not found + */ +static unsigned int xhci_extended_capability ( struct xhci_device *xhci, + unsigned int id, + unsigned int offset ) { + uint32_t xecp; + unsigned int next; + + /* Locate the extended capability */ + while ( 1 ) { + + /* Locate first or next capability as applicable */ + if ( offset ) { + xecp = readl ( xhci->cap + offset ); + next = XHCI_XECP_NEXT ( xecp ); + } else { + next = xhci->xecp; + } + if ( ! next ) + return 0; + offset += next; + + /* Check if this is the requested capability */ + xecp = readl ( xhci->cap + offset ); + if ( XHCI_XECP_ID ( xecp ) == id ) + return offset; + } +} + +/** + * Write potentially 64-bit register + * + * @v xhci xHCI device + * @v value Value + * @v reg Register address + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +xhci_writeq ( struct xhci_device *xhci, physaddr_t value, void *reg ) { + + /* If this is a 32-bit build, then this can never fail + * (allowing the compiler to optimise out the error path). + */ + if ( sizeof ( value ) <= sizeof ( uint32_t ) ) { + writel ( value, reg ); + writel ( 0, ( reg + sizeof ( uint32_t ) ) ); + return 0; + } + + /* If the device does not support 64-bit addresses and this + * address is outside the 32-bit address space, then fail. + */ + if ( ( value & ~0xffffffffULL ) && ! xhci->addr64 ) { + DBGC ( xhci, "XHCI %s cannot access address %lx\n", + xhci->name, value ); + return -ENOTSUP; + } + + /* If this is a 64-bit build, then writeq() is available */ + writeq ( value, reg ); + return 0; +} + +/** + * Calculate buffer alignment + * + * @v len Length + * @ret align Buffer alignment + * + * Determine alignment required for a buffer which must be aligned to + * at least XHCI_MIN_ALIGN and which must not cross a page boundary. + */ +static inline size_t xhci_align ( size_t len ) { + size_t align; + + /* Align to own length (rounded up to a power of two) */ + align = ( 1 << fls ( len - 1 ) ); + + /* Round up to XHCI_MIN_ALIGN if needed */ + if ( align < XHCI_MIN_ALIGN ) + align = XHCI_MIN_ALIGN; + + return align; +} + +/** + * Calculate device context offset + * + * @v xhci xHCI device + * @v ctx Context index + */ +static inline size_t xhci_device_context_offset ( struct xhci_device *xhci, + unsigned int ctx ) { + + return ( XHCI_DCI ( ctx ) << xhci->csz_shift ); +} + +/** + * Calculate input context offset + * + * @v xhci xHCI device + * @v ctx Context index + */ +static inline size_t xhci_input_context_offset ( struct xhci_device *xhci, + unsigned int ctx ) { + + return ( XHCI_ICI ( ctx ) << xhci->csz_shift ); +} + +/****************************************************************************** + * + * Diagnostics + * + ****************************************************************************** + */ + +/** + * Dump host controller registers + * + * @v xhci xHCI device + */ +static inline void xhci_dump ( struct xhci_device *xhci ) { + uint32_t usbcmd; + uint32_t usbsts; + uint32_t pagesize; + uint32_t dnctrl; + uint32_t config; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_LOG ) + return; + + /* Dump USBCMD */ + usbcmd = readl ( xhci->op + XHCI_OP_USBCMD ); + DBGC ( xhci, "XHCI %s USBCMD %08x%s%s\n", xhci->name, usbcmd, + ( ( usbcmd & XHCI_USBCMD_RUN ) ? " run" : "" ), + ( ( usbcmd & XHCI_USBCMD_HCRST ) ? " hcrst" : "" ) ); + + /* Dump USBSTS */ + usbsts = readl ( xhci->op + XHCI_OP_USBSTS ); + DBGC ( xhci, "XHCI %s USBSTS %08x%s\n", xhci->name, usbsts, + ( ( usbsts & XHCI_USBSTS_HCH ) ? " hch" : "" ) ); + + /* Dump PAGESIZE */ + pagesize = readl ( xhci->op + XHCI_OP_PAGESIZE ); + DBGC ( xhci, "XHCI %s PAGESIZE %08x\n", xhci->name, pagesize ); + + /* Dump DNCTRL */ + dnctrl = readl ( xhci->op + XHCI_OP_DNCTRL ); + DBGC ( xhci, "XHCI %s DNCTRL %08x\n", xhci->name, dnctrl ); + + /* Dump CONFIG */ + config = readl ( xhci->op + XHCI_OP_CONFIG ); + DBGC ( xhci, "XHCI %s CONFIG %08x\n", xhci->name, config ); +} + +/** + * Dump port registers + * + * @v xhci xHCI device + * @v port Port number + */ +static inline void xhci_dump_port ( struct xhci_device *xhci, + unsigned int port ) { + uint32_t portsc; + uint32_t portpmsc; + uint32_t portli; + uint32_t porthlpmc; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_LOG ) + return; + + /* Dump PORTSC */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port ) ); + DBGC ( xhci, "XHCI %s-%d PORTSC %08x%s%s%s%s psiv=%d\n", + xhci->name, port, portsc, + ( ( portsc & XHCI_PORTSC_CCS ) ? " ccs" : "" ), + ( ( portsc & XHCI_PORTSC_PED ) ? " ped" : "" ), + ( ( portsc & XHCI_PORTSC_PR ) ? " pr" : "" ), + ( ( portsc & XHCI_PORTSC_PP ) ? " pp" : "" ), + XHCI_PORTSC_PSIV ( portsc ) ); + + /* Dump PORTPMSC */ + portpmsc = readl ( xhci->op + XHCI_OP_PORTPMSC ( port ) ); + DBGC ( xhci, "XHCI %s-%d PORTPMSC %08x\n", xhci->name, port, portpmsc ); + + /* Dump PORTLI */ + portli = readl ( xhci->op + XHCI_OP_PORTLI ( port ) ); + DBGC ( xhci, "XHCI %s-%d PORTLI %08x\n", xhci->name, port, portli ); + + /* Dump PORTHLPMC */ + porthlpmc = readl ( xhci->op + XHCI_OP_PORTHLPMC ( port ) ); + DBGC ( xhci, "XHCI %s-%d PORTHLPMC %08x\n", + xhci->name, port, porthlpmc ); +} + +/****************************************************************************** + * + * USB legacy support + * + ****************************************************************************** + */ + +/** Prevent the release of ownership back to BIOS */ +static int xhci_legacy_prevent_release; + +/** + * Initialise USB legacy support + * + * @v xhci xHCI device + */ +static void xhci_legacy_init ( struct xhci_device *xhci ) { + unsigned int legacy; + uint8_t bios; + + /* Locate USB legacy support capability (if present) */ + legacy = xhci_extended_capability ( xhci, XHCI_XECP_ID_LEGACY, 0 ); + if ( ! legacy ) { + /* Not an error; capability may not be present */ + DBGC ( xhci, "XHCI %s has no USB legacy support capability\n", + xhci->name ); + return; + } + + /* Check if legacy USB support is enabled */ + bios = readb ( xhci->cap + legacy + XHCI_USBLEGSUP_BIOS ); + if ( ! ( bios & XHCI_USBLEGSUP_BIOS_OWNED ) ) { + /* Not an error; already owned by OS */ + DBGC ( xhci, "XHCI %s USB legacy support already disabled\n", + xhci->name ); + return; + } + + /* Record presence of USB legacy support capability */ + xhci->legacy = legacy; +} + +/** + * Claim ownership from BIOS + * + * @v xhci xHCI device + */ +static void xhci_legacy_claim ( struct xhci_device *xhci ) { + uint32_t ctlsts; + uint8_t bios; + unsigned int i; + + /* Do nothing unless legacy support capability is present */ + if ( ! xhci->legacy ) + return; + + /* Claim ownership */ + writeb ( XHCI_USBLEGSUP_OS_OWNED, + xhci->cap + xhci->legacy + XHCI_USBLEGSUP_OS ); + + /* Wait for BIOS to release ownership */ + for ( i = 0 ; i < XHCI_USBLEGSUP_MAX_WAIT_MS ; i++ ) { + + /* Check if BIOS has released ownership */ + bios = readb ( xhci->cap + xhci->legacy + XHCI_USBLEGSUP_BIOS ); + if ( ! ( bios & XHCI_USBLEGSUP_BIOS_OWNED ) ) { + DBGC ( xhci, "XHCI %s claimed ownership from BIOS\n", + xhci->name ); + ctlsts = readl ( xhci->cap + xhci->legacy + + XHCI_USBLEGSUP_CTLSTS ); + if ( ctlsts ) { + DBGC ( xhci, "XHCI %s warning: BIOS retained " + "SMIs: %08x\n", xhci->name, ctlsts ); + } + return; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* BIOS did not release ownership. Claim it forcibly by + * disabling all SMIs. + */ + DBGC ( xhci, "XHCI %s could not claim ownership from BIOS: forcibly " + "disabling SMIs\n", xhci->name ); + writel ( 0, xhci->cap + xhci->legacy + XHCI_USBLEGSUP_CTLSTS ); +} + +/** + * Release ownership back to BIOS + * + * @v xhci xHCI device + */ +static void xhci_legacy_release ( struct xhci_device *xhci ) { + + /* Do nothing unless legacy support capability is present */ + if ( ! xhci->legacy ) + return; + + /* Do nothing if releasing ownership is prevented */ + if ( xhci_legacy_prevent_release ) { + DBGC ( xhci, "XHCI %s not releasing ownership to BIOS\n", + xhci->name ); + return; + } + + /* Release ownership */ + writeb ( 0, xhci->cap + xhci->legacy + XHCI_USBLEGSUP_OS ); + DBGC ( xhci, "XHCI %s released ownership to BIOS\n", xhci->name ); +} + +/****************************************************************************** + * + * Supported protocols + * + ****************************************************************************** + */ + +/** + * Transcribe port speed (for debugging) + * + * @v psi Protocol speed ID + * @ret speed Transcribed speed + */ +static inline const char * xhci_speed_name ( uint32_t psi ) { + static const char *exponents[4] = { "", "k", "M", "G" }; + static char buf[ 10 /* "xxxxxXbps" + NUL */ ]; + unsigned int mantissa; + unsigned int exponent; + + /* Extract mantissa and exponent */ + mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi ); + exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi ); + + /* Transcribe speed */ + snprintf ( buf, sizeof ( buf ), "%d%sbps", + mantissa, exponents[exponent] ); + return buf; +} + +/** + * Find supported protocol extended capability for a port + * + * @v xhci xHCI device + * @v port Port number + * @ret supported Offset to extended capability, or zero if not found + */ +static unsigned int xhci_supported_protocol ( struct xhci_device *xhci, + unsigned int port ) { + unsigned int supported = 0; + unsigned int offset; + unsigned int count; + uint32_t ports; + + /* Iterate over all supported protocol structures */ + while ( ( supported = xhci_extended_capability ( xhci, + XHCI_XECP_ID_SUPPORTED, + supported ) ) ) { + + /* Determine port range */ + ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS ); + offset = XHCI_SUPPORTED_PORTS_OFFSET ( ports ); + count = XHCI_SUPPORTED_PORTS_COUNT ( ports ); + + /* Check if port lies within this range */ + if ( ( port - offset ) < count ) + return supported; + } + + DBGC ( xhci, "XHCI %s-%d has no supported protocol\n", + xhci->name, port ); + return 0; +} + +/** + * Find port protocol + * + * @v xhci xHCI device + * @v port Port number + * @ret protocol USB protocol, or zero if not found + */ +static unsigned int xhci_port_protocol ( struct xhci_device *xhci, + unsigned int port ) { + unsigned int supported = xhci_supported_protocol ( xhci, port ); + union { + uint32_t raw; + char text[5]; + } name; + unsigned int protocol; + unsigned int type; + unsigned int psic; + unsigned int psiv; + unsigned int i; + uint32_t revision; + uint32_t ports; + uint32_t slot; + uint32_t psi; + + /* Fail if there is no supported protocol */ + if ( ! supported ) + return 0; + + /* Determine protocol version */ + revision = readl ( xhci->cap + supported + XHCI_SUPPORTED_REVISION ); + protocol = XHCI_SUPPORTED_REVISION_VER ( revision ); + + /* Describe port protocol */ + if ( DBG_EXTRA ) { + name.raw = cpu_to_le32 ( readl ( xhci->cap + supported + + XHCI_SUPPORTED_NAME ) ); + name.text[4] = '\0'; + slot = readl ( xhci->cap + supported + XHCI_SUPPORTED_SLOT ); + type = XHCI_SUPPORTED_SLOT_TYPE ( slot ); + DBGC2 ( xhci, "XHCI %s-%d %sv%04x type %d", + xhci->name, port, name.text, protocol, type ); + ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS ); + psic = XHCI_SUPPORTED_PORTS_PSIC ( ports ); + if ( psic ) { + DBGC2 ( xhci, " speeds" ); + for ( i = 0 ; i < psic ; i++ ) { + psi = readl ( xhci->cap + supported + + XHCI_SUPPORTED_PSI ( i ) ); + psiv = XHCI_SUPPORTED_PSI_VALUE ( psi ); + DBGC2 ( xhci, " %d:%s", psiv, + xhci_speed_name ( psi ) ); + } + } + if ( xhci->quirks & XHCI_BAD_PSIV ) + DBGC2 ( xhci, " (ignored)" ); + DBGC2 ( xhci, "\n" ); + } + + return protocol; +} + +/** + * Find port slot type + * + * @v xhci xHCI device + * @v port Port number + * @ret type Slot type, or negative error + */ +static int xhci_port_slot_type ( struct xhci_device *xhci, unsigned int port ) { + unsigned int supported = xhci_supported_protocol ( xhci, port ); + unsigned int type; + uint32_t slot; + + /* Fail if there is no supported protocol */ + if ( ! supported ) + return -ENOTSUP; + + /* Get slot type */ + slot = readl ( xhci->cap + supported + XHCI_SUPPORTED_SLOT ); + type = XHCI_SUPPORTED_SLOT_TYPE ( slot ); + + return type; +} + +/** + * Find port speed + * + * @v xhci xHCI device + * @v port Port number + * @v psiv Protocol speed ID value + * @ret speed Port speed, or negative error + */ +static int xhci_port_speed ( struct xhci_device *xhci, unsigned int port, + unsigned int psiv ) { + unsigned int supported = xhci_supported_protocol ( xhci, port ); + unsigned int psic; + unsigned int mantissa; + unsigned int exponent; + unsigned int speed; + unsigned int i; + uint32_t ports; + uint32_t psi; + + /* Fail if there is no supported protocol */ + if ( ! supported ) + return -ENOTSUP; + + /* Get protocol speed ID count */ + ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS ); + psic = XHCI_SUPPORTED_PORTS_PSIC ( ports ); + + /* Use the default mappings if applicable */ + if ( ( psic == 0 ) || ( xhci->quirks & XHCI_BAD_PSIV ) ) { + switch ( psiv ) { + case XHCI_SPEED_LOW : return USB_SPEED_LOW; + case XHCI_SPEED_FULL : return USB_SPEED_FULL; + case XHCI_SPEED_HIGH : return USB_SPEED_HIGH; + case XHCI_SPEED_SUPER : return USB_SPEED_SUPER; + default: + DBGC ( xhci, "XHCI %s-%d non-standard PSI value %d\n", + xhci->name, port, psiv ); + return -ENOTSUP; + } + } + + /* Iterate over PSI dwords looking for a match */ + for ( i = 0 ; i < psic ; i++ ) { + psi = readl ( xhci->cap + supported + XHCI_SUPPORTED_PSI ( i )); + if ( psiv == XHCI_SUPPORTED_PSI_VALUE ( psi ) ) { + mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi ); + exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi ); + speed = USB_SPEED ( mantissa, exponent ); + return speed; + } + } + + DBGC ( xhci, "XHCI %s-%d spurious PSI value %d\n", + xhci->name, port, psiv ); + return -ENOENT; +} + +/** + * Find protocol speed ID value + * + * @v xhci xHCI device + * @v port Port number + * @v speed USB speed + * @ret psiv Protocol speed ID value, or negative error + */ +static int xhci_port_psiv ( struct xhci_device *xhci, unsigned int port, + unsigned int speed ) { + unsigned int supported = xhci_supported_protocol ( xhci, port ); + unsigned int psic; + unsigned int mantissa; + unsigned int exponent; + unsigned int psiv; + unsigned int i; + uint32_t ports; + uint32_t psi; + + /* Fail if there is no supported protocol */ + if ( ! supported ) + return -ENOTSUP; + + /* Get protocol speed ID count */ + ports = readl ( xhci->cap + supported + XHCI_SUPPORTED_PORTS ); + psic = XHCI_SUPPORTED_PORTS_PSIC ( ports ); + + /* Use the default mappings if applicable */ + if ( ( psic == 0 ) || ( xhci->quirks & XHCI_BAD_PSIV ) ) { + switch ( speed ) { + case USB_SPEED_LOW : return XHCI_SPEED_LOW; + case USB_SPEED_FULL : return XHCI_SPEED_FULL; + case USB_SPEED_HIGH : return XHCI_SPEED_HIGH; + case USB_SPEED_SUPER : return XHCI_SPEED_SUPER; + default: + DBGC ( xhci, "XHCI %s-%d non-standard speed %d\n", + xhci->name, port, speed ); + return -ENOTSUP; + } + } + + /* Iterate over PSI dwords looking for a match */ + for ( i = 0 ; i < psic ; i++ ) { + psi = readl ( xhci->cap + supported + XHCI_SUPPORTED_PSI ( i )); + mantissa = XHCI_SUPPORTED_PSI_MANTISSA ( psi ); + exponent = XHCI_SUPPORTED_PSI_EXPONENT ( psi ); + if ( speed == USB_SPEED ( mantissa, exponent ) ) { + psiv = XHCI_SUPPORTED_PSI_VALUE ( psi ); + return psiv; + } + } + + DBGC ( xhci, "XHCI %s-%d unrepresentable speed %#x\n", + xhci->name, port, speed ); + return -ENOENT; +} + +/****************************************************************************** + * + * Device context base address array + * + ****************************************************************************** + */ + +/** + * Allocate device context base address array + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_dcbaa_alloc ( struct xhci_device *xhci ) { + size_t len; + physaddr_t dcbaap; + int rc; + + /* Allocate and initialise structure. Must be at least + * 64-byte aligned and must not cross a page boundary, so + * align on its own size (rounded up to a power of two and + * with a minimum of 64 bytes). + */ + len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa[0] ) ); + xhci->dcbaa = malloc_dma ( len, xhci_align ( len ) ); + if ( ! xhci->dcbaa ) { + DBGC ( xhci, "XHCI %s could not allocate DCBAA\n", xhci->name ); + rc = -ENOMEM; + goto err_alloc; + } + memset ( xhci->dcbaa, 0, len ); + + /* Program DCBAA pointer */ + dcbaap = virt_to_phys ( xhci->dcbaa ); + if ( ( rc = xhci_writeq ( xhci, dcbaap, + xhci->op + XHCI_OP_DCBAAP ) ) != 0 ) + goto err_writeq; + + DBGC2 ( xhci, "XHCI %s DCBAA at [%08lx,%08lx)\n", + xhci->name, dcbaap, ( dcbaap + len ) ); + return 0; + + err_writeq: + free_dma ( xhci->dcbaa, len ); + err_alloc: + return rc; +} + +/** + * Free device context base address array + * + * @v xhci xHCI device + */ +static void xhci_dcbaa_free ( struct xhci_device *xhci ) { + size_t len; + unsigned int i; + + /* Sanity check */ + for ( i = 0 ; i <= xhci->slots ; i++ ) + assert ( xhci->dcbaa[i] == 0 ); + + /* Clear DCBAA pointer */ + xhci_writeq ( xhci, 0, xhci->op + XHCI_OP_DCBAAP ); + + /* Free DCBAA */ + len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa[0] ) ); + free_dma ( xhci->dcbaa, len ); +} + +/****************************************************************************** + * + * Scratchpad buffers + * + ****************************************************************************** + */ + +/** + * Allocate scratchpad buffers + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_scratchpad_alloc ( struct xhci_device *xhci ) { + size_t array_len; + size_t len; + physaddr_t phys; + unsigned int i; + int rc; + + /* Do nothing if no scratchpad buffers are used */ + if ( ! xhci->scratchpads ) + return 0; + + /* Allocate scratchpads */ + len = ( xhci->scratchpads * xhci->pagesize ); + xhci->scratchpad = umalloc ( len ); + if ( ! xhci->scratchpad ) { + DBGC ( xhci, "XHCI %s could not allocate scratchpad buffers\n", + xhci->name ); + rc = -ENOMEM; + goto err_alloc; + } + memset_user ( xhci->scratchpad, 0, 0, len ); + + /* Allocate scratchpad array */ + array_len = ( xhci->scratchpads * sizeof ( xhci->scratchpad_array[0] )); + xhci->scratchpad_array = + malloc_dma ( array_len, xhci_align ( array_len ) ); + if ( ! xhci->scratchpad_array ) { + DBGC ( xhci, "XHCI %s could not allocate scratchpad buffer " + "array\n", xhci->name ); + rc = -ENOMEM; + goto err_alloc_array; + } + + /* Populate scratchpad array */ + for ( i = 0 ; i < xhci->scratchpads ; i++ ) { + phys = user_to_phys ( xhci->scratchpad, ( i * xhci->pagesize )); + xhci->scratchpad_array[i] = phys; + } + + /* Set scratchpad array pointer */ + assert ( xhci->dcbaa != NULL ); + xhci->dcbaa[0] = cpu_to_le64 ( virt_to_phys ( xhci->scratchpad_array )); + + DBGC2 ( xhci, "XHCI %s scratchpad [%08lx,%08lx) array [%08lx,%08lx)\n", + xhci->name, user_to_phys ( xhci->scratchpad, 0 ), + user_to_phys ( xhci->scratchpad, len ), + virt_to_phys ( xhci->scratchpad_array ), + ( virt_to_phys ( xhci->scratchpad_array ) + array_len ) ); + return 0; + + free_dma ( xhci->scratchpad_array, array_len ); + err_alloc_array: + ufree ( xhci->scratchpad ); + err_alloc: + return rc; +} + +/** + * Free scratchpad buffers + * + * @v xhci xHCI device + */ +static void xhci_scratchpad_free ( struct xhci_device *xhci ) { + size_t array_len; + + /* Do nothing if no scratchpad buffers are used */ + if ( ! xhci->scratchpads ) + return; + + /* Clear scratchpad array pointer */ + assert ( xhci->dcbaa != NULL ); + xhci->dcbaa[0] = 0; + + /* Free scratchpad array */ + array_len = ( xhci->scratchpads * sizeof ( xhci->scratchpad_array[0] )); + free_dma ( xhci->scratchpad_array, array_len ); + + /* Free scratchpads */ + ufree ( xhci->scratchpad ); +} + +/****************************************************************************** + * + * Run / stop / reset + * + ****************************************************************************** + */ + +/** + * Start xHCI device + * + * @v xhci xHCI device + */ +static void xhci_run ( struct xhci_device *xhci ) { + uint32_t config; + uint32_t usbcmd; + + /* Configure number of device slots */ + config = readl ( xhci->op + XHCI_OP_CONFIG ); + config &= ~XHCI_CONFIG_MAX_SLOTS_EN_MASK; + config |= XHCI_CONFIG_MAX_SLOTS_EN ( xhci->slots ); + writel ( config, xhci->op + XHCI_OP_CONFIG ); + + /* Set run/stop bit */ + usbcmd = readl ( xhci->op + XHCI_OP_USBCMD ); + usbcmd |= XHCI_USBCMD_RUN; + writel ( usbcmd, xhci->op + XHCI_OP_USBCMD ); +} + +/** + * Stop xHCI device + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_stop ( struct xhci_device *xhci ) { + uint32_t usbcmd; + uint32_t usbsts; + unsigned int i; + + /* Clear run/stop bit */ + usbcmd = readl ( xhci->op + XHCI_OP_USBCMD ); + usbcmd &= ~XHCI_USBCMD_RUN; + writel ( usbcmd, xhci->op + XHCI_OP_USBCMD ); + + /* Wait for device to stop */ + for ( i = 0 ; i < XHCI_STOP_MAX_WAIT_MS ; i++ ) { + + /* Check if device is stopped */ + usbsts = readl ( xhci->op + XHCI_OP_USBSTS ); + if ( usbsts & XHCI_USBSTS_HCH ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( xhci, "XHCI %s timed out waiting for stop\n", xhci->name ); + return -ETIMEDOUT; +} + +/** + * Reset xHCI device + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_reset ( struct xhci_device *xhci ) { + uint32_t usbcmd; + unsigned int i; + int rc; + + /* The xHCI specification states that resetting a running + * device may result in undefined behaviour, so try stopping + * it first. + */ + if ( ( rc = xhci_stop ( xhci ) ) != 0 ) { + /* Ignore errors and attempt to reset the device anyway */ + } + + /* Reset device */ + writel ( XHCI_USBCMD_HCRST, xhci->op + XHCI_OP_USBCMD ); + + /* Wait for reset to complete */ + for ( i = 0 ; i < XHCI_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check if reset is complete */ + usbcmd = readl ( xhci->op + XHCI_OP_USBCMD ); + if ( ! ( usbcmd & XHCI_USBCMD_HCRST ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( xhci, "XHCI %s timed out waiting for reset\n", xhci->name ); + return -ETIMEDOUT; +} + +/****************************************************************************** + * + * Transfer request blocks + * + ****************************************************************************** + */ + +/** + * Allocate transfer request block ring + * + * @v xhci xHCI device + * @v ring TRB ring + * @v shift Ring size (log2) + * @v slot Device slot + * @v target Doorbell target + * @v stream Doorbell stream ID + * @ret rc Return status code + */ +static int xhci_ring_alloc ( struct xhci_device *xhci, + struct xhci_trb_ring *ring, + unsigned int shift, unsigned int slot, + unsigned int target, unsigned int stream ) { + struct xhci_trb_link *link; + unsigned int count; + int rc; + + /* Sanity check */ + assert ( shift > 0 ); + + /* Initialise structure */ + memset ( ring, 0, sizeof ( *ring ) ); + ring->shift = shift; + count = ( 1U << shift ); + ring->mask = ( count - 1 ); + ring->len = ( ( count + 1 /* Link TRB */ ) * sizeof ( ring->trb[0] ) ); + ring->db = ( xhci->db + ( slot * sizeof ( ring->dbval ) ) ); + ring->dbval = XHCI_DBVAL ( target, stream ); + + /* Allocate I/O buffers */ + ring->iobuf = zalloc ( count * sizeof ( ring->iobuf[0] ) ); + if ( ! ring->iobuf ) { + rc = -ENOMEM; + goto err_alloc_iobuf; + } + + /* Allocate TRBs */ + ring->trb = malloc_dma ( ring->len, xhci_align ( ring->len ) ); + if ( ! ring->trb ) { + rc = -ENOMEM; + goto err_alloc_trb; + } + memset ( ring->trb, 0, ring->len ); + + /* Initialise Link TRB */ + link = &ring->trb[count].link; + link->next = cpu_to_le64 ( virt_to_phys ( ring->trb ) ); + link->flags = XHCI_TRB_TC; + link->type = XHCI_TRB_LINK; + ring->link = link; + + return 0; + + free_dma ( ring->trb, ring->len ); + err_alloc_trb: + free ( ring->iobuf ); + err_alloc_iobuf: + return rc; +} + +/** + * Reset transfer request block ring + * + * @v ring TRB ring + */ +static void xhci_ring_reset ( struct xhci_trb_ring *ring ) { + unsigned int count = ( 1U << ring->shift ); + + /* Reset producer and consumer counters */ + ring->prod = 0; + ring->cons = 0; + + /* Reset TRBs (except Link TRB) */ + memset ( ring->trb, 0, ( count * sizeof ( ring->trb[0] ) ) ); +} + +/** + * Free transfer request block ring + * + * @v ring TRB ring + */ +static void xhci_ring_free ( struct xhci_trb_ring *ring ) { + unsigned int count = ( 1U << ring->shift ); + unsigned int i; + + /* Sanity checks */ + assert ( ring->cons == ring->prod ); + for ( i = 0 ; i < count ; i++ ) + assert ( ring->iobuf[i] == NULL ); + + /* Free TRBs */ + free_dma ( ring->trb, ring->len ); + + /* Free I/O buffers */ + free ( ring->iobuf ); +} + +/** + * Enqueue a transfer request block + * + * @v ring TRB ring + * @v iobuf I/O buffer (if any) + * @v trb Transfer request block (with empty Cycle flag) + * @ret rc Return status code + * + * This operation does not implicitly ring the doorbell register. + */ +static int xhci_enqueue ( struct xhci_trb_ring *ring, struct io_buffer *iobuf, + const union xhci_trb *trb ) { + union xhci_trb *dest; + unsigned int prod; + unsigned int mask; + unsigned int index; + unsigned int cycle; + + /* Sanity check */ + assert ( ! ( trb->common.flags & XHCI_TRB_C ) ); + + /* Fail if ring is full */ + if ( ! xhci_ring_remaining ( ring ) ) + return -ENOBUFS; + + /* Update producer counter (and link TRB, if applicable) */ + prod = ring->prod++; + mask = ring->mask; + cycle = ( ( ~( prod >> ring->shift ) ) & XHCI_TRB_C ); + index = ( prod & mask ); + if ( index == 0 ) + ring->link->flags = ( XHCI_TRB_TC | ( cycle ^ XHCI_TRB_C ) ); + + /* Record I/O buffer */ + ring->iobuf[index] = iobuf; + + /* Enqueue TRB */ + dest = &ring->trb[index]; + dest->template.parameter = trb->template.parameter; + dest->template.status = trb->template.status; + wmb(); + dest->template.control = ( trb->template.control | + cpu_to_le32 ( cycle ) ); + + return 0; +} + +/** + * Dequeue a transfer request block + * + * @v ring TRB ring + * @ret iobuf I/O buffer + */ +static struct io_buffer * xhci_dequeue ( struct xhci_trb_ring *ring ) { + struct io_buffer *iobuf; + unsigned int cons; + unsigned int mask; + unsigned int index; + + /* Sanity check */ + assert ( xhci_ring_fill ( ring ) != 0 ); + + /* Update consumer counter */ + cons = ring->cons++; + mask = ring->mask; + index = ( cons & mask ); + + /* Retrieve I/O buffer */ + iobuf = ring->iobuf[index]; + ring->iobuf[index] = NULL; + + return iobuf; +} + +/** + * Enqueue multiple transfer request blocks + * + * @v ring TRB ring + * @v iobuf I/O buffer + * @v trbs Transfer request blocks (with empty Cycle flag) + * @v count Number of transfer request blocks + * @ret rc Return status code + * + * This operation does not implicitly ring the doorbell register. + */ +static int xhci_enqueue_multi ( struct xhci_trb_ring *ring, + struct io_buffer *iobuf, + const union xhci_trb *trbs, + unsigned int count ) { + const union xhci_trb *trb = trbs; + int rc; + + /* Sanity check */ + assert ( iobuf != NULL ); + + /* Fail if ring does not have sufficient space */ + if ( xhci_ring_remaining ( ring ) < count ) + return -ENOBUFS; + + /* Enqueue each TRB, recording the I/O buffer with the final TRB */ + while ( count-- ) { + rc = xhci_enqueue ( ring, ( count ? NULL : iobuf ), trb++ ); + assert ( rc == 0 ); /* Should never be able to fail */ + } + + return 0; +} + +/** + * Dequeue multiple transfer request blocks + * + * @v ring TRB ring + * @ret iobuf I/O buffer + */ +static struct io_buffer * xhci_dequeue_multi ( struct xhci_trb_ring *ring ) { + struct io_buffer *iobuf; + + /* Dequeue TRBs until we reach the final TRB for an I/O buffer */ + do { + iobuf = xhci_dequeue ( ring ); + } while ( iobuf == NULL ); + + return iobuf; +} + +/** + * Ring doorbell register + * + * @v ring TRB ring + */ +static inline __attribute__ (( always_inline )) void +xhci_doorbell ( struct xhci_trb_ring *ring ) { + + wmb(); + writel ( ring->dbval, ring->db ); +} + +/****************************************************************************** + * + * Command and event rings + * + ****************************************************************************** + */ + +/** + * Allocate command ring + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_command_alloc ( struct xhci_device *xhci ) { + physaddr_t crp; + int rc; + + /* Allocate TRB ring */ + if ( ( rc = xhci_ring_alloc ( xhci, &xhci->command, XHCI_CMD_TRBS_LOG2, + 0, 0, 0 ) ) != 0 ) + goto err_ring_alloc; + + /* Program command ring control register */ + crp = virt_to_phys ( xhci->command.trb ); + if ( ( rc = xhci_writeq ( xhci, ( crp | XHCI_CRCR_RCS ), + xhci->op + XHCI_OP_CRCR ) ) != 0 ) + goto err_writeq; + + DBGC2 ( xhci, "XHCI %s CRCR at [%08lx,%08lx)\n", + xhci->name, crp, ( crp + xhci->command.len ) ); + return 0; + + err_writeq: + xhci_ring_free ( &xhci->command ); + err_ring_alloc: + return rc; +} + +/** + * Free command ring + * + * @v xhci xHCI device + */ +static void xhci_command_free ( struct xhci_device *xhci ) { + + /* Sanity check */ + assert ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 ); + + /* Clear command ring control register */ + xhci_writeq ( xhci, 0, xhci->op + XHCI_OP_CRCR ); + + /* Free TRB ring */ + xhci_ring_free ( &xhci->command ); +} + +/** + * Allocate event ring + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_event_alloc ( struct xhci_device *xhci ) { + struct xhci_event_ring *event = &xhci->event; + unsigned int count; + size_t len; + int rc; + + /* Allocate event ring */ + count = ( 1 << XHCI_EVENT_TRBS_LOG2 ); + len = ( count * sizeof ( event->trb[0] ) ); + event->trb = malloc_dma ( len, xhci_align ( len ) ); + if ( ! event->trb ) { + rc = -ENOMEM; + goto err_alloc_trb; + } + memset ( event->trb, 0, len ); + + /* Allocate event ring segment table */ + event->segment = malloc_dma ( sizeof ( event->segment[0] ), + xhci_align ( sizeof (event->segment[0]))); + if ( ! event->segment ) { + rc = -ENOMEM; + goto err_alloc_segment; + } + memset ( event->segment, 0, sizeof ( event->segment[0] ) ); + event->segment[0].base = cpu_to_le64 ( virt_to_phys ( event->trb ) ); + event->segment[0].count = cpu_to_le32 ( count ); + + /* Program event ring registers */ + writel ( 1, xhci->run + XHCI_RUN_ERSTSZ ( 0 ) ); + if ( ( rc = xhci_writeq ( xhci, virt_to_phys ( event->trb ), + xhci->run + XHCI_RUN_ERDP ( 0 ) ) ) != 0 ) + goto err_writeq_erdp; + if ( ( rc = xhci_writeq ( xhci, virt_to_phys ( event->segment ), + xhci->run + XHCI_RUN_ERSTBA ( 0 ) ) ) != 0 ) + goto err_writeq_erstba; + + DBGC2 ( xhci, "XHCI %s event ring [%08lx,%08lx) table [%08lx,%08lx)\n", + xhci->name, virt_to_phys ( event->trb ), + ( virt_to_phys ( event->trb ) + len ), + virt_to_phys ( event->segment ), + ( virt_to_phys ( event->segment ) + + sizeof (event->segment[0] ) ) ); + return 0; + + xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERSTBA ( 0 ) ); + err_writeq_erstba: + xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERDP ( 0 ) ); + err_writeq_erdp: + free_dma ( event->trb, len ); + err_alloc_segment: + free_dma ( event->segment, sizeof ( event->segment[0] ) ); + err_alloc_trb: + return rc; +} + +/** + * Free event ring + * + * @v xhci xHCI device + */ +static void xhci_event_free ( struct xhci_device *xhci ) { + struct xhci_event_ring *event = &xhci->event; + unsigned int count; + size_t len; + + /* Clear event ring registers */ + writel ( 0, xhci->run + XHCI_RUN_ERSTSZ ( 0 ) ); + xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERSTBA ( 0 ) ); + xhci_writeq ( xhci, 0, xhci->run + XHCI_RUN_ERDP ( 0 ) ); + + /* Free event ring segment table */ + free_dma ( event->segment, sizeof ( event->segment[0] ) ); + + /* Free event ring */ + count = ( 1 << XHCI_EVENT_TRBS_LOG2 ); + len = ( count * sizeof ( event->trb[0] ) ); + free_dma ( event->trb, len ); +} + +/** + * Handle transfer event + * + * @v xhci xHCI device + * @v trb Transfer event TRB + */ +static void xhci_transfer ( struct xhci_device *xhci, + struct xhci_trb_transfer *trb ) { + struct xhci_slot *slot; + struct xhci_endpoint *endpoint; + struct io_buffer *iobuf; + int rc; + + /* Profile transfer events */ + profile_start ( &xhci_transfer_profiler ); + + /* Identify slot */ + if ( ( trb->slot > xhci->slots ) || + ( ( slot = xhci->slot[trb->slot] ) == NULL ) ) { + DBGC ( xhci, "XHCI %s transfer event invalid slot %d:\n", + xhci->name, trb->slot ); + DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) ); + return; + } + + /* Identify endpoint */ + if ( ( trb->endpoint > XHCI_CTX_END ) || + ( ( endpoint = slot->endpoint[trb->endpoint] ) == NULL ) ) { + DBGC ( xhci, "XHCI %s slot %d transfer event invalid epid " + "%d:\n", xhci->name, slot->id, trb->endpoint ); + DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) ); + return; + } + + /* Dequeue TRB(s) */ + iobuf = xhci_dequeue_multi ( &endpoint->ring ); + assert ( iobuf != NULL ); + + /* Check for errors */ + if ( ! ( ( trb->code == XHCI_CMPLT_SUCCESS ) || + ( trb->code == XHCI_CMPLT_SHORT ) ) ) { + + /* Construct error */ + rc = -ECODE ( trb->code ); + DBGC ( xhci, "XHCI %s slot %d ctx %d failed (code %d): %s\n", + xhci->name, slot->id, endpoint->ctx, trb->code, + strerror ( rc ) ); + DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) ); + + /* Sanity check */ + assert ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK ) + != XHCI_ENDPOINT_RUNNING ); + + /* Report failure to USB core */ + usb_complete_err ( endpoint->ep, iobuf, rc ); + return; + } + + /* Record actual transfer size */ + iob_unput ( iobuf, le16_to_cpu ( trb->residual ) ); + + /* Sanity check (for successful completions only) */ + assert ( xhci_ring_consumed ( &endpoint->ring ) == + le64_to_cpu ( trb->transfer ) ); + + /* Report completion to USB core */ + usb_complete ( endpoint->ep, iobuf ); + profile_stop ( &xhci_transfer_profiler ); +} + +/** + * Handle command completion event + * + * @v xhci xHCI device + * @v trb Command completion event + */ +static void xhci_complete ( struct xhci_device *xhci, + struct xhci_trb_complete *trb ) { + int rc; + + /* Ignore "command ring stopped" notifications */ + if ( trb->code == XHCI_CMPLT_CMD_STOPPED ) { + DBGC2 ( xhci, "XHCI %s command ring stopped\n", xhci->name ); + return; + } + + /* Ignore unexpected completions */ + if ( ! xhci->pending ) { + rc = -ECODE ( trb->code ); + DBGC ( xhci, "XHCI %s unexpected completion (code %d): %s\n", + xhci->name, trb->code, strerror ( rc ) ); + DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) ); + return; + } + + /* Dequeue command TRB */ + xhci_dequeue ( &xhci->command ); + + /* Sanity check */ + assert ( xhci_ring_consumed ( &xhci->command ) == + le64_to_cpu ( trb->command ) ); + + /* Record completion */ + memcpy ( xhci->pending, trb, sizeof ( *xhci->pending ) ); + xhci->pending = NULL; +} + +/** + * Handle port status event + * + * @v xhci xHCI device + * @v trb Port status event + */ +static void xhci_port_status ( struct xhci_device *xhci, + struct xhci_trb_port_status *trb ) { + struct usb_port *port = usb_port ( xhci->bus->hub, trb->port ); + uint32_t portsc; + + /* Sanity check */ + assert ( ( trb->port > 0 ) && ( trb->port <= xhci->ports ) ); + + /* Record disconnections and clear changes */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( trb->port ) ); + port->disconnected |= ( portsc & XHCI_PORTSC_CSC ); + portsc &= ( XHCI_PORTSC_PRESERVE | XHCI_PORTSC_CHANGE ); + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( trb->port ) ); + + /* Report port status change */ + usb_port_changed ( port ); +} + +/** + * Handle host controller event + * + * @v xhci xHCI device + * @v trb Host controller event + */ +static void xhci_host_controller ( struct xhci_device *xhci, + struct xhci_trb_host_controller *trb ) { + int rc; + + /* Construct error */ + rc = -ECODE ( trb->code ); + DBGC ( xhci, "XHCI %s host controller event (code %d): %s\n", + xhci->name, trb->code, strerror ( rc ) ); +} + +/** + * Poll event ring + * + * @v xhci xHCI device + */ +static void xhci_event_poll ( struct xhci_device *xhci ) { + struct xhci_event_ring *event = &xhci->event; + union xhci_trb *trb; + unsigned int shift = XHCI_EVENT_TRBS_LOG2; + unsigned int count = ( 1 << shift ); + unsigned int mask = ( count - 1 ); + unsigned int consumed; + unsigned int type; + + /* Poll for events */ + profile_start ( &xhci_event_profiler ); + for ( consumed = 0 ; ; consumed++ ) { + + /* Stop if we reach an empty TRB */ + rmb(); + trb = &event->trb[ event->cons & mask ]; + if ( ! ( ( trb->common.flags ^ + ( event->cons >> shift ) ) & XHCI_TRB_C ) ) + break; + + /* Handle TRB */ + type = ( trb->common.type & XHCI_TRB_TYPE_MASK ); + switch ( type ) { + + case XHCI_TRB_TRANSFER : + xhci_transfer ( xhci, &trb->transfer ); + break; + + case XHCI_TRB_COMPLETE : + xhci_complete ( xhci, &trb->complete ); + break; + + case XHCI_TRB_PORT_STATUS: + xhci_port_status ( xhci, &trb->port ); + break; + + case XHCI_TRB_HOST_CONTROLLER: + xhci_host_controller ( xhci, &trb->host ); + break; + + default: + DBGC ( xhci, "XHCI %s unrecognised event %#x\n:", + xhci->name, event->cons ); + DBGC_HDA ( xhci, virt_to_phys ( trb ), + trb, sizeof ( *trb ) ); + break; + } + + /* Consume this TRB */ + event->cons++; + } + + /* Update dequeue pointer if applicable */ + if ( consumed ) { + xhci_writeq ( xhci, virt_to_phys ( trb ), + xhci->run + XHCI_RUN_ERDP ( 0 ) ); + profile_stop ( &xhci_event_profiler ); + } +} + +/** + * Abort command + * + * @v xhci xHCI device + */ +static void xhci_abort ( struct xhci_device *xhci ) { + physaddr_t crp; + + /* Abort the command */ + DBGC2 ( xhci, "XHCI %s aborting command\n", xhci->name ); + xhci_writeq ( xhci, XHCI_CRCR_CA, xhci->op + XHCI_OP_CRCR ); + + /* Allow time for command to abort */ + mdelay ( XHCI_COMMAND_ABORT_DELAY_MS ); + + /* Sanity check */ + assert ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 ); + + /* Consume (and ignore) any final command status */ + xhci_event_poll ( xhci ); + + /* Reset the command ring control register */ + xhci_ring_reset ( &xhci->command ); + crp = virt_to_phys ( xhci->command.trb ); + xhci_writeq ( xhci, ( crp | XHCI_CRCR_RCS ), xhci->op + XHCI_OP_CRCR ); +} + +/** + * Issue command and wait for completion + * + * @v xhci xHCI device + * @v trb Transfer request block (with empty Cycle flag) + * @ret rc Return status code + * + * On a successful completion, the TRB will be overwritten with the + * completion. + */ +static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) { + struct xhci_trb_complete *complete = &trb->complete; + unsigned int i; + int rc; + + /* Record the pending command */ + xhci->pending = trb; + + /* Enqueue the command */ + if ( ( rc = xhci_enqueue ( &xhci->command, NULL, trb ) ) != 0 ) + goto err_enqueue; + + /* Ring the command doorbell */ + xhci_doorbell ( &xhci->command ); + + /* Wait for the command to complete */ + for ( i = 0 ; i < XHCI_COMMAND_MAX_WAIT_MS ; i++ ) { + + /* Poll event ring */ + xhci_event_poll ( xhci ); + + /* Check for completion */ + if ( ! xhci->pending ) { + if ( complete->code != XHCI_CMPLT_SUCCESS ) { + rc = -ECODE ( complete->code ); + DBGC ( xhci, "XHCI %s command failed (code " + "%d): %s\n", xhci->name, complete->code, + strerror ( rc ) ); + DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) ); + return rc; + } + return 0; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* Timeout */ + DBGC ( xhci, "XHCI %s timed out waiting for completion\n", xhci->name ); + rc = -ETIMEDOUT; + + /* Abort command */ + xhci_abort ( xhci ); + + err_enqueue: + xhci->pending = NULL; + return rc; +} + +/** + * Issue NOP and wait for completion + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static inline int xhci_nop ( struct xhci_device *xhci ) { + union xhci_trb trb; + struct xhci_trb_common *nop = &trb.common; + int rc; + + /* Construct command */ + memset ( nop, 0, sizeof ( *nop ) ); + nop->flags = XHCI_TRB_IOC; + nop->type = XHCI_TRB_NOP_CMD; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Enable slot + * + * @v xhci xHCI device + * @v type Slot type + * @ret slot Device slot ID, or negative error + */ +static inline int xhci_enable_slot ( struct xhci_device *xhci, + unsigned int type ) { + union xhci_trb trb; + struct xhci_trb_enable_slot *enable = &trb.enable; + struct xhci_trb_complete *enabled = &trb.complete; + unsigned int slot; + int rc; + + /* Construct command */ + memset ( enable, 0, sizeof ( *enable ) ); + enable->slot = type; + enable->type = XHCI_TRB_ENABLE_SLOT; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) { + DBGC ( xhci, "XHCI %s could not enable new slot: %s\n", + xhci->name, strerror ( rc ) ); + return rc; + } + + /* Extract slot number */ + slot = enabled->slot; + + DBGC2 ( xhci, "XHCI %s slot %d enabled\n", xhci->name, slot ); + return slot; +} + +/** + * Disable slot + * + * @v xhci xHCI device + * @v slot Device slot + * @ret rc Return status code + */ +static inline int xhci_disable_slot ( struct xhci_device *xhci, + unsigned int slot ) { + union xhci_trb trb; + struct xhci_trb_disable_slot *disable = &trb.disable; + int rc; + + /* Construct command */ + memset ( disable, 0, sizeof ( *disable ) ); + disable->type = XHCI_TRB_DISABLE_SLOT; + disable->slot = slot; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) { + DBGC ( xhci, "XHCI %s could not disable slot %d: %s\n", + xhci->name, slot, strerror ( rc ) ); + return rc; + } + + DBGC2 ( xhci, "XHCI %s slot %d disabled\n", xhci->name, slot ); + return 0; +} + +/** + * Issue context-based command and wait for completion + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @v type TRB type + * @v populate Input context populater + * @ret rc Return status code + */ +static int xhci_context ( struct xhci_device *xhci, struct xhci_slot *slot, + struct xhci_endpoint *endpoint, unsigned int type, + void ( * populate ) ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint, + void *input ) ) { + union xhci_trb trb; + struct xhci_trb_context *context = &trb.context; + size_t len; + void *input; + int rc; + + /* Allocate an input context */ + len = xhci_input_context_offset ( xhci, XHCI_CTX_END ); + input = malloc_dma ( len, xhci_align ( len ) ); + if ( ! input ) { + rc = -ENOMEM; + goto err_alloc; + } + memset ( input, 0, len ); + + /* Populate input context */ + populate ( xhci, slot, endpoint, input ); + + /* Construct command */ + memset ( context, 0, sizeof ( *context ) ); + context->type = type; + context->input = cpu_to_le64 ( virt_to_phys ( input ) ); + context->slot = slot->id; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) + goto err_command; + + err_command: + free_dma ( input, len ); + err_alloc: + return rc; +} + +/** + * Populate address device input context + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @v input Input context + */ +static void xhci_address_device_input ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint, + void *input ) { + struct xhci_control_context *control_ctx; + struct xhci_slot_context *slot_ctx; + struct xhci_endpoint_context *ep_ctx; + + /* Sanity checks */ + assert ( endpoint->ctx == XHCI_CTX_EP0 ); + + /* Populate control context */ + control_ctx = input; + control_ctx->add = cpu_to_le32 ( ( 1 << XHCI_CTX_SLOT ) | + ( 1 << XHCI_CTX_EP0 ) ); + + /* Populate slot context */ + slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT )); + slot_ctx->info = cpu_to_le32 ( XHCI_SLOT_INFO ( 1, 0, slot->psiv, + slot->route ) ); + slot_ctx->port = slot->port; + slot_ctx->tt_id = slot->tt_id; + slot_ctx->tt_port = slot->tt_port; + + /* Populate control endpoint context */ + ep_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_EP0 ) ); + ep_ctx->type = XHCI_EP_TYPE_CONTROL; + ep_ctx->burst = endpoint->ep->burst; + ep_ctx->mtu = cpu_to_le16 ( endpoint->ep->mtu ); + ep_ctx->dequeue = cpu_to_le64 ( virt_to_phys ( endpoint->ring.trb ) | + XHCI_EP_DCS ); + ep_ctx->trb_len = cpu_to_le16 ( XHCI_EP0_TRB_LEN ); +} + +/** + * Address device + * + * @v xhci xHCI device + * @v slot Device slot + * @ret rc Return status code + */ +static inline int xhci_address_device ( struct xhci_device *xhci, + struct xhci_slot *slot ) { + struct usb_device *usb = slot->usb; + struct xhci_slot_context *slot_ctx; + int rc; + + /* Assign device address */ + if ( ( rc = xhci_context ( xhci, slot, slot->endpoint[XHCI_CTX_EP0], + XHCI_TRB_ADDRESS_DEVICE, + xhci_address_device_input ) ) != 0 ) + return rc; + + /* Get assigned address */ + slot_ctx = ( slot->context + + xhci_device_context_offset ( xhci, XHCI_CTX_SLOT ) ); + usb->address = slot_ctx->address; + DBGC2 ( xhci, "XHCI %s assigned address %d to %s\n", + xhci->name, usb->address, usb->name ); + + return 0; +} + +/** + * Populate configure endpoint input context + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @v input Input context + */ +static void xhci_configure_endpoint_input ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint, + void *input ) { + struct xhci_control_context *control_ctx; + struct xhci_slot_context *slot_ctx; + struct xhci_endpoint_context *ep_ctx; + + /* Populate control context */ + control_ctx = input; + control_ctx->add = cpu_to_le32 ( ( 1 << XHCI_CTX_SLOT ) | + ( 1 << endpoint->ctx ) ); + + /* Populate slot context */ + slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT )); + slot_ctx->info = cpu_to_le32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ), + ( slot->ports ? 1 : 0 ), + slot->psiv, 0 ) ); + slot_ctx->ports = slot->ports; + + /* Populate endpoint context */ + ep_ctx = ( input + xhci_input_context_offset ( xhci, endpoint->ctx ) ); + ep_ctx->interval = endpoint->interval; + ep_ctx->type = endpoint->type; + ep_ctx->burst = endpoint->ep->burst; + ep_ctx->mtu = cpu_to_le16 ( endpoint->ep->mtu ); + ep_ctx->dequeue = cpu_to_le64 ( virt_to_phys ( endpoint->ring.trb ) | + XHCI_EP_DCS ); + ep_ctx->trb_len = cpu_to_le16 ( endpoint->ep->mtu ); /* best guess */ +} + +/** + * Configure endpoint + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int xhci_configure_endpoint ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + int rc; + + /* Configure endpoint */ + if ( ( rc = xhci_context ( xhci, slot, endpoint, + XHCI_TRB_CONFIGURE_ENDPOINT, + xhci_configure_endpoint_input ) ) != 0 ) + return rc; + + DBGC2 ( xhci, "XHCI %s slot %d ctx %d configured\n", + xhci->name, slot->id, endpoint->ctx ); + return 0; +} + +/** + * Populate deconfigure endpoint input context + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @v input Input context + */ +static void +xhci_deconfigure_endpoint_input ( struct xhci_device *xhci __unused, + struct xhci_slot *slot __unused, + struct xhci_endpoint *endpoint, + void *input ) { + struct xhci_control_context *control_ctx; + struct xhci_slot_context *slot_ctx; + + /* Populate control context */ + control_ctx = input; + control_ctx->add = cpu_to_le32 ( 1 << XHCI_CTX_SLOT ); + control_ctx->drop = cpu_to_le32 ( 1 << endpoint->ctx ); + + /* Populate slot context */ + slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT )); + slot_ctx->info = cpu_to_le32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ), + 0, 0, 0 ) ); +} + +/** + * Deconfigure endpoint + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int xhci_deconfigure_endpoint ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + int rc; + + /* Deconfigure endpoint */ + if ( ( rc = xhci_context ( xhci, slot, endpoint, + XHCI_TRB_CONFIGURE_ENDPOINT, + xhci_deconfigure_endpoint_input ) ) != 0 ) + return rc; + + DBGC2 ( xhci, "XHCI %s slot %d ctx %d deconfigured\n", + xhci->name, slot->id, endpoint->ctx ); + return 0; +} + +/** + * Populate evaluate context input context + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @v input Input context + */ +static void xhci_evaluate_context_input ( struct xhci_device *xhci, + struct xhci_slot *slot __unused, + struct xhci_endpoint *endpoint, + void *input ) { + struct xhci_control_context *control_ctx; + struct xhci_slot_context *slot_ctx; + struct xhci_endpoint_context *ep_ctx; + + /* Populate control context */ + control_ctx = input; + control_ctx->add = cpu_to_le32 ( ( 1 << XHCI_CTX_SLOT ) | + ( 1 << endpoint->ctx ) ); + + /* Populate slot context */ + slot_ctx = ( input + xhci_input_context_offset ( xhci, XHCI_CTX_SLOT )); + slot_ctx->info = cpu_to_le32 ( XHCI_SLOT_INFO ( ( XHCI_CTX_END - 1 ), + 0, 0, 0 ) ); + + /* Populate endpoint context */ + ep_ctx = ( input + xhci_input_context_offset ( xhci, endpoint->ctx ) ); + ep_ctx->mtu = cpu_to_le16 ( endpoint->ep->mtu ); +} + +/** + * Evaluate context + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int xhci_evaluate_context ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + int rc; + + /* Configure endpoint */ + if ( ( rc = xhci_context ( xhci, slot, endpoint, + XHCI_TRB_EVALUATE_CONTEXT, + xhci_evaluate_context_input ) ) != 0 ) + return rc; + + DBGC2 ( xhci, "XHCI %s slot %d ctx %d (re-)evaluated\n", + xhci->name, slot->id, endpoint->ctx ); + return 0; +} + +/** + * Reset endpoint + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int xhci_reset_endpoint ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + union xhci_trb trb; + struct xhci_trb_reset_endpoint *reset = &trb.reset; + int rc; + + /* Construct command */ + memset ( reset, 0, sizeof ( *reset ) ); + reset->slot = slot->id; + reset->endpoint = endpoint->ctx; + reset->type = XHCI_TRB_RESET_ENDPOINT; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) { + DBGC ( xhci, "XHCI %s slot %d ctx %d could not reset endpoint " + "in state %d: %s\n", xhci->name, slot->id, endpoint->ctx, + endpoint->context->state, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Stop endpoint + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int xhci_stop_endpoint ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + union xhci_trb trb; + struct xhci_trb_stop_endpoint *stop = &trb.stop; + int rc; + + /* Construct command */ + memset ( stop, 0, sizeof ( *stop ) ); + stop->slot = slot->id; + stop->endpoint = endpoint->ctx; + stop->type = XHCI_TRB_STOP_ENDPOINT; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) { + DBGC ( xhci, "XHCI %s slot %d ctx %d could not stop endpoint " + "in state %d: %s\n", xhci->name, slot->id, endpoint->ctx, + endpoint->context->state, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Set transfer ring dequeue pointer + * + * @v xhci xHCI device + * @v slot Device slot + * @v endpoint Endpoint + * @ret rc Return status code + */ +static inline int +xhci_set_tr_dequeue_pointer ( struct xhci_device *xhci, + struct xhci_slot *slot, + struct xhci_endpoint *endpoint ) { + union xhci_trb trb; + struct xhci_trb_set_tr_dequeue_pointer *dequeue = &trb.dequeue; + struct xhci_trb_ring *ring = &endpoint->ring; + unsigned int cons; + unsigned int mask; + unsigned int index; + unsigned int dcs; + int rc; + + /* Construct command */ + memset ( dequeue, 0, sizeof ( *dequeue ) ); + cons = ring->cons; + mask = ring->mask; + dcs = ( ( ~( cons >> ring->shift ) ) & XHCI_EP_DCS ); + index = ( cons & mask ); + dequeue->dequeue = + cpu_to_le64 ( virt_to_phys ( &ring->trb[index] ) | dcs ); + dequeue->slot = slot->id; + dequeue->endpoint = endpoint->ctx; + dequeue->type = XHCI_TRB_SET_TR_DEQUEUE_POINTER; + + /* Issue command and wait for completion */ + if ( ( rc = xhci_command ( xhci, &trb ) ) != 0 ) { + DBGC ( xhci, "XHCI %s slot %d ctx %d could not set TR dequeue " + "pointer in state %d: %s\n", xhci->name, slot->id, + endpoint->ctx, endpoint->context->state, strerror ( rc)); + return rc; + } + + return 0; +} + +/****************************************************************************** + * + * Endpoint operations + * + ****************************************************************************** + */ + +/** + * Open endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int xhci_endpoint_open ( struct usb_endpoint *ep ) { + struct usb_device *usb = ep->usb; + struct xhci_slot *slot = usb_get_hostdata ( usb ); + struct xhci_device *xhci = slot->xhci; + struct xhci_endpoint *endpoint; + unsigned int ctx; + unsigned int type; + unsigned int interval; + int rc; + + /* Calculate context index */ + ctx = XHCI_CTX ( ep->address ); + assert ( slot->endpoint[ctx] == NULL ); + + /* Calculate endpoint type */ + type = XHCI_EP_TYPE ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); + if ( type == XHCI_EP_TYPE ( USB_ENDPOINT_ATTR_CONTROL ) ) + type = XHCI_EP_TYPE_CONTROL; + if ( ep->address & USB_DIR_IN ) + type |= XHCI_EP_TYPE_IN; + + /* Calculate interval */ + if ( type & XHCI_EP_TYPE_PERIODIC ) { + interval = ( fls ( ep->interval ) - 1 ); + } else { + interval = ep->interval; + } + + /* Allocate and initialise structure */ + endpoint = zalloc ( sizeof ( *endpoint ) ); + if ( ! endpoint ) { + rc = -ENOMEM; + goto err_alloc; + } + usb_endpoint_set_hostdata ( ep, endpoint ); + slot->endpoint[ctx] = endpoint; + endpoint->xhci = xhci; + endpoint->slot = slot; + endpoint->ep = ep; + endpoint->ctx = ctx; + endpoint->type = type; + endpoint->interval = interval; + endpoint->context = ( ( ( void * ) slot->context ) + + xhci_device_context_offset ( xhci, ctx ) ); + + /* Allocate transfer ring */ + if ( ( rc = xhci_ring_alloc ( xhci, &endpoint->ring, + XHCI_TRANSFER_TRBS_LOG2, + slot->id, ctx, 0 ) ) != 0 ) + goto err_ring_alloc; + + /* Configure endpoint, if applicable */ + if ( ( ctx != XHCI_CTX_EP0 ) && + ( ( rc = xhci_configure_endpoint ( xhci, slot, endpoint ) ) != 0 )) + goto err_configure_endpoint; + + DBGC2 ( xhci, "XHCI %s slot %d ctx %d ring [%08lx,%08lx)\n", + xhci->name, slot->id, ctx, virt_to_phys ( endpoint->ring.trb ), + ( virt_to_phys ( endpoint->ring.trb ) + endpoint->ring.len ) ); + return 0; + + xhci_deconfigure_endpoint ( xhci, slot, endpoint ); + err_configure_endpoint: + xhci_ring_free ( &endpoint->ring ); + err_ring_alloc: + slot->endpoint[ctx] = NULL; + free ( endpoint ); + err_alloc: + return rc; +} + +/** + * Close endpoint + * + * @v ep USB endpoint + */ +static void xhci_endpoint_close ( struct usb_endpoint *ep ) { + struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct xhci_slot *slot = endpoint->slot; + struct xhci_device *xhci = slot->xhci; + struct io_buffer *iobuf; + unsigned int ctx = endpoint->ctx; + + /* Deconfigure endpoint, if applicable */ + if ( ctx != XHCI_CTX_EP0 ) + xhci_deconfigure_endpoint ( xhci, slot, endpoint ); + + /* Cancel any incomplete transfers */ + while ( xhci_ring_fill ( &endpoint->ring ) ) { + iobuf = xhci_dequeue_multi ( &endpoint->ring ); + usb_complete_err ( ep, iobuf, -ECANCELED ); + } + + /* Free endpoint */ + xhci_ring_free ( &endpoint->ring ); + slot->endpoint[ctx] = NULL; + free ( endpoint ); +} + +/** + * Reset endpoint + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int xhci_endpoint_reset ( struct usb_endpoint *ep ) { + struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct xhci_slot *slot = endpoint->slot; + struct xhci_device *xhci = slot->xhci; + int rc; + + /* Reset endpoint context */ + if ( ( rc = xhci_reset_endpoint ( xhci, slot, endpoint ) ) != 0 ) + return rc; + + /* Set transfer ring dequeue pointer */ + if ( ( rc = xhci_set_tr_dequeue_pointer ( xhci, slot, endpoint ) ) != 0) + return rc; + + /* Ring doorbell to resume processing */ + xhci_doorbell ( &endpoint->ring ); + + DBGC ( xhci, "XHCI %s slot %d ctx %d reset\n", + xhci->name, slot->id, endpoint->ctx ); + return 0; +} + +/** + * Update MTU + * + * @v ep USB endpoint + * @ret rc Return status code + */ +static int xhci_endpoint_mtu ( struct usb_endpoint *ep ) { + struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct xhci_slot *slot = endpoint->slot; + struct xhci_device *xhci = slot->xhci; + int rc; + + /* Evalulate context */ + if ( ( rc = xhci_evaluate_context ( xhci, slot, endpoint ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Enqueue message transfer + * + * @v ep USB endpoint + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int xhci_endpoint_message ( struct usb_endpoint *ep, + struct io_buffer *iobuf ) { + struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct usb_setup_packet *packet; + unsigned int input; + size_t len; + union xhci_trb trbs[ 1 /* setup */ + 1 /* possible data */ + + 1 /* status */ ]; + union xhci_trb *trb = trbs; + struct xhci_trb_setup *setup; + struct xhci_trb_data *data; + struct xhci_trb_status *status; + int rc; + + /* Profile message transfers */ + profile_start ( &xhci_message_profiler ); + + /* Construct setup stage TRB */ + memset ( trbs, 0, sizeof ( trbs ) ); + assert ( iob_len ( iobuf ) >= sizeof ( *packet ) ); + packet = iobuf->data; + iob_pull ( iobuf, sizeof ( *packet ) ); + setup = &(trb++)->setup; + memcpy ( &setup->packet, packet, sizeof ( setup->packet ) ); + setup->len = cpu_to_le32 ( sizeof ( *packet ) ); + setup->flags = XHCI_TRB_IDT; + setup->type = XHCI_TRB_SETUP; + len = iob_len ( iobuf ); + input = ( packet->request & cpu_to_le16 ( USB_DIR_IN ) ); + if ( len ) + setup->direction = ( input ? XHCI_SETUP_IN : XHCI_SETUP_OUT ); + + /* Construct data stage TRB, if applicable */ + if ( len ) { + data = &(trb++)->data; + data->data = cpu_to_le64 ( virt_to_phys ( iobuf->data ) ); + data->len = cpu_to_le32 ( len ); + data->type = XHCI_TRB_DATA; + data->direction = ( input ? XHCI_DATA_IN : XHCI_DATA_OUT ); + } + + /* Construct status stage TRB */ + status = &(trb++)->status; + status->flags = XHCI_TRB_IOC; + status->type = XHCI_TRB_STATUS; + status->direction = + ( ( len && input ) ? XHCI_STATUS_OUT : XHCI_STATUS_IN ); + + /* Enqueue TRBs */ + if ( ( rc = xhci_enqueue_multi ( &endpoint->ring, iobuf, trbs, + ( trb - trbs ) ) ) != 0 ) + return rc; + + /* Ring the doorbell */ + xhci_doorbell ( &endpoint->ring ); + + profile_stop ( &xhci_message_profiler ); + return 0; +} + +/** + * 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 xhci_endpoint_stream ( struct usb_endpoint *ep, + struct io_buffer *iobuf, int terminate ) { + struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + union xhci_trb trbs[ 1 /* Normal */ + 1 /* Possible zero-length */ ]; + union xhci_trb *trb = trbs; + struct xhci_trb_normal *normal; + size_t len = iob_len ( iobuf ); + int rc; + + /* Profile stream transfers */ + profile_start ( &xhci_stream_profiler ); + + /* Construct normal TRBs */ + memset ( &trbs, 0, sizeof ( trbs ) ); + normal = &(trb++)->normal; + normal->data = cpu_to_le64 ( virt_to_phys ( iobuf->data ) ); + normal->len = cpu_to_le32 ( len ); + normal->type = XHCI_TRB_NORMAL; + if ( terminate && ( ( len & ( ep->mtu - 1 ) ) == 0 ) ) { + normal->flags = XHCI_TRB_CH; + normal = &(trb++)->normal; + normal->type = XHCI_TRB_NORMAL; + } + normal->flags = XHCI_TRB_IOC; + + /* Enqueue TRBs */ + if ( ( rc = xhci_enqueue_multi ( &endpoint->ring, iobuf, trbs, + ( trb - trbs ) ) ) != 0 ) + return rc; + + /* Ring the doorbell */ + xhci_doorbell ( &endpoint->ring ); + + profile_stop ( &xhci_stream_profiler ); + return 0; +} + +/****************************************************************************** + * + * Device operations + * + ****************************************************************************** + */ + +/** + * Open device + * + * @v usb USB device + * @ret rc Return status code + */ +static int xhci_device_open ( struct usb_device *usb ) { + struct xhci_device *xhci = usb_bus_get_hostdata ( usb->port->hub->bus ); + struct usb_port *tt = usb_transaction_translator ( usb ); + struct xhci_slot *slot; + struct xhci_slot *tt_slot; + size_t len; + int type; + int id; + int rc; + + /* Determine applicable slot type */ + type = xhci_port_slot_type ( xhci, usb->port->address ); + if ( type < 0 ) { + rc = type; + DBGC ( xhci, "XHCI %s-%d has no slot type\n", + xhci->name, usb->port->address ); + goto err_type; + } + + /* Allocate a device slot number */ + id = xhci_enable_slot ( xhci, type ); + if ( id < 0 ) { + rc = id; + goto err_enable_slot; + } + assert ( ( id > 0 ) && ( ( unsigned int ) id <= xhci->slots ) ); + assert ( xhci->slot[id] == NULL ); + + /* Allocate and initialise structure */ + slot = zalloc ( sizeof ( *slot ) ); + if ( ! slot ) { + rc = -ENOMEM; + goto err_alloc; + } + usb_set_hostdata ( usb, slot ); + xhci->slot[id] = slot; + slot->xhci = xhci; + slot->usb = usb; + slot->id = id; + if ( tt ) { + tt_slot = usb_get_hostdata ( tt->hub->usb ); + slot->tt_id = tt_slot->id; + slot->tt_port = tt->address; + } + + /* Allocate a device context */ + len = xhci_device_context_offset ( xhci, XHCI_CTX_END ); + slot->context = malloc_dma ( len, xhci_align ( len ) ); + if ( ! slot->context ) { + rc = -ENOMEM; + goto err_alloc_context; + } + memset ( slot->context, 0, len ); + + /* Set device context base address */ + assert ( xhci->dcbaa[id] == 0 ); + xhci->dcbaa[id] = cpu_to_le64 ( virt_to_phys ( slot->context ) ); + + DBGC2 ( xhci, "XHCI %s slot %d device context [%08lx,%08lx) for %s\n", + xhci->name, slot->id, virt_to_phys ( slot->context ), + ( virt_to_phys ( slot->context ) + len ), usb->name ); + return 0; + + xhci->dcbaa[id] = 0; + free_dma ( slot->context, len ); + err_alloc_context: + xhci->slot[id] = NULL; + free ( slot ); + err_alloc: + xhci_disable_slot ( xhci, id ); + err_enable_slot: + err_type: + return rc; +} + +/** + * Close device + * + * @v usb USB device + */ +static void xhci_device_close ( struct usb_device *usb ) { + struct xhci_slot *slot = usb_get_hostdata ( usb ); + struct xhci_device *xhci = slot->xhci; + size_t len = xhci_device_context_offset ( xhci, XHCI_CTX_END ); + unsigned int id = slot->id; + int rc; + + /* Disable slot */ + if ( ( rc = xhci_disable_slot ( xhci, id ) ) != 0 ) { + /* Slot is still enabled. Leak the slot context, + * since the controller may still write to this + * memory, and leave the DCBAA entry intact. + * + * If the controller later reports that this same slot + * has been re-enabled, then some assertions will be + * triggered. + */ + DBGC ( xhci, "XHCI %s slot %d leaking context memory\n", + xhci->name, slot->id ); + slot->context = NULL; + } + + /* Free slot */ + if ( slot->context ) { + free_dma ( slot->context, len ); + xhci->dcbaa[id] = 0; + } + xhci->slot[id] = NULL; + free ( slot ); +} + +/** + * Assign device address + * + * @v usb USB device + * @ret rc Return status code + */ +static int xhci_device_address ( struct usb_device *usb ) { + struct xhci_slot *slot = usb_get_hostdata ( usb ); + struct xhci_device *xhci = slot->xhci; + struct usb_port *port = usb->port; + struct usb_port *root_port; + int psiv; + int rc; + + /* Calculate route string */ + slot->route = usb_route_string ( usb ); + + /* Calculate root hub port number */ + root_port = usb_root_hub_port ( usb ); + slot->port = root_port->address; + + /* Calculate protocol speed ID */ + psiv = xhci_port_psiv ( xhci, slot->port, port->speed ); + if ( psiv < 0 ) { + rc = psiv; + return rc; + } + slot->psiv = psiv; + + /* Address device */ + if ( ( rc = xhci_address_device ( xhci, slot ) ) != 0 ) + return rc; + + return 0; +} + +/****************************************************************************** + * + * Bus operations + * + ****************************************************************************** + */ + +/** + * Open USB bus + * + * @v bus USB bus + * @ret rc Return status code + */ +static int xhci_bus_open ( struct usb_bus *bus ) { + struct xhci_device *xhci = usb_bus_get_hostdata ( bus ); + int rc; + + /* Allocate device slot array */ + xhci->slot = zalloc ( ( xhci->slots + 1 ) * sizeof ( xhci->slot[0] ) ); + if ( ! xhci->slot ) { + rc = -ENOMEM; + goto err_slot_alloc; + } + + /* Allocate device context base address array */ + if ( ( rc = xhci_dcbaa_alloc ( xhci ) ) != 0 ) + goto err_dcbaa_alloc; + + /* Allocate scratchpad buffers */ + if ( ( rc = xhci_scratchpad_alloc ( xhci ) ) != 0 ) + goto err_scratchpad_alloc; + + /* Allocate command ring */ + if ( ( rc = xhci_command_alloc ( xhci ) ) != 0 ) + goto err_command_alloc; + + /* Allocate event ring */ + if ( ( rc = xhci_event_alloc ( xhci ) ) != 0 ) + goto err_event_alloc; + + /* Start controller */ + xhci_run ( xhci ); + + return 0; + + xhci_stop ( xhci ); + xhci_event_free ( xhci ); + err_event_alloc: + xhci_command_free ( xhci ); + err_command_alloc: + xhci_scratchpad_free ( xhci ); + err_scratchpad_alloc: + xhci_dcbaa_free ( xhci ); + err_dcbaa_alloc: + free ( xhci->slot ); + err_slot_alloc: + return rc; +} + +/** + * Close USB bus + * + * @v bus USB bus + */ +static void xhci_bus_close ( struct usb_bus *bus ) { + struct xhci_device *xhci = usb_bus_get_hostdata ( bus ); + unsigned int i; + + /* Sanity checks */ + assert ( xhci->slot != NULL ); + for ( i = 0 ; i <= xhci->slots ; i++ ) + assert ( xhci->slot[i] == NULL ); + + xhci_stop ( xhci ); + xhci_event_free ( xhci ); + xhci_command_free ( xhci ); + xhci_scratchpad_free ( xhci ); + xhci_dcbaa_free ( xhci ); + free ( xhci->slot ); +} + +/** + * Poll USB bus + * + * @v bus USB bus + */ +static void xhci_bus_poll ( struct usb_bus *bus ) { + struct xhci_device *xhci = usb_bus_get_hostdata ( bus ); + + /* Poll event ring */ + xhci_event_poll ( xhci ); +} + +/****************************************************************************** + * + * Hub operations + * + ****************************************************************************** + */ + +/** + * Open hub + * + * @v hub USB hub + * @ret rc Return status code + */ +static int xhci_hub_open ( struct usb_hub *hub ) { + struct xhci_slot *slot; + + /* Do nothing if this is the root hub */ + if ( ! hub->usb ) + return 0; + + /* Get device slot */ + slot = usb_get_hostdata ( hub->usb ); + + /* Update device slot hub parameters. We don't inform the + * hardware of this information until the hub's interrupt + * endpoint is opened, since the only mechanism for so doing + * provided by the xHCI specification is a Configure Endpoint + * command, and we can't issue that command until we have a + * non-EP0 endpoint to configure. + */ + slot->ports = hub->ports; + + return 0; +} + +/** + * Close hub + * + * @v hub USB hub + */ +static void xhci_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 xhci_root_open ( struct usb_hub *hub ) { + struct usb_bus *bus = hub->bus; + struct xhci_device *xhci = usb_bus_get_hostdata ( bus ); + struct usb_port *port; + uint32_t portsc; + unsigned int i; + + /* Enable power to all ports */ + for ( i = 1 ; i <= xhci->ports ; i++ ) { + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( i ) ); + portsc &= XHCI_PORTSC_PRESERVE; + portsc |= XHCI_PORTSC_PP; + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( i ) ); + } + + /* xHCI spec requires us to potentially wait 20ms after + * enabling power to a port. + */ + mdelay ( XHCI_PORT_POWER_DELAY_MS ); + + /* USB3 ports may power up as Disabled */ + for ( i = 1 ; i <= xhci->ports ; i++ ) { + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( i ) ); + port = usb_port ( hub, i ); + if ( ( port->protocol >= USB_PROTO_3_0 ) && + ( ( portsc & XHCI_PORTSC_PLS_MASK ) == + XHCI_PORTSC_PLS_DISABLED ) ) { + /* Force link state to RxDetect */ + portsc &= XHCI_PORTSC_PRESERVE; + portsc |= ( XHCI_PORTSC_PLS_RXDETECT | XHCI_PORTSC_LWS); + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( i ) ); + } + } + + /* Some xHCI cards seem to require an additional delay after + * setting the link state to RxDetect. + */ + mdelay ( XHCI_LINK_STATE_DELAY_MS ); + + /* Record hub driver private data */ + usb_hub_set_drvdata ( hub, xhci ); + + return 0; +} + +/** + * Close root hub + * + * @v hub USB hub + */ +static void xhci_root_close ( struct usb_hub *hub ) { + + /* Clear hub driver private data */ + usb_hub_set_drvdata ( hub, NULL ); +} + +/** + * Enable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int xhci_root_enable ( struct usb_hub *hub, struct usb_port *port ) { + struct xhci_device *xhci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + unsigned int i; + + /* Reset port */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) ); + portsc &= XHCI_PORTSC_PRESERVE; + portsc |= XHCI_PORTSC_PR; + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( port->address ) ); + + /* Wait for port to become enabled */ + for ( i = 0 ; i < XHCI_PORT_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check port status */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) ); + if ( portsc & XHCI_PORTSC_PED ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( xhci, "XHCI %s-%d timed out waiting for port to enable\n", + xhci->name, port->address ); + return -ETIMEDOUT; +} + +/** + * Disable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int xhci_root_disable ( struct usb_hub *hub, struct usb_port *port ) { + struct xhci_device *xhci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + + /* Disable port */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) ); + portsc &= XHCI_PORTSC_PRESERVE; + portsc |= XHCI_PORTSC_PED; + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( port->address ) ); + + return 0; +} + +/** + * Update root hub port speed + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int xhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) { + struct xhci_device *xhci = usb_hub_get_drvdata ( hub ); + uint32_t portsc; + unsigned int psiv; + int ccs; + int ped; + int csc; + int speed; + int rc; + + /* Read port status */ + portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) ); + DBGC2 ( xhci, "XHCI %s-%d status is %08x\n", + xhci->name, port->address, portsc ); + ccs = ( portsc & XHCI_PORTSC_CCS ); + ped = ( portsc & XHCI_PORTSC_PED ); + csc = ( portsc & XHCI_PORTSC_CSC ); + psiv = XHCI_PORTSC_PSIV ( portsc ); + + /* Record disconnections and clear changes */ + port->disconnected |= csc; + portsc &= ( XHCI_PORTSC_PRESERVE | XHCI_PORTSC_CHANGE ); + writel ( portsc, xhci->op + XHCI_OP_PORTSC ( port->address ) ); + + /* Port speed is not valid unless port is connected */ + if ( ! ccs ) { + port->speed = USB_SPEED_NONE; + return 0; + } + + /* For USB2 ports, the PSIV field is not valid until the port + * completes reset and becomes enabled. + */ + if ( ( port->protocol < USB_PROTO_3_0 ) && ! ped ) { + port->speed = USB_SPEED_FULL; + return 0; + } + + /* Get port speed and map to generic USB speed */ + speed = xhci_port_speed ( xhci, port->address, psiv ); + if ( speed < 0 ) { + rc = speed; + return rc; + } + + port->speed = speed; + 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 xhci_root_clear_tt ( struct usb_hub *hub, struct usb_port *port, + struct usb_endpoint *ep ) { + struct xhci_device *xhci = usb_hub_get_drvdata ( hub ); + + /* Should never be called; this is a root hub */ + DBGC ( xhci, "XHCI %s-%d nonsensical CLEAR_TT for %s %s\n", xhci->name, + port->address, ep->usb->name, usb_endpoint_name ( ep ) ); + + return -ENOTSUP; +} + +/****************************************************************************** + * + * PCI interface + * + ****************************************************************************** + */ + +/** USB host controller operations */ +static struct usb_host_operations xhci_operations = { + .endpoint = { + .open = xhci_endpoint_open, + .close = xhci_endpoint_close, + .reset = xhci_endpoint_reset, + .mtu = xhci_endpoint_mtu, + .message = xhci_endpoint_message, + .stream = xhci_endpoint_stream, + }, + .device = { + .open = xhci_device_open, + .close = xhci_device_close, + .address = xhci_device_address, + }, + .bus = { + .open = xhci_bus_open, + .close = xhci_bus_close, + .poll = xhci_bus_poll, + }, + .hub = { + .open = xhci_hub_open, + .close = xhci_hub_close, + }, + .root = { + .open = xhci_root_open, + .close = xhci_root_close, + .enable = xhci_root_enable, + .disable = xhci_root_disable, + .speed = xhci_root_speed, + .clear_tt = xhci_root_clear_tt, + }, +}; + +/** + * Fix Intel PCH-specific quirks + * + * @v xhci xHCI device + * @v pci PCI device + */ +static void xhci_pch_fix ( struct xhci_device *xhci, struct pci_device *pci ) { + struct xhci_pch *pch = &xhci->pch; + uint32_t xusb2pr; + uint32_t xusb2prm; + uint32_t usb3pssen; + uint32_t usb3prm; + + /* Enable SuperSpeed capability. Do this before rerouting + * USB2 ports, so that USB3 devices connect at SuperSpeed. + */ + pci_read_config_dword ( pci, XHCI_PCH_USB3PSSEN, &usb3pssen ); + pci_read_config_dword ( pci, XHCI_PCH_USB3PRM, &usb3prm ); + if ( usb3prm & ~usb3pssen ) { + DBGC ( xhci, "XHCI %s enabling SuperSpeed on ports %08x\n", + xhci->name, ( usb3prm & ~usb3pssen ) ); + } + pch->usb3pssen = usb3pssen; + usb3pssen |= usb3prm; + pci_write_config_dword ( pci, XHCI_PCH_USB3PSSEN, usb3pssen ); + + /* Route USB2 ports from EHCI to xHCI */ + pci_read_config_dword ( pci, XHCI_PCH_XUSB2PR, &xusb2pr ); + pci_read_config_dword ( pci, XHCI_PCH_XUSB2PRM, &xusb2prm ); + if ( xusb2prm & ~xusb2pr ) { + DBGC ( xhci, "XHCI %s routing ports %08x from EHCI to xHCI\n", + xhci->name, ( xusb2prm & ~xusb2pr ) ); + } + pch->xusb2pr = xusb2pr; + xusb2pr |= xusb2prm; + pci_write_config_dword ( pci, XHCI_PCH_XUSB2PR, xusb2pr ); +} + +/** + * Undo Intel PCH-specific quirk fixes + * + * @v xhci xHCI device + * @v pci PCI device + */ +static void xhci_pch_undo ( struct xhci_device *xhci, struct pci_device *pci ) { + struct xhci_pch *pch = &xhci->pch; + + /* Restore USB2 port routing to original state */ + pci_write_config_dword ( pci, XHCI_PCH_XUSB2PR, pch->xusb2pr ); + + /* Restore SuperSpeed capability to original state */ + pci_write_config_dword ( pci, XHCI_PCH_USB3PSSEN, pch->usb3pssen ); +} + +/** + * Probe PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +static int xhci_probe ( struct pci_device *pci ) { + struct xhci_device *xhci; + struct usb_port *port; + unsigned long bar_start; + size_t bar_size; + unsigned int i; + int rc; + + /* Allocate and initialise structure */ + xhci = zalloc ( sizeof ( *xhci ) ); + if ( ! xhci ) { + rc = -ENOMEM; + goto err_alloc; + } + xhci->name = pci->dev.name; + xhci->quirks = pci->id->driver_data; + + /* Fix up PCI device */ + adjust_pci_device ( pci ); + + /* Map registers */ + bar_start = pci_bar_start ( pci, XHCI_BAR ); + bar_size = pci_bar_size ( pci, XHCI_BAR ); + xhci->regs = ioremap ( bar_start, bar_size ); + if ( ! xhci->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Initialise xHCI device */ + xhci_init ( xhci, xhci->regs ); + + /* Initialise USB legacy support and claim ownership */ + xhci_legacy_init ( xhci ); + xhci_legacy_claim ( xhci ); + + /* Fix Intel PCH-specific quirks, if applicable */ + if ( xhci->quirks & XHCI_PCH ) + xhci_pch_fix ( xhci, pci ); + + /* Reset device */ + if ( ( rc = xhci_reset ( xhci ) ) != 0 ) + goto err_reset; + + /* Allocate USB bus */ + xhci->bus = alloc_usb_bus ( &pci->dev, xhci->ports, XHCI_MTU, + &xhci_operations ); + if ( ! xhci->bus ) { + rc = -ENOMEM; + goto err_alloc_bus; + } + usb_bus_set_hostdata ( xhci->bus, xhci ); + usb_hub_set_drvdata ( xhci->bus->hub, xhci ); + + /* Set port protocols */ + for ( i = 1 ; i <= xhci->ports ; i++ ) { + port = usb_port ( xhci->bus->hub, i ); + port->protocol = xhci_port_protocol ( xhci, i ); + } + + /* Register USB bus */ + if ( ( rc = register_usb_bus ( xhci->bus ) ) != 0 ) + goto err_register; + + pci_set_drvdata ( pci, xhci ); + return 0; + + unregister_usb_bus ( xhci->bus ); + err_register: + free_usb_bus ( xhci->bus ); + err_alloc_bus: + xhci_reset ( xhci ); + err_reset: + if ( xhci->quirks & XHCI_PCH ) + xhci_pch_undo ( xhci, pci ); + xhci_legacy_release ( xhci ); + iounmap ( xhci->regs ); + err_ioremap: + free ( xhci ); + err_alloc: + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void xhci_remove ( struct pci_device *pci ) { + struct xhci_device *xhci = pci_get_drvdata ( pci ); + struct usb_bus *bus = xhci->bus; + + unregister_usb_bus ( bus ); + free_usb_bus ( bus ); + xhci_reset ( xhci ); + if ( xhci->quirks & XHCI_PCH ) + xhci_pch_undo ( xhci, pci ); + xhci_legacy_release ( xhci ); + iounmap ( xhci->regs ); + free ( xhci ); +} + +/** XHCI PCI device IDs */ +static struct pci_device_id xhci_ids[] = { + PCI_ROM ( 0x8086, 0x9d2f, "xhci-skylake", "xHCI (Skylake)", ( XHCI_PCH | XHCI_BAD_PSIV ) ), + PCI_ROM ( 0x8086, 0xffff, "xhci-pch", "xHCI (Intel PCH)", XHCI_PCH ), + PCI_ROM ( 0xffff, 0xffff, "xhci", "xHCI", 0 ), +}; + +/** XHCI PCI driver */ +struct pci_driver xhci_driver __pci_driver = { + .ids = xhci_ids, + .id_count = ( sizeof ( xhci_ids ) / sizeof ( xhci_ids[0] ) ), + .class = PCI_CLASS_ID ( PCI_CLASS_SERIAL, PCI_CLASS_SERIAL_USB, + PCI_CLASS_SERIAL_USB_XHCI ), + .probe = xhci_probe, + .remove = xhci_remove, +}; + +/** + * Prepare for exit + * + * @v booting System is shutting down for OS boot + */ +static void xhci_shutdown ( int booting ) { + /* If we are shutting down to boot an OS, then prevent the + * release of ownership back to BIOS. + */ + xhci_legacy_prevent_release = booting; +} + +/** Startup/shutdown function */ +struct startup_fn xhci_startup __startup_fn ( STARTUP_LATE ) = { + .shutdown = xhci_shutdown, +}; diff --git a/qemu/roms/ipxe/src/drivers/usb/xhci.h b/qemu/roms/ipxe/src/drivers/usb/xhci.h new file mode 100644 index 000000000..83bf71e7e --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/usb/xhci.h @@ -0,0 +1,1150 @@ +#ifndef _IPXE_XHCI_H +#define _IPXE_XHCI_H + +/** @file + * + * USB eXtensible Host Controller Interface (xHCI) driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <assert.h> +#include <ipxe/pci.h> +#include <ipxe/uaccess.h> +#include <ipxe/usb.h> + +/** Minimum alignment required for data structures + * + * With the exception of the scratchpad buffer pages (which are + * page-aligned), data structures used by xHCI generally require from + * 16 to 64 byte alignment and must not cross an (xHCI) page boundary. + * We simplify this requirement by aligning each structure on its own + * size, with a minimum of a 64 byte alignment. + */ +#define XHCI_MIN_ALIGN 64 + +/** Maximum transfer size */ +#define XHCI_MTU 65536 + +/** xHCI PCI BAR */ +#define XHCI_BAR PCI_BASE_ADDRESS_0 + +/** Capability register length */ +#define XHCI_CAP_CAPLENGTH 0x00 + +/** Host controller interface version number */ +#define XHCI_CAP_HCIVERSION 0x02 + +/** Structural parameters 1 */ +#define XHCI_CAP_HCSPARAMS1 0x04 + +/** Number of device slots */ +#define XHCI_HCSPARAMS1_SLOTS(params) ( ( (params) >> 0 ) & 0xff ) + +/** Number of interrupters */ +#define XHCI_HCSPARAMS1_INTRS(params) ( ( (params) >> 8 ) & 0x3ff ) + +/** Number of ports */ +#define XHCI_HCSPARAMS1_PORTS(params) ( ( (params) >> 24 ) & 0xff ) + +/** Structural parameters 2 */ +#define XHCI_CAP_HCSPARAMS2 0x08 + +/** Number of page-sized scratchpad buffers */ +#define XHCI_HCSPARAMS2_SCRATCHPADS(params) \ + ( ( ( (params) >> 16 ) & 0x3e0 ) | ( ( (params) >> 27 ) & 0x1f ) ) + +/** Capability parameters */ +#define XHCI_CAP_HCCPARAMS1 0x10 + +/** 64-bit addressing capability */ +#define XHCI_HCCPARAMS1_ADDR64(params) ( ( (params) >> 0 ) & 0x1 ) + +/** Context size shift */ +#define XHCI_HCCPARAMS1_CSZ_SHIFT(params) ( 5 + ( ( (params) >> 2 ) & 0x1 ) ) + +/** xHCI extended capabilities pointer */ +#define XHCI_HCCPARAMS1_XECP(params) ( ( ( (params) >> 16 ) & 0xffff ) << 2 ) + +/** Doorbell offset */ +#define XHCI_CAP_DBOFF 0x14 + +/** Runtime register space offset */ +#define XHCI_CAP_RTSOFF 0x18 + +/** xHCI extended capability ID */ +#define XHCI_XECP_ID(xecp) ( ( (xecp) >> 0 ) & 0xff ) + +/** Next xHCI extended capability pointer */ +#define XHCI_XECP_NEXT(xecp) ( ( ( (xecp) >> 8 ) & 0xff ) << 2 ) + +/** USB legacy support extended capability */ +#define XHCI_XECP_ID_LEGACY 1 + +/** USB legacy support BIOS owned semaphore */ +#define XHCI_USBLEGSUP_BIOS 0x02 + +/** USB legacy support BIOS ownership flag */ +#define XHCI_USBLEGSUP_BIOS_OWNED 0x01 + +/** USB legacy support OS owned semaphore */ +#define XHCI_USBLEGSUP_OS 0x03 + +/** USB legacy support OS ownership flag */ +#define XHCI_USBLEGSUP_OS_OWNED 0x01 + +/** USB legacy support control/status */ +#define XHCI_USBLEGSUP_CTLSTS 0x04 + +/** Supported protocol extended capability */ +#define XHCI_XECP_ID_SUPPORTED 2 + +/** Supported protocol revision */ +#define XHCI_SUPPORTED_REVISION 0x00 + +/** Supported protocol minor revision */ +#define XHCI_SUPPORTED_REVISION_VER(revision) ( ( (revision) >> 16 ) & 0xffff ) + +/** Supported protocol name */ +#define XHCI_SUPPORTED_NAME 0x04 + +/** Supported protocol ports */ +#define XHCI_SUPPORTED_PORTS 0x08 + +/** Supported protocol port offset */ +#define XHCI_SUPPORTED_PORTS_OFFSET(ports) ( ( (ports) >> 0 ) & 0xff ) + +/** Supported protocol port count */ +#define XHCI_SUPPORTED_PORTS_COUNT(ports) ( ( (ports) >> 8 ) & 0xff ) + +/** Supported protocol PSI count */ +#define XHCI_SUPPORTED_PORTS_PSIC(ports) ( ( (ports) >> 28 ) & 0x0f ) + +/** Supported protocol slot */ +#define XHCI_SUPPORTED_SLOT 0x0c + +/** Supported protocol slot type */ +#define XHCI_SUPPORTED_SLOT_TYPE(slot) ( ( (slot) >> 0 ) & 0x1f ) + +/** Supported protocol PSI */ +#define XHCI_SUPPORTED_PSI(index) ( 0x10 + ( (index) * 4 ) ) + +/** Supported protocol PSI value */ +#define XHCI_SUPPORTED_PSI_VALUE(psi) ( ( (psi) >> 0 ) & 0x0f ) + +/** Supported protocol PSI mantissa */ +#define XHCI_SUPPORTED_PSI_MANTISSA(psi) ( ( (psi) >> 16 ) & 0xffff ) + +/** Supported protocol PSI exponent */ +#define XHCI_SUPPORTED_PSI_EXPONENT(psi) ( ( (psi) >> 4 ) & 0x03 ) + +/** Default PSI values */ +enum xhci_default_psi_value { + /** Full speed (12Mbps) */ + XHCI_SPEED_FULL = 1, + /** Low speed (1.5Mbps) */ + XHCI_SPEED_LOW = 2, + /** High speed (480Mbps) */ + XHCI_SPEED_HIGH = 3, + /** Super speed */ + XHCI_SPEED_SUPER = 4, +}; + +/** USB command register */ +#define XHCI_OP_USBCMD 0x00 + +/** Run/stop */ +#define XHCI_USBCMD_RUN 0x00000001UL + +/** Host controller reset */ +#define XHCI_USBCMD_HCRST 0x00000002UL + +/** USB status register */ +#define XHCI_OP_USBSTS 0x04 + +/** Host controller halted */ +#define XHCI_USBSTS_HCH 0x00000001UL + +/** Page size register */ +#define XHCI_OP_PAGESIZE 0x08 + +/** Page size */ +#define XHCI_PAGESIZE(pagesize) ( (pagesize) << 12 ) + +/** Device notifcation control register */ +#define XHCI_OP_DNCTRL 0x14 + +/** Command ring control register */ +#define XHCI_OP_CRCR 0x18 + +/** Command ring cycle state */ +#define XHCI_CRCR_RCS 0x00000001UL + +/** Command abort */ +#define XHCI_CRCR_CA 0x00000004UL + +/** Command ring running */ +#define XHCI_CRCR_CRR 0x00000008UL + +/** Device context base address array pointer */ +#define XHCI_OP_DCBAAP 0x30 + +/** Configure register */ +#define XHCI_OP_CONFIG 0x38 + +/** Maximum device slots enabled */ +#define XHCI_CONFIG_MAX_SLOTS_EN(slots) ( (slots) << 0 ) + +/** Maximum device slots enabled mask */ +#define XHCI_CONFIG_MAX_SLOTS_EN_MASK \ + XHCI_CONFIG_MAX_SLOTS_EN ( 0xff ) + +/** Port status and control register */ +#define XHCI_OP_PORTSC(port) ( 0x400 - 0x10 + ( (port) << 4 ) ) + +/** Current connect status */ +#define XHCI_PORTSC_CCS 0x00000001UL + +/** Port enabled */ +#define XHCI_PORTSC_PED 0x00000002UL + +/** Port reset */ +#define XHCI_PORTSC_PR 0x00000010UL + +/** Port link state */ +#define XHCI_PORTSC_PLS(pls) ( (pls) << 5 ) + +/** Disabled port link state */ +#define XHCI_PORTSC_PLS_DISABLED XHCI_PORTSC_PLS ( 4 ) + +/** RxDetect port link state */ +#define XHCI_PORTSC_PLS_RXDETECT XHCI_PORTSC_PLS ( 5 ) + +/** Port link state mask */ +#define XHCI_PORTSC_PLS_MASK XHCI_PORTSC_PLS ( 0xf ) + +/** Port power */ +#define XHCI_PORTSC_PP 0x00000200UL + +/** Time to delay after enabling power to a port */ +#define XHCI_PORT_POWER_DELAY_MS 20 + +/** Port speed ID value */ +#define XHCI_PORTSC_PSIV(portsc) ( ( (portsc) >> 10 ) & 0xf ) + +/** Port indicator control */ +#define XHCI_PORTSC_PIC(indicators) ( (indicators) << 14 ) + +/** Port indicator control mask */ +#define XHCI_PORTSC_PIC_MASK XHCI_PORTSC_PIC ( 3 ) + +/** Port link state write strobe */ +#define XHCI_PORTSC_LWS 0x00010000UL + +/** Time to delay after writing the port link state */ +#define XHCI_LINK_STATE_DELAY_MS 20 + +/** Connect status change */ +#define XHCI_PORTSC_CSC 0x00020000UL + +/** Port enabled/disabled change */ +#define XHCI_PORTSC_PEC 0x00040000UL + +/** Warm port reset change */ +#define XHCI_PORTSC_WRC 0x00080000UL + +/** Over-current change */ +#define XHCI_PORTSC_OCC 0x00100000UL + +/** Port reset change */ +#define XHCI_PORTSC_PRC 0x00200000UL + +/** Port link state change */ +#define XHCI_PORTSC_PLC 0x00400000UL + +/** Port config error change */ +#define XHCI_PORTSC_CEC 0x00800000UL + +/** Port status change mask */ +#define XHCI_PORTSC_CHANGE \ + ( XHCI_PORTSC_CSC | XHCI_PORTSC_PEC | XHCI_PORTSC_WRC | \ + XHCI_PORTSC_OCC | XHCI_PORTSC_PRC | XHCI_PORTSC_PLC | \ + XHCI_PORTSC_CEC ) + +/** Port status and control bits which should be preserved + * + * The port status and control register is a horrendous mix of + * differing semantics. Some bits are written to only when a separate + * write strobe bit is set. Some bits should be preserved when + * modifying other bits. Some bits will be cleared if written back as + * a one. Most excitingly, the "port enabled" bit has the semantics + * that 1=enabled, 0=disabled, yet writing a 1 will disable the port. + */ +#define XHCI_PORTSC_PRESERVE ( XHCI_PORTSC_PP | XHCI_PORTSC_PIC_MASK ) + +/** Port power management status and control register */ +#define XHCI_OP_PORTPMSC(port) ( 0x404 - 0x10 + ( (port) << 4 ) ) + +/** Port link info register */ +#define XHCI_OP_PORTLI(port) ( 0x408 - 0x10 + ( (port) << 4 ) ) + +/** Port hardware link power management control register */ +#define XHCI_OP_PORTHLPMC(port) ( 0x40c - 0x10 + ( (port) << 4 ) ) + +/** Event ring segment table size register */ +#define XHCI_RUN_ERSTSZ(intr) ( 0x28 + ( (intr) << 5 ) ) + +/** Event ring segment table base address register */ +#define XHCI_RUN_ERSTBA(intr) ( 0x30 + ( (intr) << 5 ) ) + +/** Event ring dequeue pointer register */ +#define XHCI_RUN_ERDP(intr) ( 0x38 + ( (intr) << 5 ) ) + +/** A transfer request block template */ +struct xhci_trb_template { + /** Parameter */ + uint64_t parameter; + /** Status */ + uint32_t status; + /** Control */ + uint32_t control; +}; + +/** A transfer request block */ +struct xhci_trb_common { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint16_t reserved_c; +} __attribute__ (( packed )); + +/** Transfer request block cycle bit flag */ +#define XHCI_TRB_C 0x01 + +/** Transfer request block toggle cycle bit flag */ +#define XHCI_TRB_TC 0x02 + +/** Transfer request block chain flag */ +#define XHCI_TRB_CH 0x10 + +/** Transfer request block interrupt on completion flag */ +#define XHCI_TRB_IOC 0x20 + +/** Transfer request block immediate data flag */ +#define XHCI_TRB_IDT 0x40 + +/** Transfer request block type */ +#define XHCI_TRB_TYPE(type) ( (type) << 2 ) + +/** Transfer request block type mask */ +#define XHCI_TRB_TYPE_MASK XHCI_TRB_TYPE ( 0x3f ) + +/** A normal transfer request block */ +struct xhci_trb_normal { + /** Data buffer */ + uint64_t data; + /** Length */ + uint32_t len; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint16_t reserved; +} __attribute__ (( packed )); + +/** A normal transfer request block */ +#define XHCI_TRB_NORMAL XHCI_TRB_TYPE ( 1 ) + +/** Construct TD size field */ +#define XHCI_TD_SIZE(remaining) \ + ( ( ( (remaining) <= 0xf ) ? remaining : 0xf ) << 17 ) + +/** A setup stage transfer request block */ +struct xhci_trb_setup { + /** Setup packet */ + struct usb_setup_packet packet; + /** Length */ + uint32_t len; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Transfer direction */ + uint8_t direction; + /** Reserved */ + uint8_t reserved; +} __attribute__ (( packed )); + +/** A setup stage transfer request block */ +#define XHCI_TRB_SETUP XHCI_TRB_TYPE ( 2 ) + +/** Setup stage input data direction */ +#define XHCI_SETUP_IN 3 + +/** Setup stage output data direction */ +#define XHCI_SETUP_OUT 2 + +/** A data stage transfer request block */ +struct xhci_trb_data { + /** Data buffer */ + uint64_t data; + /** Length */ + uint32_t len; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Transfer direction */ + uint8_t direction; + /** Reserved */ + uint8_t reserved; +} __attribute__ (( packed )); + +/** A data stage transfer request block */ +#define XHCI_TRB_DATA XHCI_TRB_TYPE ( 3 ) + +/** Input data direction */ +#define XHCI_DATA_IN 0x01 + +/** Output data direction */ +#define XHCI_DATA_OUT 0x00 + +/** A status stage transfer request block */ +struct xhci_trb_status { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Direction */ + uint8_t direction; + /** Reserved */ + uint8_t reserved_c; +} __attribute__ (( packed )); + +/** A status stage transfer request block */ +#define XHCI_TRB_STATUS XHCI_TRB_TYPE ( 4 ) + +/** Input status direction */ +#define XHCI_STATUS_IN 0x01 + +/** Output status direction */ +#define XHCI_STATUS_OUT 0x00 + +/** A link transfer request block */ +struct xhci_trb_link { + /** Next ring segment */ + uint64_t next; + /** Reserved */ + uint32_t reserved_a; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint16_t reserved_c; +} __attribute__ (( packed )); + +/** A link transfer request block */ +#define XHCI_TRB_LINK XHCI_TRB_TYPE ( 6 ) + +/** A no-op transfer request block */ +#define XHCI_TRB_NOP XHCI_TRB_TYPE ( 8 ) + +/** An enable slot transfer request block */ +struct xhci_trb_enable_slot { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Slot type */ + uint8_t slot; + /** Reserved */ + uint8_t reserved_c; +} __attribute__ (( packed )); + +/** An enable slot transfer request block */ +#define XHCI_TRB_ENABLE_SLOT XHCI_TRB_TYPE ( 9 ) + +/** A disable slot transfer request block */ +struct xhci_trb_disable_slot { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint8_t reserved_c; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A disable slot transfer request block */ +#define XHCI_TRB_DISABLE_SLOT XHCI_TRB_TYPE ( 10 ) + +/** A context transfer request block */ +struct xhci_trb_context { + /** Input context */ + uint64_t input; + /** Reserved */ + uint32_t reserved_a; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint8_t reserved_b; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** An address device transfer request block */ +#define XHCI_TRB_ADDRESS_DEVICE XHCI_TRB_TYPE ( 11 ) + +/** A configure endpoint transfer request block */ +#define XHCI_TRB_CONFIGURE_ENDPOINT XHCI_TRB_TYPE ( 12 ) + +/** An evaluate context transfer request block */ +#define XHCI_TRB_EVALUATE_CONTEXT XHCI_TRB_TYPE ( 13 ) + +/** A reset endpoint transfer request block */ +struct xhci_trb_reset_endpoint { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Endpoint ID */ + uint8_t endpoint; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A reset endpoint transfer request block */ +#define XHCI_TRB_RESET_ENDPOINT XHCI_TRB_TYPE ( 14 ) + +/** A stop endpoint transfer request block */ +struct xhci_trb_stop_endpoint { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint32_t reserved_b; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Endpoint ID */ + uint8_t endpoint; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A stop endpoint transfer request block */ +#define XHCI_TRB_STOP_ENDPOINT XHCI_TRB_TYPE ( 15 ) + +/** A set transfer ring dequeue pointer transfer request block */ +struct xhci_trb_set_tr_dequeue_pointer { + /** Dequeue pointer */ + uint64_t dequeue; + /** Reserved */ + uint32_t reserved; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Endpoint ID */ + uint8_t endpoint; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A set transfer ring dequeue pointer transfer request block */ +#define XHCI_TRB_SET_TR_DEQUEUE_POINTER XHCI_TRB_TYPE ( 16 ) + +/** A no-op command transfer request block */ +#define XHCI_TRB_NOP_CMD XHCI_TRB_TYPE ( 23 ) + +/** A transfer event transfer request block */ +struct xhci_trb_transfer { + /** Transfer TRB pointer */ + uint64_t transfer; + /** Residual transfer length */ + uint16_t residual; + /** Reserved */ + uint8_t reserved; + /** Completion code */ + uint8_t code; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Endpoint ID */ + uint8_t endpoint; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A transfer event transfer request block */ +#define XHCI_TRB_TRANSFER XHCI_TRB_TYPE ( 32 ) + +/** A command completion event transfer request block */ +struct xhci_trb_complete { + /** Command TRB pointer */ + uint64_t command; + /** Parameter */ + uint8_t parameter[3]; + /** Completion code */ + uint8_t code; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Virtual function ID */ + uint8_t vf; + /** Slot ID */ + uint8_t slot; +} __attribute__ (( packed )); + +/** A command completion event transfer request block */ +#define XHCI_TRB_COMPLETE XHCI_TRB_TYPE ( 33 ) + +/** xHCI completion codes */ +enum xhci_completion_code { + /** Success */ + XHCI_CMPLT_SUCCESS = 1, + /** Short packet */ + XHCI_CMPLT_SHORT = 13, + /** Command ring stopped */ + XHCI_CMPLT_CMD_STOPPED = 24, +}; + +/** A port status change transfer request block */ +struct xhci_trb_port_status { + /** Reserved */ + uint8_t reserved_a[3]; + /** Port ID */ + uint8_t port; + /** Reserved */ + uint8_t reserved_b[7]; + /** Completion code */ + uint8_t code; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint16_t reserved_c; +} __attribute__ (( packed )); + +/** A port status change transfer request block */ +#define XHCI_TRB_PORT_STATUS XHCI_TRB_TYPE ( 34 ) + +/** A port status change transfer request block */ +struct xhci_trb_host_controller { + /** Reserved */ + uint64_t reserved_a; + /** Reserved */ + uint8_t reserved_b[3]; + /** Completion code */ + uint8_t code; + /** Flags */ + uint8_t flags; + /** Type */ + uint8_t type; + /** Reserved */ + uint16_t reserved_c; +} __attribute__ (( packed )); + +/** A port status change transfer request block */ +#define XHCI_TRB_HOST_CONTROLLER XHCI_TRB_TYPE ( 37 ) + +/** A transfer request block */ +union xhci_trb { + /** Template */ + struct xhci_trb_template template; + /** Common fields */ + struct xhci_trb_common common; + /** Normal TRB */ + struct xhci_trb_normal normal; + /** Setup stage TRB */ + struct xhci_trb_setup setup; + /** Data stage TRB */ + struct xhci_trb_data data; + /** Status stage TRB */ + struct xhci_trb_status status; + /** Link TRB */ + struct xhci_trb_link link; + /** Enable slot TRB */ + struct xhci_trb_enable_slot enable; + /** Disable slot TRB */ + struct xhci_trb_disable_slot disable; + /** Input context TRB */ + struct xhci_trb_context context; + /** Reset endpoint TRB */ + struct xhci_trb_reset_endpoint reset; + /** Stop endpoint TRB */ + struct xhci_trb_stop_endpoint stop; + /** Set transfer ring dequeue pointer TRB */ + struct xhci_trb_set_tr_dequeue_pointer dequeue; + /** Transfer event */ + struct xhci_trb_transfer transfer; + /** Command completion event */ + struct xhci_trb_complete complete; + /** Port status changed event */ + struct xhci_trb_port_status port; + /** Host controller event */ + struct xhci_trb_host_controller host; +} __attribute__ (( packed )); + +/** An input control context */ +struct xhci_control_context { + /** Drop context flags */ + uint32_t drop; + /** Add context flags */ + uint32_t add; + /** Reserved */ + uint32_t reserved_a[5]; + /** Configuration value */ + uint8_t config; + /** Interface number */ + uint8_t intf; + /** Alternate setting */ + uint8_t alt; + /** Reserved */ + uint8_t reserved_b; +} __attribute__ (( packed )); + +/** A slot context */ +struct xhci_slot_context { + /** Device info */ + uint32_t info; + /** Maximum exit latency */ + uint16_t latency; + /** Root hub port number */ + uint8_t port; + /** Number of downstream ports */ + uint8_t ports; + /** TT hub slot ID */ + uint8_t tt_id; + /** TT port number */ + uint8_t tt_port; + /** Interrupter target */ + uint16_t intr; + /** USB address */ + uint8_t address; + /** Reserved */ + uint16_t reserved_a; + /** Slot state */ + uint8_t state; + /** Reserved */ + uint32_t reserved_b[4]; +} __attribute__ (( packed )); + +/** Construct slot context device info */ +#define XHCI_SLOT_INFO( entries, hub, speed, route ) \ + ( ( (entries) << 27 ) | ( (hub) << 26 ) | ( (speed) << 20 ) | (route) ) + +/** An endpoint context */ +struct xhci_endpoint_context { + /** Endpoint state */ + uint8_t state; + /** Stream configuration */ + uint8_t stream; + /** Polling interval */ + uint8_t interval; + /** Max ESIT payload high */ + uint8_t esit_high; + /** Endpoint type */ + uint8_t type; + /** Maximum burst size */ + uint8_t burst; + /** Maximum packet size */ + uint16_t mtu; + /** Transfer ring dequeue pointer */ + uint64_t dequeue; + /** Average TRB length */ + uint16_t trb_len; + /** Max ESIT payload low */ + uint16_t esit_low; + /** Reserved */ + uint32_t reserved[3]; +} __attribute__ (( packed )); + +/** Endpoint states */ +enum xhci_endpoint_state { + /** Endpoint is disabled */ + XHCI_ENDPOINT_DISABLED = 0, + /** Endpoint is running */ + XHCI_ENDPOINT_RUNNING = 1, + /** Endpoint is halted due to a USB Halt condition */ + XHCI_ENDPOINT_HALTED = 2, + /** Endpoint is stopped */ + XHCI_ENDPOINT_STOPPED = 3, + /** Endpoint is halted due to a TRB error */ + XHCI_ENDPOINT_ERROR = 4, +}; + +/** Endpoint state mask */ +#define XHCI_ENDPOINT_STATE_MASK 0x07 + +/** Endpoint type */ +#define XHCI_EP_TYPE(type) ( (type) << 3 ) + +/** Control endpoint type */ +#define XHCI_EP_TYPE_CONTROL XHCI_EP_TYPE ( 4 ) + +/** Input endpoint type */ +#define XHCI_EP_TYPE_IN XHCI_EP_TYPE ( 4 ) + +/** Periodic endpoint type */ +#define XHCI_EP_TYPE_PERIODIC XHCI_EP_TYPE ( 1 ) + +/** Endpoint dequeue cycle state */ +#define XHCI_EP_DCS 0x00000001UL + +/** Control endpoint average TRB length */ +#define XHCI_EP0_TRB_LEN 8 + +/** An event ring segment */ +struct xhci_event_ring_segment { + /** Base address */ + uint64_t base; + /** Number of TRBs */ + uint32_t count; + /** Reserved */ + uint32_t reserved; +} __attribute__ (( packed )); + +/** A transfer request block command/transfer ring */ +struct xhci_trb_ring { + /** Producer counter */ + unsigned int prod; + /** Consumer counter */ + unsigned int cons; + /** Ring size (log2) */ + unsigned int shift; + /** Ring counter mask */ + unsigned int mask; + + /** I/O buffers */ + struct io_buffer **iobuf; + + /** Transfer request blocks */ + union xhci_trb *trb; + /** Length of transfer request blocks */ + size_t len; + /** Link TRB (if applicable) */ + struct xhci_trb_link *link; + + /** Doorbell register */ + void *db; + /** Doorbell register value */ + uint32_t dbval; +}; + +/** An event ring */ +struct xhci_event_ring { + /** Consumer counter */ + unsigned int cons; + /** Event ring segment table */ + struct xhci_event_ring_segment *segment; + /** Transfer request blocks */ + union xhci_trb *trb; +}; + +/** + * Calculate doorbell register value + * + * @v target Doorbell target + * @v stream Doorbell stream ID + * @ret dbval Doorbell register value + */ +#define XHCI_DBVAL( target, stream ) ( (target) | ( (stream) << 16 ) ) + +/** + * Calculate space used in TRB ring + * + * @v ring TRB ring + * @ret fill Number of entries used + */ +static inline __attribute__ (( always_inline )) unsigned int +xhci_ring_fill ( struct xhci_trb_ring *ring ) { + + return ( ring->prod - ring->cons ); +} + +/** + * Calculate space remaining in TRB ring + * + * @v ring TRB ring + * @ret remaining Number of entries remaining + * + * xHCI does not allow us to completely fill a ring; there must be at + * least one free entry (excluding the Link TRB). + */ +static inline __attribute__ (( always_inline )) unsigned int +xhci_ring_remaining ( struct xhci_trb_ring *ring ) { + unsigned int fill = xhci_ring_fill ( ring ); + + /* We choose to utilise rings with ( 2^n + 1 ) entries, with + * the final entry being a Link TRB. The maximum fill level + * is therefore + * + * ( ( 2^n + 1 ) - 1 (Link TRB) - 1 (one slot always empty) + * == ( 2^n - 1 ) + * + * which is therefore equal to the ring mask. + */ + assert ( fill <= ring->mask ); + return ( ring->mask - fill ); +} + +/** + * Calculate physical address of most recently consumed TRB + * + * @v ring TRB ring + * @ret trb TRB physical address + */ +static inline __attribute__ (( always_inline )) physaddr_t +xhci_ring_consumed ( struct xhci_trb_ring *ring ) { + unsigned int index = ( ( ring->cons - 1 ) & ring->mask ); + + return virt_to_phys ( &ring->trb[index] ); +} + +/** Slot context index */ +#define XHCI_CTX_SLOT 0 + +/** Calculate context index from USB endpoint address */ +#define XHCI_CTX(address) \ + ( (address) ? ( ( ( (address) & 0x0f ) << 1 ) | \ + ( ( (address) & 0x80 ) >> 7 ) ) : 1 ) + +/** Endpoint zero context index */ +#define XHCI_CTX_EP0 XHCI_CTX ( 0x00 ) + +/** End of contexts */ +#define XHCI_CTX_END 32 + +/** Device context index */ +#define XHCI_DCI(ctx) ( (ctx) + 0 ) + +/** Input context index */ +#define XHCI_ICI(ctx) ( (ctx) + 1 ) + +/** Number of TRBs (excluding Link TRB) in the command ring + * + * This is a policy decision. + */ +#define XHCI_CMD_TRBS_LOG2 2 + +/** Number of TRBs in the event ring + * + * This is a policy decision. + */ +#define XHCI_EVENT_TRBS_LOG2 6 + +/** Number of TRBs in a transfer ring + * + * This is a policy decision. + */ +#define XHCI_TRANSFER_TRBS_LOG2 6 + +/** Maximum time to wait for BIOS to release ownership + * + * This is a policy decision. + */ +#define XHCI_USBLEGSUP_MAX_WAIT_MS 100 + +/** Maximum time to wait for host controller to stop + * + * This is a policy decision. + */ +#define XHCI_STOP_MAX_WAIT_MS 100 + +/** Maximum time to wait for reset to complete + * + * This is a policy decision. + */ +#define XHCI_RESET_MAX_WAIT_MS 500 + +/** Maximum time to wait for a command to complete + * + * The "address device" command involves waiting for a response to a + * USB control transaction, and so we must wait for up to the 5000ms + * that USB allows for devices to respond to control transactions. + */ +#define XHCI_COMMAND_MAX_WAIT_MS USB_CONTROL_MAX_WAIT_MS + +/** Time to delay after aborting a command + * + * This is a policy decision + */ +#define XHCI_COMMAND_ABORT_DELAY_MS 500 + +/** Maximum time to wait for a port reset to complete + * + * This is a policy decision. + */ +#define XHCI_PORT_RESET_MAX_WAIT_MS 500 + +/** Intel PCH quirk */ +struct xhci_pch { + /** USB2 port routing register original value */ + uint32_t xusb2pr; + /** USB3 port SuperSpeed enable register original value */ + uint32_t usb3pssen; +}; + +/** Intel PCH quirk flag */ +#define XHCI_PCH 0x0001 + +/** Intel PCH USB2 port routing register */ +#define XHCI_PCH_XUSB2PR 0xd0 + +/** Intel PCH USB2 port routing mask register */ +#define XHCI_PCH_XUSB2PRM 0xd4 + +/** Intel PCH SuperSpeed enable register */ +#define XHCI_PCH_USB3PSSEN 0xd8 + +/** Intel PCH USB3 port routing mask register */ +#define XHCI_PCH_USB3PRM 0xdc + +/** Invalid protocol speed ID values quirk */ +#define XHCI_BAD_PSIV 0x0002 + +/** An xHCI device */ +struct xhci_device { + /** Registers */ + void *regs; + /** Name */ + const char *name; + /** Quirks */ + unsigned int quirks; + + /** Capability registers */ + void *cap; + /** Operational registers */ + void *op; + /** Runtime registers */ + void *run; + /** Doorbell registers */ + void *db; + + /** Number of device slots */ + unsigned int slots; + /** Number of interrupters */ + unsigned int intrs; + /** Number of ports */ + unsigned int ports; + + /** Number of page-sized scratchpad buffers */ + unsigned int scratchpads; + + /** 64-bit addressing capability */ + int addr64; + /** Context size shift */ + unsigned int csz_shift; + /** xHCI extended capabilities offset */ + unsigned int xecp; + + /** Page size */ + size_t pagesize; + + /** USB legacy support capability (if present and enabled) */ + unsigned int legacy; + + /** Device context base address array */ + uint64_t *dcbaa; + + /** Scratchpad buffer area */ + userptr_t scratchpad; + /** Scratchpad buffer array */ + uint64_t *scratchpad_array; + + /** Command ring */ + struct xhci_trb_ring command; + /** Event ring */ + struct xhci_event_ring event; + /** Current command (if any) */ + union xhci_trb *pending; + + /** Device slots, indexed by slot ID */ + struct xhci_slot **slot; + + /** USB bus */ + struct usb_bus *bus; + + /** Intel PCH quirk */ + struct xhci_pch pch; +}; + +/** An xHCI device slot */ +struct xhci_slot { + /** xHCI device */ + struct xhci_device *xhci; + /** USB device */ + struct usb_device *usb; + /** Slot ID */ + unsigned int id; + /** Slot context */ + struct xhci_slot_context *context; + /** Route string */ + unsigned int route; + /** Root hub port number */ + unsigned int port; + /** Protocol speed ID */ + unsigned int psiv; + /** Number of ports (if this device is a hub) */ + unsigned int ports; + /** Transaction translator slot ID */ + unsigned int tt_id; + /** Transaction translator port */ + unsigned int tt_port; + /** Endpoints, indexed by context ID */ + struct xhci_endpoint *endpoint[XHCI_CTX_END]; +}; + +/** An xHCI endpoint */ +struct xhci_endpoint { + /** xHCI device */ + struct xhci_device *xhci; + /** xHCI slot */ + struct xhci_slot *slot; + /** USB endpoint */ + struct usb_endpoint *ep; + /** Context index */ + unsigned int ctx; + /** Endpoint type */ + unsigned int type; + /** Endpoint interval */ + unsigned int interval; + /** Endpoint context */ + struct xhci_endpoint_context *context; + /** Transfer ring */ + struct xhci_trb_ring ring; +}; + +#endif /* _IPXE_XHCI_H */ |