/* * 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 %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; } /****************************************************************************** * * Diagnostics * ****************************************************************************** */ /** * Dump host controller registers * * @v ehci EHCI device */ static __unused void ehci_dump ( struct ehci_device *ehci ) { uint8_t caplength; uint16_t hciversion; uint32_t hcsparams; uint32_t hccparams; uint32_t usbcmd; uint32_t usbsts; uint32_t usbintr; uint32_t frindex; uint32_t ctrldssegment; uint32_t periodiclistbase; uint32_t asynclistaddr; uint32_t configflag; /* Do nothing unless debugging is enabled */ if ( ! DBG_LOG ) return; /* Dump capability registers */ caplength = readb ( ehci->cap + EHCI_CAP_CAPLENGTH ); hciversion = readw ( ehci->cap + EHCI_CAP_HCIVERSION ); hcsparams = readl ( ehci->cap + EHCI_CAP_HCSPARAMS ); hccparams = readl ( ehci->cap + EHCI_CAP_HCCPARAMS ); DBGC ( ehci, "EHCI %s caplen %02x hciversion %04x hcsparams %08x " "hccparams %08x\n", ehci->name, caplength, hciversion, hcsparams, hccparams ); /* Dump operational registers */ usbcmd = readl ( ehci->op + EHCI_OP_USBCMD ); usbsts = readl ( ehci->op + EHCI_OP_USBSTS ); usbintr = readl ( ehci->op + EHCI_OP_USBINTR ); frindex = readl ( ehci->op + EHCI_OP_FRINDEX ); ctrldssegment = readl ( ehci->op + EHCI_OP_CTRLDSSEGMENT ); periodiclistbase = readl ( ehci->op + EHCI_OP_PERIODICLISTBASE ); asynclistaddr = readl ( ehci->op + EHCI_OP_ASYNCLISTADDR ); configflag = readl ( ehci->op + EHCI_OP_CONFIGFLAG ); DBGC ( ehci, "EHCI %s usbcmd %08x usbsts %08x usbint %08x frindx " "%08x\n", ehci->name, usbcmd, usbsts, usbintr, frindex ); DBGC ( ehci, "EHCI %s ctrlds %08x period %08x asyncl %08x cfgflg " "%08x\n", ehci->name, ctrldssegment, periodiclistbase, asynclistaddr, configflag ); } /****************************************************************************** * * 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; /* Dump original SMI usage */ pci_read_config_dword ( pci, ( legacy + EHCI_USBLEGSUP_CTLSTS ), &ctlsts ); if ( ctlsts ) { DBGC ( ehci, "EHCI %s BIOS using SMIs: %08x\n", ehci->name, ctlsts ); } /* 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 ) { unsigned int legacy = ehci->legacy; uint32_t ctlsts; /* Do nothing unless legacy support capability is present */ if ( ! 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, ( legacy + EHCI_USBLEGSUP_OS ), 0 ); DBGC ( ehci, "EHCI %s released ownership to BIOS\n", ehci->name ); /* Dump restored SMI usage */ pci_read_config_dword ( pci, ( legacy + EHCI_USBLEGSUP_CTLSTS ), &ctlsts ); DBGC ( ehci, "EHCI %s BIOS reclaimed SMIs: %08x\n", ehci->name, ctlsts ); } /****************************************************************************** * * 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++ ) { if ( ! xfer[i].len ) 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 | 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->speed == USB_SPEED_HIGH ) { chr |= EHCI_CHR_EPS_HIGH; } else { if ( usb->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; } /** * Calculate number of transfer descriptors * * @v len Length of data * @v zlp Append a zero-length packet * @ret count Number of transfer descriptors */ static unsigned int ehci_endpoint_count ( size_t len, int zlp ) { unsigned int count; /* Split into 16kB transfers. A single transfer can handle up * to 20kB if it happens to be page-aligned, or up to 16kB * with arbitrary alignment. We simplify the code by assuming * that we can fit only 16kB into each transfer. */ count = ( ( len + EHCI_MTU - 1 ) / EHCI_MTU ); /* Append a zero-length transfer if applicable */ if ( zlp || ( count == 0 ) ) count++; return count; } /** * Enqueue stream transfer * * @v ep USB endpoint * @v iobuf I/O buffer * @v zlp Append a zero-length packet * @ret rc Return status code */ static int ehci_endpoint_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf, int zlp ) { struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); struct ehci_device *ehci = endpoint->ehci; void *data = iobuf->data; size_t len = iob_len ( iobuf ); unsigned int count = ehci_endpoint_count ( len, zlp ); unsigned int input = ( ep->address & USB_DIR_IN ); unsigned int flags = ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ); struct ehci_transfer xfers[count]; struct ehci_transfer *xfer = xfers; size_t xfer_len; unsigned int i; int rc; /* Create transfers */ for ( i = 0 ; i < count ; i++ ) { /* Calculate transfer length */ xfer_len = EHCI_MTU; if ( xfer_len > len ) xfer_len = len; /* Create transfer */ xfer->data = data; xfer->len = xfer_len; xfer->flags = flags; /* Move to next transfer */ data += xfer_len; len -= xfer_len; xfer++; } xfer[-1].flags |= EHCI_FL_IOC; /* Enqueue transfer */ if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers, count ) ) != 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, };