diff --git a/src/config/config_usb.c b/src/config/config_usb.c index 0251b282..a62d695e 100644 --- a/src/config/config_usb.c +++ b/src/config/config_usb.c @@ -37,3 +37,6 @@ PROVIDE_REQUIRING_SYMBOL(); #ifdef USB_HCD_XHCI REQUIRE_OBJECT ( xhci ); #endif +#ifdef USB_HCD_EHCI +REQUIRE_OBJECT ( ehci ); +#endif diff --git a/src/config/defaults/pcbios.h b/src/config/defaults/pcbios.h index 425e4a0d..2fbb0e93 100644 --- a/src/config/defaults/pcbios.h +++ b/src/config/defaults/pcbios.h @@ -37,6 +37,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define SANBOOT_PROTO_FCP /* Fibre Channel protocol */ #define USB_HCD_XHCI /* xHCI USB host controller */ +#define USB_HCD_EHCI /* EHCI USB host controller */ #define REBOOT_CMD /* Reboot command */ #define CPUID_CMD /* x86 CPU feature detection command */ diff --git a/src/config/usb.h b/src/config/usb.h index ad8b3b72..372c0418 100644 --- a/src/config/usb.h +++ b/src/config/usb.h @@ -16,6 +16,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ //#undef USB_HCD_XHCI /* xHCI USB host controller */ +//#undef USB_HCD_EHCI /* EHCI USB host controller */ #include #include NAMED_CONFIG(usb.h) diff --git a/src/drivers/usb/ehci.c b/src/drivers/usb/ehci.c new file mode 100644 index 00000000..4436c982 --- /dev/null +++ b/src/drivers/usb/ehci.c @@ -0,0 +1,1845 @@ +/* + * Copyright (C) 2014 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 %p cap %08lx op %08lx\n", ehci, + 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 %p has %d ports\n", ehci, 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 %p %d-bit flsize %d\n", ehci, + ( 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 %p has no USB legacy support capability\n", + ehci ); + 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 %p USB legacy support already disabled\n", + ehci ); + 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 %p claimed ownership from BIOS\n", + ehci ); + pci_read_config_dword ( pci, ( legacy + + EHCI_USBLEGSUP_CTLSTS ), + &ctlsts ); + if ( ctlsts ) { + DBGC ( ehci, "EHCI %p warning: BIOS retained " + "SMIs: %08x\n", ehci, ctlsts ); + } + return; + } + + /* Delay */ + mdelay ( 1 ); + } + + /* BIOS did not release ownership. Claim it forcibly by + * disabling all SMIs. + */ + DBGC ( ehci, "EHCI %p could not claim ownership from BIOS: forcibly " + "disabling SMIs\n", ehci ); + 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 %p not releasing ownership to BIOS\n", ehci); + return; + } + + /* Release ownership */ + pci_write_config_byte ( pci, ( ehci->legacy + EHCI_USBLEGSUP_OS ), 0 ); + DBGC ( ehci, "EHCI %p released ownership to BIOS\n", ehci ); +} + +/****************************************************************************** + * + * 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 %p timed out waiting for stop\n", ehci ); + 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 %p timed out waiting for reset\n", ehci ); + 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 %p queue head unreachable\n", ehci ); + 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 %p descriptor unreachable\n", ehci ); + 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++ ) { + if ( xfer->flags & EHCI_FL_IMMEDIATE ) + continue; + 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; + + /* Copy data to immediate data buffer (if requested) */ + data = xfer->data; + len = xfer->len; + if ( xfer->flags & EHCI_FL_IMMEDIATE ) { + assert ( len <= sizeof ( desc->immediate ) ); + memcpy ( desc->immediate, data, len ); + data = desc->immediate; + } + + /* Populate buffer pointers */ + 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 %p timed out waiting for asynchronous schedule " + "to advance\n", ehci ); + 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 %p periodic schedule: ", ehci ); + 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 %p periodic frame list:", ehci ); + 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 */ + switch ( usb->port->speed ) { + case USB_SPEED_HIGH : + chr |= EHCI_CHR_EPS_HIGH; + break; + case USB_SPEED_FULL : + chr |= EHCI_CHR_EPS_FULL; + break; + default: + assert ( usb->port->speed == USB_SPEED_LOW ); + chr |= EHCI_CHR_EPS_LOW; + if ( attr == USB_ENDPOINT_ATTR_CONTROL ) + chr |= EHCI_CHR_CONTROL; + break; + } + + return chr; +} + +/** + * Determine endpoint capabilities + * + * @v ep USB endpoint + * @ret cap Endpoint capabilities + */ +static uint32_t ehci_endpoint_capabilities ( struct usb_endpoint *ep ) { + 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 ); + } + } + + 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 %p %s endpoint %d could not unschedule: " + "%s\n", ehci, usb->name, ep->address, 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 packet Setup packet + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int ehci_endpoint_message ( struct usb_endpoint *ep, + struct usb_setup_packet *packet, + struct io_buffer *iobuf ) { + struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); + struct ehci_device *ehci = endpoint->ehci; + unsigned int input = ( packet->request & cpu_to_le16 ( USB_DIR_IN ) ); + struct ehci_transfer xfers[3]; + struct ehci_transfer *xfer = xfers; + size_t len = iob_len ( iobuf ); + int rc; + + /* Construct setup stage */ + xfer->data = packet; + xfer->len = sizeof ( *packet ); + xfer->flags = ( EHCI_FL_IMMEDIATE | EHCI_FL_PID_SETUP ); + xfer++; + + /* Construct data stage, if applicable */ + 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 %p %s endpoint %d completion %d " + "failed (status %02x): %s\n", ehci, usb->name, + ep->address, 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 %p %s could not allocate address: %s\n", + ehci, 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; +} + +/****************************************************************************** + * + * Root hub operations + * + ****************************************************************************** + */ + +/** + * Open root hub + * + * @v hub USB hub + * @ret rc Return status code + */ +static int ehci_hub_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_hub_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_hub_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 %p port %d detected low-speed device: " + "disowning\n", ehci, 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 %p port %d not enabled after reset: " + "disowning\n", ehci, port->address ); + goto disown; + } + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( ehci, "EHCI %p timed out waiting for port %d to reset\n", + ehci, port->address ); + return -ETIMEDOUT; + + disown: + portsc &= ~EHCI_PORTSC_CHANGE; + portsc |= EHCI_PORTSC_OWNER; + writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); + return -ENODEV; +} + +/** + * Disable port + * + * @v hub USB hub + * @v port USB port + * @ret rc Return status code + */ +static int ehci_hub_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_hub_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 ped; + + /* Read port status */ + portsc = readl ( ehci->op + EHCI_OP_PORTSC ( port->address ) ); + DBGC2 ( ehci, "EHCI %p port %d status is %08x\n", + ehci, port->address, portsc ); + ccs = ( portsc & EHCI_PORTSC_CCS ); + ped = ( portsc & EHCI_PORTSC_PED ); + line = EHCI_PORTSC_LINE_STATUS ( portsc ); + + /* 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; +} + +/** + * Poll for port status changes + * + * @v hub USB hub + * @v port USB port + */ +static void ehci_hub_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; + + /* Acknowledge changes */ + 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 %p CTRLDSSEGMENT not supported\n", ehci ); + 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 %p frame list unreachable\n", ehci ); + 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_hub_poll ( hub, usb_port ( hub, i ) ); + } + + /* Report fatal errors */ + if ( change & EHCI_USBSTS_SYSERR ) + DBGC ( ehci, "EHCI %p host system error\n", ehci ); +} + +/****************************************************************************** + * + * 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, + .enable = ehci_hub_enable, + .disable = ehci_hub_disable, + .speed = ehci_hub_speed, + }, +}; + +/** + * 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; + } + 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 ( 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/src/drivers/usb/ehci.h b/src/drivers/usb/ehci.h new file mode 100644 index 00000000..e9437d40 --- /dev/null +++ b/src/drivers/usb/ehci.h @@ -0,0 +1,516 @@ +#ifndef _IPXE_EHCI_H +#define _IPXE_EHCI_H + +/** @file + * + * USB Enhanced Host Controller Interface (EHCI) driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** 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]; + + /** Immediate data buffer + * + * This is not part of the hardware data structure. Transfer + * descriptors must be aligned to a 32-byte boundary. Create + * an array of descriptors therefore requires 12 bytes of + * padding at the end of each descriptor. + * + * We can use this padding as an immediate data buffer (for + * setup packets). This avoids the need for separate + * allocations. As a bonus, there is no need to check this + * buffer for reachability, since it is contained within a + * transfer descriptor which must already be reachable. + */ + uint8_t immediate[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 ) + +/** 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 ) ) + +/** 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 + +/** 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; +}; + +/** Copy data to immediate data buffer */ +#define EHCI_FL_IMMEDIATE 0x0100 + +/** Set initial data toggle */ +#define EHCI_FL_TOGGLE 0x8000 + +/** An EHCI device */ +struct ehci_device { + /** Registers */ + void *regs; + + /** 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; +}; + +#endif /* _IPXE_EHCI_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index b096861e..f0e5871b 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -79,6 +79,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_usb ( ERRFILE_DRIVER | 0x00070000 ) #define ERRFILE_usbhub ( ERRFILE_DRIVER | 0x00080000 ) #define ERRFILE_xhci ( ERRFILE_DRIVER | 0x00090000 ) +#define ERRFILE_ehci ( ERRFILE_DRIVER | 0x000a0000 ) #define ERRFILE_nvs ( ERRFILE_DRIVER | 0x00100000 ) #define ERRFILE_spi ( ERRFILE_DRIVER | 0x00110000 ) diff --git a/src/include/ipxe/usb.h b/src/include/ipxe/usb.h index 70038368..e961f748 100644 --- a/src/include/ipxe/usb.h +++ b/src/include/ipxe/usb.h @@ -1178,6 +1178,9 @@ extern unsigned int usb_route_string ( struct usb_device *usb ); extern unsigned int usb_depth ( struct usb_device *usb ); extern struct usb_port * usb_root_hub_port ( struct usb_device *usb ); +/** Minimum reset time */ +#define USB_RESET_DELAY_MS 50 + /** Maximum time to wait for a control transaction to complete * * This is a policy decision.