| /* | |
| * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License as | |
| * published by the Free Software Foundation; either version 2 of the | |
| * License, or (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| * 02110-1301, USA. | |
| * | |
| * You can also choose to distribute this program under the terms of | |
| * the Unmodified Binary Distribution Licence (as given in the file | |
| * COPYING.UBDL), provided that you have satisfied its requirements. | |
| */ | |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <strings.h> | |
| #include <unistd.h> | |
| #include <errno.h> | |
| #include <byteswap.h> | |
| #include <ipxe/malloc.h> | |
| #include <ipxe/pci.h> | |
| #include <ipxe/usb.h> | |
| #include "ehci.h" | |
| #include "uhci.h" | |
| /** @file | |
| * | |
| * USB Universal Host Controller Interface (UHCI) driver | |
| * | |
| */ | |
| /****************************************************************************** | |
| * | |
| * Register access | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Check that address is reachable | |
| * | |
| * @v addr Address | |
| * @v len Length | |
| * @ret rc Return status code | |
| */ | |
| static inline __attribute__ (( always_inline)) int | |
| uhci_reachable ( void *addr, size_t len ) { | |
| physaddr_t phys = virt_to_phys ( addr ); | |
| /* Always reachable in a 32-bit build */ | |
| if ( sizeof ( physaddr_t ) <= sizeof ( uint32_t ) ) | |
| return 0; | |
| /* Reachable if below 4GB */ | |
| if ( ( ( phys + len - 1 ) & ~0xffffffffULL ) == 0 ) | |
| return 0; | |
| return -ENOTSUP; | |
| } | |
| /****************************************************************************** | |
| * | |
| * Run / stop / reset | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Start UHCI device | |
| * | |
| * @v uhci UHCI device | |
| */ | |
| static void uhci_run ( struct uhci_device *uhci ) { | |
| uint16_t usbcmd; | |
| /* Set run/stop bit */ | |
| usbcmd = inw ( uhci->regs + UHCI_USBCMD ); | |
| usbcmd |= ( UHCI_USBCMD_RUN | UHCI_USBCMD_MAX64 ); | |
| outw ( usbcmd, uhci->regs + UHCI_USBCMD ); | |
| } | |
| /** | |
| * Stop UHCI device | |
| * | |
| * @v uhci UHCI device | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_stop ( struct uhci_device *uhci ) { | |
| uint16_t usbcmd; | |
| uint16_t usbsts; | |
| unsigned int i; | |
| /* Clear run/stop bit */ | |
| usbcmd = inw ( uhci->regs + UHCI_USBCMD ); | |
| usbcmd &= ~UHCI_USBCMD_RUN; | |
| outw ( usbcmd, uhci->regs + UHCI_USBCMD ); | |
| /* Wait for device to stop */ | |
| for ( i = 0 ; i < UHCI_STOP_MAX_WAIT_MS ; i++ ) { | |
| /* Check if device is stopped */ | |
| usbsts = inw ( uhci->regs + UHCI_USBSTS ); | |
| if ( usbsts & UHCI_USBSTS_HCHALTED ) | |
| return 0; | |
| /* Delay */ | |
| mdelay ( 1 ); | |
| } | |
| DBGC ( uhci, "UHCI %s timed out waiting for stop\n", uhci->name ); | |
| return -ETIMEDOUT; | |
| } | |
| /** | |
| * Reset UHCI device | |
| * | |
| * @v uhci UHCI device | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_reset ( struct uhci_device *uhci ) { | |
| uint16_t usbcmd; | |
| unsigned int i; | |
| int rc; | |
| /* The UHCI specification states that resetting a running | |
| * device may result in undefined behaviour, so try stopping | |
| * it first. | |
| */ | |
| if ( ( rc = uhci_stop ( uhci ) ) != 0 ) { | |
| /* Ignore errors and attempt to reset the device anyway */ | |
| } | |
| /* Reset device */ | |
| outw ( UHCI_USBCMD_HCRESET, uhci->regs + UHCI_USBCMD ); | |
| /* Wait for reset to complete */ | |
| for ( i = 0 ; i < UHCI_RESET_MAX_WAIT_MS ; i++ ) { | |
| /* Check if reset is complete */ | |
| usbcmd = inw ( uhci->regs + UHCI_USBCMD ); | |
| if ( ! ( usbcmd & UHCI_USBCMD_HCRESET ) ) | |
| return 0; | |
| /* Delay */ | |
| mdelay ( 1 ); | |
| } | |
| DBGC ( uhci, "UHCI %s timed out waiting for reset\n", uhci->name ); | |
| return -ETIMEDOUT; | |
| } | |
| /****************************************************************************** | |
| * | |
| * Transfer descriptor rings | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Allocate transfer ring | |
| * | |
| * @v ring Transfer ring | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_ring_alloc ( struct uhci_ring *ring ) { | |
| int rc; | |
| /* Initialise structure */ | |
| memset ( ring, 0, sizeof ( *ring ) ); | |
| /* Allocate queue head */ | |
| ring->head = malloc_phys ( sizeof ( *ring->head ), UHCI_ALIGN ); | |
| if ( ! ring->head ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| if ( ( rc = uhci_reachable ( ring->head, | |
| sizeof ( *ring->head ) ) ) != 0 ) | |
| goto err_unreachable; | |
| /* Initialise queue head */ | |
| ring->head->current = cpu_to_le32 ( UHCI_LINK_TERMINATE ); | |
| return 0; | |
| err_unreachable: | |
| free_phys ( ring->head, sizeof ( *ring->head ) ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Free transfer ring | |
| * | |
| * @v ring Transfer ring | |
| */ | |
| static void uhci_ring_free ( struct uhci_ring *ring ) { | |
| unsigned int i; | |
| /* Sanity checks */ | |
| assert ( uhci_ring_fill ( ring ) == 0 ); | |
| for ( i = 0 ; i < UHCI_RING_COUNT ; i++ ) | |
| assert ( ring->xfer[i] == NULL ); | |
| /* Free queue head */ | |
| free_phys ( ring->head, sizeof ( *ring->head ) ); | |
| } | |
| /** | |
| * Enqueue new transfer | |
| * | |
| * @v ring Transfer ring | |
| * @v iobuf I/O buffer | |
| * @v count Number of descriptors | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_enqueue ( struct uhci_ring *ring, struct io_buffer *iobuf, | |
| unsigned int count ) { | |
| struct uhci_transfer *xfer; | |
| struct uhci_transfer *end; | |
| struct uhci_transfer_descriptor *desc; | |
| unsigned int index = ( ring->prod % UHCI_RING_COUNT ); | |
| uint32_t link; | |
| size_t len; | |
| int rc; | |
| /* Sanity check */ | |
| assert ( count > 0 ); | |
| assert ( iobuf != NULL ); | |
| /* Check for space in ring */ | |
| if ( ! uhci_ring_remaining ( ring ) ) { | |
| rc = -ENOBUFS; | |
| goto err_ring_full; | |
| } | |
| /* Check for reachability of I/O buffer */ | |
| if ( ( rc = uhci_reachable ( iobuf->data, iob_len ( iobuf ) ) ) != 0 ) | |
| goto err_unreachable_iobuf; | |
| /* Allocate transfer */ | |
| xfer = malloc ( sizeof ( *xfer ) ); | |
| if ( ! xfer ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_xfer; | |
| } | |
| /* Initialise transfer */ | |
| xfer->prod = 0; | |
| xfer->cons = 0; | |
| xfer->len = 0; | |
| xfer->iobuf = iobuf; | |
| /* Allocate transfer descriptors */ | |
| len = ( count * sizeof ( xfer->desc[0] ) ); | |
| xfer->desc = malloc_phys ( len, UHCI_ALIGN ); | |
| if ( ! xfer->desc ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_desc; | |
| } | |
| if ( ( rc = uhci_reachable ( xfer->desc, len ) ) != 0 ) | |
| goto err_unreachable_desc; | |
| /* Initialise transfer descriptors */ | |
| memset ( xfer->desc, 0, len ); | |
| desc = xfer->desc; | |
| for ( ; --count ; desc++ ) { | |
| link = ( virt_to_phys ( desc + 1 ) | UHCI_LINK_DEPTH_FIRST ); | |
| desc->link = cpu_to_le32 ( link ); | |
| desc->flags = ring->flags; | |
| } | |
| desc->link = cpu_to_le32 ( UHCI_LINK_TERMINATE ); | |
| desc->flags = ( ring->flags | UHCI_FL_IOC ); | |
| /* Add to ring */ | |
| wmb(); | |
| link = virt_to_phys ( xfer->desc ); | |
| if ( uhci_ring_fill ( ring ) > 0 ) { | |
| end = ring->end; | |
| end->desc[ end->prod - 1 ].link = cpu_to_le32 ( link ); | |
| } else { | |
| ring->head->current = cpu_to_le32 ( link ); | |
| } | |
| assert ( ring->xfer[index] == NULL ); | |
| ring->xfer[index] = xfer; | |
| ring->end = xfer; | |
| ring->prod++; | |
| return 0; | |
| err_unreachable_desc: | |
| free_phys ( xfer->desc, len ); | |
| err_alloc_desc: | |
| free ( xfer ); | |
| err_alloc_xfer: | |
| err_unreachable_iobuf: | |
| err_ring_full: | |
| return rc; | |
| } | |
| /** | |
| * Describe transfer | |
| * | |
| * @v ring Transfer ring | |
| * @v data Data | |
| * @v len Length of data | |
| * @v pid Packet ID | |
| */ | |
| static void uhci_describe ( struct uhci_ring *ring, void *data, | |
| size_t len, uint8_t pid ) { | |
| struct uhci_transfer *xfer = ring->end; | |
| struct uhci_transfer_descriptor *desc; | |
| size_t frag_len; | |
| uint32_t control; | |
| do { | |
| /* Calculate fragment length */ | |
| frag_len = len; | |
| if ( frag_len > ring->mtu ) | |
| frag_len = ring->mtu; | |
| /* Populate descriptor */ | |
| desc = &xfer->desc[xfer->prod++]; | |
| if ( pid == USB_PID_IN ) | |
| desc->flags |= UHCI_FL_SPD; | |
| control = ( ring->control | UHCI_CONTROL_PID ( pid ) | | |
| UHCI_CONTROL_LEN ( frag_len ) ); | |
| desc->control = cpu_to_le32 ( control ); | |
| if ( data ) | |
| desc->data = cpu_to_le32 ( virt_to_phys ( data ) ); | |
| wmb(); | |
| desc->status = UHCI_STATUS_ACTIVE; | |
| /* Update data toggle */ | |
| ring->control ^= UHCI_CONTROL_TOGGLE; | |
| /* Move to next descriptor */ | |
| data += frag_len; | |
| len -= frag_len; | |
| } while ( len ); | |
| } | |
| /** | |
| * Dequeue transfer | |
| * | |
| * @v ring Transfer ring | |
| * @ret iobuf I/O buffer | |
| */ | |
| static struct io_buffer * uhci_dequeue ( struct uhci_ring *ring ) { | |
| unsigned int index = ( ring->cons % UHCI_RING_COUNT ); | |
| struct io_buffer *iobuf; | |
| struct uhci_transfer *xfer; | |
| size_t len; | |
| /* Sanity checks */ | |
| assert ( uhci_ring_fill ( ring ) > 0 ); | |
| /* Consume transfer */ | |
| xfer = ring->xfer[index]; | |
| assert ( xfer != NULL ); | |
| assert ( xfer->desc != NULL ); | |
| iobuf = xfer->iobuf; | |
| assert ( iobuf != NULL ); | |
| ring->xfer[index] = NULL; | |
| ring->cons++; | |
| /* Free transfer descriptors */ | |
| len = ( xfer->prod * sizeof ( xfer->desc[0] ) ); | |
| free_phys ( xfer->desc, len ); | |
| /* Free transfer */ | |
| free ( xfer ); | |
| return iobuf; | |
| } | |
| /** | |
| * Restart ring | |
| * | |
| * @v ring Transfer ring | |
| * @v toggle Expected data toggle for next descriptor | |
| */ | |
| static void uhci_restart ( struct uhci_ring *ring, uint32_t toggle ) { | |
| struct uhci_transfer *xfer; | |
| struct uhci_transfer_descriptor *desc; | |
| struct uhci_transfer_descriptor *first; | |
| uint32_t link; | |
| unsigned int i; | |
| unsigned int j; | |
| /* Sanity check */ | |
| assert ( ring->head->current == cpu_to_le32 ( UHCI_LINK_TERMINATE ) ); | |
| /* If ring is empty, then just update the data toggle for the | |
| * next descriptor. | |
| */ | |
| if ( uhci_ring_fill ( ring ) == 0 ) { | |
| ring->control &= ~UHCI_CONTROL_TOGGLE; | |
| ring->control |= toggle; | |
| return; | |
| } | |
| /* If expected toggle does not match the toggle in the first | |
| * unconsumed descriptor, then invert all toggles. | |
| */ | |
| xfer = ring->xfer[ ring->cons % UHCI_RING_COUNT ]; | |
| assert ( xfer != NULL ); | |
| assert ( xfer->cons == 0 ); | |
| first = &xfer->desc[0]; | |
| if ( ( le32_to_cpu ( first->control ) ^ toggle ) & UHCI_CONTROL_TOGGLE){ | |
| /* Invert toggle on all unconsumed transfer descriptors */ | |
| for ( i = ring->cons ; i != ring->prod ; i++ ) { | |
| xfer = ring->xfer[ i % UHCI_RING_COUNT ]; | |
| assert ( xfer != NULL ); | |
| assert ( xfer->cons == 0 ); | |
| for ( j = 0 ; j < xfer->prod ; j++ ) { | |
| desc = &xfer->desc[j]; | |
| desc->control ^= | |
| cpu_to_le32 ( UHCI_CONTROL_TOGGLE ); | |
| } | |
| } | |
| /* Invert toggle for next descriptor to be enqueued */ | |
| ring->control ^= UHCI_CONTROL_TOGGLE; | |
| } | |
| /* Restart ring at first unconsumed transfer */ | |
| link = virt_to_phys ( first ); | |
| wmb(); | |
| ring->head->current = cpu_to_le32 ( link ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Schedule management | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Get link value for a queue head | |
| * | |
| * @v queue Queue head | |
| * @ret link Link value | |
| */ | |
| static inline uint32_t uhci_link_qh ( struct uhci_queue_head *queue ) { | |
| return ( virt_to_phys ( queue ) | UHCI_LINK_TYPE_QH ); | |
| } | |
| /** | |
| * (Re)build asynchronous schedule | |
| * | |
| * @v uhci UHCI device | |
| */ | |
| static void uhci_async_schedule ( struct uhci_device *uhci ) { | |
| struct uhci_endpoint *endpoint; | |
| struct uhci_queue_head *queue; | |
| uint32_t end; | |
| uint32_t link; | |
| /* Build schedule in reverse order of execution. Provided | |
| * that we only ever add or remove single endpoints, this can | |
| * safely run concurrently with hardware execution of the | |
| * schedule. | |
| */ | |
| link = end = uhci_link_qh ( uhci->head ); | |
| list_for_each_entry_reverse ( endpoint, &uhci->async, schedule ) { | |
| queue = endpoint->ring.head; | |
| queue->link = cpu_to_le32 ( link ); | |
| wmb(); | |
| link = uhci_link_qh ( queue ); | |
| } | |
| if ( link == end ) | |
| link = UHCI_LINK_TERMINATE; | |
| uhci->head->link = cpu_to_le32 ( link ); | |
| wmb(); | |
| } | |
| /** | |
| * Add endpoint to asynchronous schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_async_add ( struct uhci_endpoint *endpoint ) { | |
| struct uhci_device *uhci = endpoint->uhci; | |
| /* Add to end of schedule */ | |
| list_add_tail ( &endpoint->schedule, &uhci->async ); | |
| /* Rebuild schedule */ | |
| uhci_async_schedule ( uhci ); | |
| } | |
| /** | |
| * Remove endpoint from asynchronous schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_async_del ( struct uhci_endpoint *endpoint ) { | |
| struct uhci_device *uhci = endpoint->uhci; | |
| /* Remove from schedule */ | |
| list_check_contains_entry ( endpoint, &uhci->async, schedule ); | |
| list_del ( &endpoint->schedule ); | |
| /* Rebuild schedule */ | |
| uhci_async_schedule ( uhci ); | |
| /* Delay for a whole USB frame (with a 100% safety margin) */ | |
| mdelay ( 2 ); | |
| } | |
| /** | |
| * (Re)build periodic schedule | |
| * | |
| * @v uhci UHCI device | |
| */ | |
| static void uhci_periodic_schedule ( struct uhci_device *uhci ) { | |
| struct uhci_endpoint *endpoint; | |
| struct uhci_queue_head *queue; | |
| uint32_t link; | |
| uint32_t end; | |
| unsigned int max_interval; | |
| unsigned int i; | |
| /* Build schedule in reverse order of execution. Provided | |
| * that we only ever add or remove single endpoints, this can | |
| * safely run concurrently with hardware execution of the | |
| * schedule. | |
| */ | |
| DBGCP ( uhci, "UHCI %s periodic schedule: ", uhci->name ); | |
| link = end = uhci_link_qh ( uhci->head ); | |
| list_for_each_entry_reverse ( endpoint, &uhci->periodic, schedule ) { | |
| queue = endpoint->ring.head; | |
| queue->link = cpu_to_le32 ( link ); | |
| wmb(); | |
| DBGCP ( uhci, "%s%d", ( ( link == end ) ? "" : "<-" ), | |
| endpoint->ep->interval ); | |
| link = uhci_link_qh ( queue ); | |
| } | |
| DBGCP ( uhci, "\n" ); | |
| /* Populate periodic frame list */ | |
| DBGCP ( uhci, "UHCI %s periodic frame list:", uhci->name ); | |
| for ( i = 0 ; i < UHCI_FRAMES ; i++ ) { | |
| /* Calculate maximum interval (in microframes) which | |
| * may appear as part of this frame list. | |
| */ | |
| if ( i == 0 ) { | |
| /* Start of list: include all endpoints */ | |
| max_interval = -1U; | |
| } else { | |
| /* Calculate highest power-of-two frame interval */ | |
| max_interval = ( 1 << ( ffs ( i ) - 1 ) ); | |
| /* Convert to microframes */ | |
| max_interval <<= 3; | |
| /* Round up to nearest 2^n-1 */ | |
| max_interval = ( ( max_interval << 1 ) - 1 ); | |
| } | |
| /* Find first endpoint in schedule satisfying this | |
| * maximum interval constraint. | |
| */ | |
| link = uhci_link_qh ( uhci->head ); | |
| list_for_each_entry ( endpoint, &uhci->periodic, schedule ) { | |
| if ( endpoint->ep->interval <= max_interval ) { | |
| queue = endpoint->ring.head; | |
| link = uhci_link_qh ( queue ); | |
| DBGCP ( uhci, " %d:%d", | |
| i, endpoint->ep->interval ); | |
| break; | |
| } | |
| } | |
| uhci->frame->link[i] = cpu_to_le32 ( link ); | |
| } | |
| wmb(); | |
| DBGCP ( uhci, "\n" ); | |
| } | |
| /** | |
| * Add endpoint to periodic schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_periodic_add ( struct uhci_endpoint *endpoint ) { | |
| struct uhci_device *uhci = endpoint->uhci; | |
| struct uhci_endpoint *before; | |
| unsigned int interval = endpoint->ep->interval; | |
| /* Find first endpoint with a smaller interval */ | |
| list_for_each_entry ( before, &uhci->periodic, schedule ) { | |
| if ( before->ep->interval < interval ) | |
| break; | |
| } | |
| list_add_tail ( &endpoint->schedule, &before->schedule ); | |
| /* Rebuild schedule */ | |
| uhci_periodic_schedule ( uhci ); | |
| } | |
| /** | |
| * Remove endpoint from periodic schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_periodic_del ( struct uhci_endpoint *endpoint ) { | |
| struct uhci_device *uhci = endpoint->uhci; | |
| /* Remove from schedule */ | |
| list_check_contains_entry ( endpoint, &uhci->periodic, schedule ); | |
| list_del ( &endpoint->schedule ); | |
| /* Rebuild schedule */ | |
| uhci_periodic_schedule ( uhci ); | |
| /* Delay for a whole USB frame (with a 100% safety margin) */ | |
| mdelay ( 2 ); | |
| } | |
| /** | |
| * Add endpoint to appropriate schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_schedule_add ( struct uhci_endpoint *endpoint ) { | |
| struct usb_endpoint *ep = endpoint->ep; | |
| unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); | |
| if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { | |
| uhci_periodic_add ( endpoint ); | |
| } else { | |
| uhci_async_add ( endpoint ); | |
| } | |
| } | |
| /** | |
| * Remove endpoint from appropriate schedule | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_schedule_del ( struct uhci_endpoint *endpoint ) { | |
| struct usb_endpoint *ep = endpoint->ep; | |
| unsigned int attr = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK ); | |
| if ( attr == USB_ENDPOINT_ATTR_INTERRUPT ) { | |
| uhci_periodic_del ( endpoint ); | |
| } else { | |
| uhci_async_del ( endpoint ); | |
| } | |
| } | |
| /****************************************************************************** | |
| * | |
| * Endpoint operations | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open endpoint | |
| * | |
| * @v ep USB endpoint | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_endpoint_open ( struct usb_endpoint *ep ) { | |
| struct usb_device *usb = ep->usb; | |
| struct uhci_device *uhci = usb_get_hostdata ( usb ); | |
| struct uhci_endpoint *endpoint; | |
| int rc; | |
| /* Allocate and initialise structure */ | |
| endpoint = zalloc ( sizeof ( *endpoint ) ); | |
| if ( ! endpoint ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| endpoint->uhci = uhci; | |
| endpoint->ep = ep; | |
| usb_endpoint_set_hostdata ( ep, endpoint ); | |
| /* Initialise descriptor ring */ | |
| if ( ( rc = uhci_ring_alloc ( &endpoint->ring ) ) != 0 ) | |
| goto err_ring_alloc; | |
| endpoint->ring.mtu = ep->mtu; | |
| endpoint->ring.flags = UHCI_FL_CERR_MAX; | |
| if ( usb->speed < USB_SPEED_FULL ) | |
| endpoint->ring.flags |= UHCI_FL_LS; | |
| endpoint->ring.control = ( UHCI_CONTROL_DEVICE ( usb->address ) | | |
| UHCI_CONTROL_ENDPOINT ( ep->address ) ); | |
| /* Add to list of endpoints */ | |
| list_add_tail ( &endpoint->list, &uhci->endpoints ); | |
| /* Add to schedule */ | |
| uhci_schedule_add ( endpoint ); | |
| return 0; | |
| uhci_ring_free ( &endpoint->ring ); | |
| err_ring_alloc: | |
| free ( endpoint ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Close endpoint | |
| * | |
| * @v ep USB endpoint | |
| */ | |
| static void uhci_endpoint_close ( struct usb_endpoint *ep ) { | |
| struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); | |
| struct io_buffer *iobuf; | |
| /* Remove from schedule */ | |
| uhci_schedule_del ( endpoint ); | |
| /* Cancel any incomplete transfers */ | |
| while ( uhci_ring_fill ( &endpoint->ring ) ) { | |
| iobuf = uhci_dequeue ( &endpoint->ring ); | |
| if ( iobuf ) | |
| usb_complete_err ( ep, iobuf, -ECANCELED ); | |
| } | |
| /* Remove from list of endpoints */ | |
| list_del ( &endpoint->list ); | |
| /* Free descriptor ring */ | |
| uhci_ring_free ( &endpoint->ring ); | |
| /* Free endpoint */ | |
| free ( endpoint ); | |
| } | |
| /** | |
| * Reset endpoint | |
| * | |
| * @v ep USB endpoint | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_endpoint_reset ( struct usb_endpoint *ep ) { | |
| struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); | |
| struct uhci_ring *ring = &endpoint->ring; | |
| /* Restart ring */ | |
| uhci_restart ( ring, 0 ); | |
| return 0; | |
| } | |
| /** | |
| * Update MTU | |
| * | |
| * @v ep USB endpoint | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_endpoint_mtu ( struct usb_endpoint *ep ) { | |
| struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); | |
| /* Update endpoint MTU */ | |
| endpoint->ring.mtu = ep->mtu; | |
| return 0; | |
| } | |
| /** | |
| * Enqueue message transfer | |
| * | |
| * @v ep USB endpoint | |
| * @v iobuf I/O buffer | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_endpoint_message ( struct usb_endpoint *ep, | |
| struct io_buffer *iobuf ) { | |
| struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); | |
| struct uhci_ring *ring = &endpoint->ring; | |
| struct usb_setup_packet *packet; | |
| unsigned int count; | |
| size_t len; | |
| int input; | |
| int rc; | |
| /* Calculate number of descriptors */ | |
| assert ( iob_len ( iobuf ) >= sizeof ( *packet ) ); | |
| len = ( iob_len ( iobuf ) - sizeof ( *packet ) ); | |
| count = ( 1 /* setup stage */ + | |
| ( ( len + ring->mtu - 1 ) / ring->mtu ) /* data stage */ + | |
| 1 /* status stage */ ); | |
| /* Enqueue transfer */ | |
| if ( ( rc = uhci_enqueue ( ring, iobuf, count ) ) != 0 ) | |
| return rc; | |
| /* Describe setup stage */ | |
| packet = iobuf->data; | |
| ring->control &= ~UHCI_CONTROL_TOGGLE; | |
| uhci_describe ( ring, packet, sizeof ( *packet ), USB_PID_SETUP ); | |
| iob_pull ( iobuf, sizeof ( *packet ) ); | |
| /* Describe data stage, if applicable */ | |
| assert ( ring->control & UHCI_CONTROL_TOGGLE ); | |
| input = ( packet->request & cpu_to_le16 ( USB_DIR_IN ) ); | |
| if ( len ) { | |
| uhci_describe ( ring, iobuf->data, len, | |
| ( input ? USB_PID_IN : USB_PID_OUT ) ); | |
| } | |
| /* Describe status stage */ | |
| ring->control |= UHCI_CONTROL_TOGGLE; | |
| uhci_describe ( ring, NULL, 0, | |
| ( ( len && input ) ? USB_PID_OUT : USB_PID_IN ) ); | |
| /* Sanity check */ | |
| assert ( ring->end->prod == count ); | |
| return 0; | |
| } | |
| /** | |
| * Enqueue stream transfer | |
| * | |
| * @v ep USB endpoint | |
| * @v iobuf I/O buffer | |
| * @v zlp Append a zero-length packet | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_endpoint_stream ( struct usb_endpoint *ep, | |
| struct io_buffer *iobuf, int zlp ) { | |
| struct uhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); | |
| struct uhci_ring *ring = &endpoint->ring; | |
| unsigned int count; | |
| size_t len; | |
| int input; | |
| int rc; | |
| /* Calculate number of descriptors */ | |
| len = iob_len ( iobuf ); | |
| count = ( ( len + ring->mtu - 1 ) / ring->mtu ); | |
| if ( zlp || ( count == 0 ) ) | |
| count++; | |
| /* Enqueue transfer */ | |
| if ( ( rc = uhci_enqueue ( ring, iobuf, count ) ) != 0 ) | |
| return rc; | |
| /* Describe data packet */ | |
| input = ( ep->address & USB_DIR_IN ); | |
| uhci_describe ( ring, iobuf->data, len, | |
| ( input ? USB_PID_IN : USB_PID_OUT ) ); | |
| /* Describe zero-length packet, if applicable */ | |
| if ( len && zlp ) | |
| uhci_describe ( ring, NULL, 0, USB_PID_OUT ); | |
| /* Sanity check */ | |
| assert ( ring->end->prod == count ); | |
| return 0; | |
| } | |
| /** | |
| * Check if transfer is a message transfer | |
| * | |
| * @v xfer UHCI transfer | |
| * @ret is_message Transfer is a message transfer | |
| */ | |
| static inline int uhci_is_message ( struct uhci_transfer *xfer ) { | |
| struct uhci_transfer_descriptor *desc = &xfer->desc[0]; | |
| return ( ( desc->control & cpu_to_le32 ( UHCI_CONTROL_PID_MASK ) ) == | |
| cpu_to_le32 ( UHCI_CONTROL_PID ( USB_PID_SETUP ) ) ); | |
| } | |
| /** | |
| * Poll for completions | |
| * | |
| * @v endpoint Endpoint | |
| */ | |
| static void uhci_endpoint_poll ( struct uhci_endpoint *endpoint ) { | |
| struct uhci_ring *ring = &endpoint->ring; | |
| struct uhci_device *uhci = endpoint->uhci; | |
| struct usb_endpoint *ep = endpoint->ep; | |
| struct usb_device *usb = ep->usb; | |
| struct uhci_transfer *xfer; | |
| struct uhci_transfer_descriptor *desc; | |
| struct io_buffer *iobuf; | |
| unsigned int index; | |
| uint32_t link; | |
| uint32_t toggle; | |
| uint32_t control; | |
| uint16_t actual; | |
| size_t len; | |
| /* Consume all completed descriptors */ | |
| while ( uhci_ring_fill ( ring ) ) { | |
| /* Stop if we reach an uncompleted descriptor */ | |
| index = ( ring->cons % UHCI_RING_COUNT ); | |
| xfer = ring->xfer[index]; | |
| assert ( xfer != NULL ); | |
| assert ( xfer->cons < xfer->prod ); | |
| desc = &xfer->desc[xfer->cons]; | |
| rmb(); | |
| if ( desc->status & UHCI_STATUS_ACTIVE ) | |
| break; | |
| control = le32_to_cpu ( desc->control ); | |
| actual = le16_to_cpu ( desc->actual ); | |
| /* Update data length, if applicable */ | |
| if ( UHCI_DATA_PACKET ( control ) ) | |
| xfer->len += UHCI_ACTUAL_LEN ( actual ); | |
| /* If we have encountered an error, then deactivate | |
| * the queue head (to prevent further hardware | |
| * accesses to this transfer), consume the transfer, | |
| * and report the error to the USB core. | |
| */ | |
| if ( desc->status & UHCI_STATUS_STALLED ) { | |
| DBGC ( uhci, "UHCI %s %s completion %d.%d failed " | |
| "(status %02x)\n", usb->name, | |
| usb_endpoint_name ( ep ), index, | |
| xfer->cons, desc->status ); | |
| link = UHCI_LINK_TERMINATE; | |
| ring->head->current = cpu_to_le32 ( link ); | |
| wmb(); | |
| iobuf = uhci_dequeue ( ring ); | |
| usb_complete_err ( ep, iobuf, -EIO ); | |
| break; | |
| } | |
| /* Consume this descriptor */ | |
| xfer->cons++; | |
| /* Check for short packets */ | |
| if ( UHCI_SHORT_PACKET ( control, actual ) ) { | |
| /* Sanity checks */ | |
| assert ( desc->flags & UHCI_FL_SPD ); | |
| link = virt_to_phys ( desc ); | |
| assert ( ( le32_to_cpu ( ring->head->current ) & | |
| ~( UHCI_ALIGN - 1 ) ) == link ); | |
| /* If this is a message transfer, then restart | |
| * at the status stage. | |
| */ | |
| if ( uhci_is_message ( xfer ) ) { | |
| xfer->cons = ( xfer->prod - 1 ); | |
| link = virt_to_phys ( &xfer->desc[xfer->cons] ); | |
| ring->head->current = cpu_to_le32 ( link ); | |
| break; | |
| } | |
| /* Otherwise, this is a stream transfer. | |
| * First, prevent further hardware access to | |
| * this transfer. | |
| */ | |
| link = UHCI_LINK_TERMINATE; | |
| ring->head->current = cpu_to_le32 ( link ); | |
| wmb(); | |
| /* Determine expected data toggle for next descriptor */ | |
| toggle = ( ( control ^ UHCI_CONTROL_TOGGLE ) & | |
| UHCI_CONTROL_TOGGLE ); | |
| /* Consume this transfer */ | |
| len = xfer->len; | |
| iobuf = uhci_dequeue ( ring ); | |
| /* Update packet length */ | |
| assert ( len <= iob_len ( iobuf ) ); | |
| iob_unput ( iobuf, ( iob_len ( iobuf ) - len ) ); | |
| /* Restart ring */ | |
| uhci_restart ( ring, toggle ); | |
| } else if ( xfer->cons == xfer->prod ) { | |
| /* Completed a transfer: consume it */ | |
| len = xfer->len; | |
| iobuf = uhci_dequeue ( ring ); | |
| assert ( len == iob_len ( iobuf ) ); | |
| } else { | |
| /* Not a short packet and not yet complete: | |
| * continue processing. | |
| */ | |
| continue; | |
| } | |
| /* Report completion to USB core */ | |
| usb_complete ( ep, iobuf ); | |
| } | |
| } | |
| /****************************************************************************** | |
| * | |
| * Device operations | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open device | |
| * | |
| * @v usb USB device | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_device_open ( struct usb_device *usb ) { | |
| struct uhci_device *uhci = usb_bus_get_hostdata ( usb->port->hub->bus ); | |
| usb_set_hostdata ( usb, uhci ); | |
| return 0; | |
| } | |
| /** | |
| * Close device | |
| * | |
| * @v usb USB device | |
| */ | |
| static void uhci_device_close ( struct usb_device *usb ) { | |
| struct uhci_device *uhci = usb_get_hostdata ( usb ); | |
| struct usb_bus *bus = uhci->bus; | |
| /* Free device address, if assigned */ | |
| if ( usb->address ) | |
| usb_free_address ( bus, usb->address ); | |
| } | |
| /** | |
| * Assign device address | |
| * | |
| * @v usb USB device | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_device_address ( struct usb_device *usb ) { | |
| struct uhci_device *uhci = usb_get_hostdata ( usb ); | |
| struct usb_bus *bus = uhci->bus; | |
| struct usb_endpoint *ep0 = usb_endpoint ( usb, USB_EP0_ADDRESS ); | |
| struct uhci_endpoint *endpoint0 = usb_endpoint_get_hostdata ( ep0 ); | |
| int address; | |
| int rc; | |
| /* Sanity checks */ | |
| assert ( usb->address == 0 ); | |
| assert ( ep0 != NULL ); | |
| /* Allocate device address */ | |
| address = usb_alloc_address ( bus ); | |
| if ( address < 0 ) { | |
| rc = address; | |
| DBGC ( uhci, "UHCI %s could not allocate address: %s\n", | |
| usb->name, strerror ( rc ) ); | |
| goto err_alloc_address; | |
| } | |
| /* Set address */ | |
| if ( ( rc = usb_set_address ( usb, address ) ) != 0 ) | |
| goto err_set_address; | |
| /* Update device address */ | |
| usb->address = address; | |
| endpoint0->ring.control |= UHCI_CONTROL_DEVICE ( address ); | |
| return 0; | |
| err_set_address: | |
| usb_free_address ( bus, address ); | |
| err_alloc_address: | |
| return rc; | |
| } | |
| /****************************************************************************** | |
| * | |
| * Hub operations | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open hub | |
| * | |
| * @v hub USB hub | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_hub_open ( struct usb_hub *hub __unused ) { | |
| /* Nothing to do */ | |
| return 0; | |
| } | |
| /** | |
| * Close hub | |
| * | |
| * @v hub USB hub | |
| */ | |
| static void uhci_hub_close ( struct usb_hub *hub __unused ) { | |
| /* Nothing to do */ | |
| } | |
| /****************************************************************************** | |
| * | |
| * Root hub operations | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open root hub | |
| * | |
| * @v hub USB hub | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_root_open ( struct usb_hub *hub __unused) { | |
| /* Nothing to do */ | |
| return 0; | |
| } | |
| /** | |
| * Close root hub | |
| * | |
| * @v hub USB hub | |
| */ | |
| static void uhci_root_close ( struct usb_hub *hub __unused ) { | |
| /* Nothing to do */ | |
| } | |
| /** | |
| * Enable port | |
| * | |
| * @v hub USB hub | |
| * @v port USB port | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_root_enable ( struct usb_hub *hub, struct usb_port *port ) { | |
| struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); | |
| uint16_t portsc; | |
| unsigned int i; | |
| /* Reset port */ | |
| portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| portsc |= UHCI_PORTSC_PR; | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| mdelay ( USB_RESET_DELAY_MS ); | |
| portsc &= ~UHCI_PORTSC_PR; | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| mdelay ( USB_RESET_RECOVER_DELAY_MS ); | |
| /* Enable port */ | |
| portsc |= UHCI_PORTSC_PED; | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| mdelay ( USB_RESET_RECOVER_DELAY_MS ); | |
| /* Wait for port to become enabled */ | |
| for ( i = 0 ; i < UHCI_PORT_ENABLE_MAX_WAIT_MS ; i++ ) { | |
| /* Check port status */ | |
| portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| if ( portsc & UHCI_PORTSC_PED ) | |
| return 0; | |
| /* Delay */ | |
| mdelay ( 1 ); | |
| } | |
| DBGC ( uhci, "UHCI %s-%d timed out waiting for port to enable " | |
| "(status %04x)\n", uhci->name, port->address, portsc ); | |
| return -ETIMEDOUT; | |
| } | |
| /** | |
| * Disable port | |
| * | |
| * @v hub USB hub | |
| * @v port USB port | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_root_disable ( struct usb_hub *hub, struct usb_port *port ) { | |
| struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); | |
| uint16_t portsc; | |
| /* Disable port */ | |
| portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| portsc &= ~UHCI_PORTSC_PED; | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| return 0; | |
| } | |
| /** | |
| * Update root hub port speed | |
| * | |
| * @v hub USB hub | |
| * @v port USB port | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) { | |
| struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); | |
| struct pci_device pci; | |
| uint16_t portsc; | |
| unsigned int speed; | |
| /* Read port status */ | |
| portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| if ( ! ( portsc & UHCI_PORTSC_CCS ) ) { | |
| /* Port not connected */ | |
| speed = USB_SPEED_NONE; | |
| } else if ( uhci->companion && | |
| ! find_usb_bus_by_location ( BUS_TYPE_PCI, | |
| uhci->companion ) ) { | |
| /* Defer connection detection until companion | |
| * controller has been enumerated. | |
| */ | |
| pci_init ( &pci, uhci->companion ); | |
| DBGC ( uhci, "UHCI %s-%d deferring for companion " PCI_FMT "\n", | |
| uhci->name, port->address, PCI_ARGS ( &pci ) ); | |
| speed = USB_SPEED_NONE; | |
| } else if ( portsc & UHCI_PORTSC_LS ) { | |
| /* Low-speed device */ | |
| speed = USB_SPEED_LOW; | |
| } else { | |
| /* Full-speed device */ | |
| speed = USB_SPEED_FULL; | |
| } | |
| port->speed = speed; | |
| /* Record disconnections and clear changes */ | |
| port->disconnected |= ( portsc & UHCI_PORTSC_CSC ); | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| return 0; | |
| } | |
| /** | |
| * Clear transaction translator buffer | |
| * | |
| * @v hub USB hub | |
| * @v port USB port | |
| * @v ep USB endpoint | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_root_clear_tt ( struct usb_hub *hub, struct usb_port *port, | |
| struct usb_endpoint *ep ) { | |
| struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); | |
| /* Should never be called; this is a root hub */ | |
| DBGC ( uhci, "UHCI %s-%d nonsensical CLEAR_TT for %s %s\n", uhci->name, | |
| port->address, ep->usb->name, usb_endpoint_name ( ep ) ); | |
| return -ENOTSUP; | |
| } | |
| /** | |
| * Poll for port status changes | |
| * | |
| * @v hub USB hub | |
| * @v port USB port | |
| */ | |
| static void uhci_root_poll ( struct usb_hub *hub, struct usb_port *port ) { | |
| struct uhci_device *uhci = usb_hub_get_drvdata ( hub ); | |
| uint16_t portsc; | |
| uint16_t change; | |
| /* Do nothing unless something has changed */ | |
| portsc = inw ( uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| change = ( portsc & UHCI_PORTSC_CHANGE ); | |
| if ( ! change ) | |
| return; | |
| /* Record disconnections and clear changes */ | |
| port->disconnected |= ( portsc & UHCI_PORTSC_CSC ); | |
| outw ( portsc, uhci->regs + UHCI_PORTSC ( port->address ) ); | |
| /* Report port status change */ | |
| usb_port_changed ( port ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * Bus operations | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** | |
| * Open USB bus | |
| * | |
| * @v bus USB bus | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_bus_open ( struct usb_bus *bus ) { | |
| struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); | |
| int rc; | |
| /* Sanity checks */ | |
| assert ( list_empty ( &uhci->async ) ); | |
| assert ( list_empty ( &uhci->periodic ) ); | |
| /* Allocate and initialise asynchronous queue head */ | |
| uhci->head = malloc_phys ( sizeof ( *uhci->head ), UHCI_ALIGN ); | |
| if ( ! uhci->head ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_head; | |
| } | |
| if ( ( rc = uhci_reachable ( uhci->head, sizeof ( *uhci->head ) ) ) !=0) | |
| goto err_unreachable_head; | |
| memset ( uhci->head, 0, sizeof ( *uhci->head ) ); | |
| uhci->head->current = cpu_to_le32 ( UHCI_LINK_TERMINATE ); | |
| uhci_async_schedule ( uhci ); | |
| /* Allocate periodic frame list */ | |
| uhci->frame = malloc_phys ( sizeof ( *uhci->frame ), | |
| sizeof ( *uhci->frame ) ); | |
| if ( ! uhci->frame ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_frame; | |
| } | |
| if ( ( rc = uhci_reachable ( uhci->frame, | |
| sizeof ( *uhci->frame ) ) ) != 0 ) | |
| goto err_unreachable_frame; | |
| uhci_periodic_schedule ( uhci ); | |
| outl ( virt_to_phys ( uhci->frame ), uhci->regs + UHCI_FLBASEADD ); | |
| /* Start controller */ | |
| uhci_run ( uhci ); | |
| return 0; | |
| uhci_stop ( uhci ); | |
| err_unreachable_frame: | |
| free_phys ( uhci->frame, sizeof ( *uhci->frame ) ); | |
| err_alloc_frame: | |
| err_unreachable_head: | |
| free_phys ( uhci->head, sizeof ( *uhci->head ) ); | |
| err_alloc_head: | |
| return rc; | |
| } | |
| /** | |
| * Close USB bus | |
| * | |
| * @v bus USB bus | |
| */ | |
| static void uhci_bus_close ( struct usb_bus *bus ) { | |
| struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); | |
| /* Sanity checks */ | |
| assert ( list_empty ( &uhci->async ) ); | |
| assert ( list_empty ( &uhci->periodic ) ); | |
| /* Stop controller */ | |
| uhci_stop ( uhci ); | |
| /* Free periodic frame list */ | |
| free_phys ( uhci->frame, sizeof ( *uhci->frame ) ); | |
| /* Free asynchronous schedule */ | |
| free_phys ( uhci->head, sizeof ( *uhci->head ) ); | |
| } | |
| /** | |
| * Poll USB bus | |
| * | |
| * @v bus USB bus | |
| */ | |
| static void uhci_bus_poll ( struct usb_bus *bus ) { | |
| struct uhci_device *uhci = usb_bus_get_hostdata ( bus ); | |
| struct usb_hub *hub = bus->hub; | |
| struct uhci_endpoint *endpoint; | |
| unsigned int i; | |
| /* UHCI defers interrupts (including short packet detection) | |
| * until the end of the frame. This can result in bulk IN | |
| * endpoints remaining halted for much of the time, waiting | |
| * for software action to reset the data toggles. We | |
| * therefore ignore USBSTS and unconditionally poll all | |
| * endpoints for completed transfer descriptors. | |
| * | |
| * As with EHCI, we trust that completion handlers are minimal | |
| * and will not do anything that could plausibly affect the | |
| * endpoint list itself. | |
| */ | |
| list_for_each_entry ( endpoint, &uhci->endpoints, list ) | |
| uhci_endpoint_poll ( endpoint ); | |
| /* UHCI provides no single bit to indicate that a port status | |
| * change has occurred. We therefore unconditionally iterate | |
| * over all ports looking for status changes. | |
| */ | |
| for ( i = 1 ; i <= UHCI_PORTS ; i++ ) | |
| uhci_root_poll ( hub, usb_port ( hub, i ) ); | |
| } | |
| /****************************************************************************** | |
| * | |
| * PCI interface | |
| * | |
| ****************************************************************************** | |
| */ | |
| /** USB host controller operations */ | |
| static struct usb_host_operations uhci_operations = { | |
| .endpoint = { | |
| .open = uhci_endpoint_open, | |
| .close = uhci_endpoint_close, | |
| .reset = uhci_endpoint_reset, | |
| .mtu = uhci_endpoint_mtu, | |
| .message = uhci_endpoint_message, | |
| .stream = uhci_endpoint_stream, | |
| }, | |
| .device = { | |
| .open = uhci_device_open, | |
| .close = uhci_device_close, | |
| .address = uhci_device_address, | |
| }, | |
| .bus = { | |
| .open = uhci_bus_open, | |
| .close = uhci_bus_close, | |
| .poll = uhci_bus_poll, | |
| }, | |
| .hub = { | |
| .open = uhci_hub_open, | |
| .close = uhci_hub_close, | |
| }, | |
| .root = { | |
| .open = uhci_root_open, | |
| .close = uhci_root_close, | |
| .enable = uhci_root_enable, | |
| .disable = uhci_root_disable, | |
| .speed = uhci_root_speed, | |
| .clear_tt = uhci_root_clear_tt, | |
| }, | |
| }; | |
| /** | |
| * Locate EHCI companion controller (when no EHCI support is present) | |
| * | |
| * @v pci PCI device | |
| * @ret busdevfn EHCI companion controller bus:dev.fn (if any) | |
| */ | |
| __weak unsigned int ehci_companion ( struct pci_device *pci __unused ) { | |
| return 0; | |
| } | |
| /** | |
| * Probe PCI device | |
| * | |
| * @v pci PCI device | |
| * @ret rc Return status code | |
| */ | |
| static int uhci_probe ( struct pci_device *pci ) { | |
| struct uhci_device *uhci; | |
| struct usb_port *port; | |
| unsigned int i; | |
| int rc; | |
| /* Allocate and initialise structure */ | |
| uhci = zalloc ( sizeof ( *uhci ) ); | |
| if ( ! uhci ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| uhci->name = pci->dev.name; | |
| INIT_LIST_HEAD ( &uhci->endpoints ); | |
| INIT_LIST_HEAD ( &uhci->async ); | |
| INIT_LIST_HEAD ( &uhci->periodic ); | |
| /* Fix up PCI device */ | |
| adjust_pci_device ( pci ); | |
| /* Identify EHCI companion controller, if any */ | |
| uhci->companion = ehci_companion ( pci ); | |
| /* Claim ownership from BIOS. (There is no release mechanism | |
| * for UHCI.) | |
| */ | |
| pci_write_config_word ( pci, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT ); | |
| /* Map registers */ | |
| uhci->regs = pci->ioaddr; | |
| if ( ! uhci->regs ) { | |
| rc = -ENODEV; | |
| goto err_ioremap; | |
| } | |
| /* Reset device */ | |
| if ( ( rc = uhci_reset ( uhci ) ) != 0 ) | |
| goto err_reset; | |
| /* Allocate USB bus */ | |
| uhci->bus = alloc_usb_bus ( &pci->dev, UHCI_PORTS, UHCI_MTU, | |
| &uhci_operations ); | |
| if ( ! uhci->bus ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_bus; | |
| } | |
| usb_bus_set_hostdata ( uhci->bus, uhci ); | |
| usb_hub_set_drvdata ( uhci->bus->hub, uhci ); | |
| /* Set port protocols */ | |
| for ( i = 1 ; i <= UHCI_PORTS ; i++ ) { | |
| port = usb_port ( uhci->bus->hub, i ); | |
| port->protocol = USB_PROTO_2_0; | |
| } | |
| /* Register USB bus */ | |
| if ( ( rc = register_usb_bus ( uhci->bus ) ) != 0 ) | |
| goto err_register; | |
| pci_set_drvdata ( pci, uhci ); | |
| return 0; | |
| unregister_usb_bus ( uhci->bus ); | |
| err_register: | |
| free_usb_bus ( uhci->bus ); | |
| err_alloc_bus: | |
| uhci_reset ( uhci ); | |
| err_reset: | |
| err_ioremap: | |
| free ( uhci ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Remove PCI device | |
| * | |
| * @v pci PCI device | |
| */ | |
| static void uhci_remove ( struct pci_device *pci ) { | |
| struct uhci_device *uhci = pci_get_drvdata ( pci ); | |
| struct usb_bus *bus = uhci->bus; | |
| unregister_usb_bus ( bus ); | |
| assert ( list_empty ( &uhci->async ) ); | |
| assert ( list_empty ( &uhci->periodic ) ); | |
| free_usb_bus ( bus ); | |
| uhci_reset ( uhci ); | |
| free ( uhci ); | |
| } | |
| /** UHCI PCI device IDs */ | |
| static struct pci_device_id uhci_ids[] = { | |
| PCI_ROM ( 0xffff, 0xffff, "uhci", "UHCI", 0 ), | |
| }; | |
| /** UHCI PCI driver */ | |
| struct pci_driver uhci_driver __pci_driver = { | |
| .ids = uhci_ids, | |
| .id_count = ( sizeof ( uhci_ids ) / sizeof ( uhci_ids[0] ) ), | |
| .class = PCI_CLASS_ID ( PCI_CLASS_SERIAL, PCI_CLASS_SERIAL_USB, | |
| PCI_CLASS_SERIAL_USB_UHCI ), | |
| .probe = uhci_probe, | |
| .remove = uhci_remove, | |
| }; |