/*
 * 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 = 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 ) + ( zlp ? 1 : 0 ) );

	/* Enqueue transfer */
	if ( ( rc = uhci_enqueue ( ring, iobuf, count ) ) != 0 )
		return rc;

	/* Describe data packet */
	input = ( ep->address & USB_DIR_IN );
	uhci_describe ( ring, iobuf->data, len,
			( input ? USB_PID_IN : USB_PID_OUT ) );

	/* Describe zero-length packet, if applicable */
	if ( zlp )
		uhci_describe ( ring, NULL, 0, USB_PID_OUT );

	/* Sanity check */
	assert ( ring->end->prod == count );

	return 0;
}

/**
 * Check if transfer is a message transfer
 *
 * @v xfer		UHCI transfer
 * @ret is_message	Transfer is a message transfer
 */
static inline int uhci_is_message ( struct uhci_transfer *xfer ) {
	struct uhci_transfer_descriptor *desc = &xfer->desc[0];

	return ( ( desc->control & cpu_to_le32 ( UHCI_CONTROL_PID_MASK ) ) ==
		 cpu_to_le32 ( UHCI_CONTROL_PID ( USB_PID_SETUP ) ) );
}

/**
 * Poll for completions
 *
 * @v endpoint		Endpoint
 */
static void uhci_endpoint_poll ( struct uhci_endpoint *endpoint ) {
	struct uhci_ring *ring = &endpoint->ring;
	struct uhci_device *uhci = endpoint->uhci;
	struct usb_endpoint *ep = endpoint->ep;
	struct usb_device *usb = ep->usb;
	struct uhci_transfer *xfer;
	struct uhci_transfer_descriptor *desc;
	struct io_buffer *iobuf;
	unsigned int index;
	uint32_t link;
	uint32_t toggle;
	uint32_t control;
	uint16_t actual;
	size_t len;

	/* Consume all completed descriptors */
	while ( uhci_ring_fill ( ring ) ) {

		/* Stop if we reach an uncompleted descriptor */
		index = ( ring->cons % UHCI_RING_COUNT );
		xfer = ring->xfer[index];
		assert ( xfer != NULL );
		assert ( xfer->cons < xfer->prod );
		desc = &xfer->desc[xfer->cons];
		rmb();
		if ( desc->status & UHCI_STATUS_ACTIVE )
			break;
		control = le32_to_cpu ( desc->control );
		actual = le16_to_cpu ( desc->actual );

		/* Update data length, if applicable */
		if ( UHCI_DATA_PACKET ( control ) )
			xfer->len += UHCI_ACTUAL_LEN ( actual );

		/* If we have encountered an error, then deactivate
		 * the queue head (to prevent further hardware
		 * accesses to this transfer), consume the transfer,
		 * and report the error to the USB core.
		 */
		if ( desc->status & UHCI_STATUS_STALLED ) {
			DBGC ( uhci, "UHCI %s %s completion %d.%d failed "
			       "(status %02x)\n", usb->name,
			       usb_endpoint_name ( ep ), index,
			       xfer->cons, desc->status );
			link = UHCI_LINK_TERMINATE;
			ring->head->current = cpu_to_le32 ( link );
			wmb();
			iobuf = uhci_dequeue ( ring );
			usb_complete_err ( ep, iobuf, -EIO );
			break;
		}

		/* Consume this descriptor */
		xfer->cons++;

		/* Check for short packets */
		if ( UHCI_SHORT_PACKET ( control, actual ) ) {

			/* Sanity checks */
			assert ( desc->flags & UHCI_FL_SPD );
			link = virt_to_phys ( desc );
			assert ( ( le32_to_cpu ( ring->head->current ) &
				   ~( UHCI_ALIGN - 1 ) ) == link );

			/* If this is a message transfer, then restart
			 * at the status stage.
			 */
			if ( uhci_is_message ( xfer ) ) {
				xfer->cons = ( xfer->prod - 1 );
				link = virt_to_phys ( &xfer->desc[xfer->cons] );
				ring->head->current = cpu_to_le32 ( link );
				break;
			}

			/* Otherwise, this is a stream transfer.
			 * First, prevent further hardware access to
			 * this transfer.
			 */
			link = UHCI_LINK_TERMINATE;
			ring->head->current = cpu_to_le32 ( link );
			wmb();

			/* Determine expected data toggle for next descriptor */
			toggle = ( ( control ^ UHCI_CONTROL_TOGGLE ) &
				   UHCI_CONTROL_TOGGLE );

			/* Consume this transfer */
			len = xfer->len;
			iobuf = uhci_dequeue ( ring );

			/* Update packet length */
			assert ( len <= iob_len ( iobuf ) );
			iob_unput ( iobuf, ( iob_len ( iobuf ) - len ) );

			/* Restart ring */
			uhci_restart ( ring, toggle );

		} else if ( xfer->cons == xfer->prod ) {

			/* Completed a transfer: consume it */
			len = xfer->len;
			iobuf = uhci_dequeue ( ring );
			assert ( len == iob_len ( iobuf ) );

		} else {

			/* Not a short packet and not yet complete:
			 * continue processing.
			 */
			continue;
		}

		/* Report completion to USB core */
		usb_complete ( ep, iobuf );
	}
}

/******************************************************************************
 *
 * Device operations
 *
 ******************************************************************************
 */

/**
 * Open device
 *
 * @v usb		USB device
 * @ret rc		Return status code
 */
static int uhci_device_open ( struct usb_device *usb ) {
	struct uhci_device *uhci = usb_bus_get_hostdata ( usb->port->hub->bus );

	usb_set_hostdata ( usb, uhci );
	return 0;
}

/**
 * Close device
 *
 * @v usb		USB device
 */
static void uhci_device_close ( struct usb_device *usb ) {
	struct uhci_device *uhci = usb_get_hostdata ( usb );
	struct usb_bus *bus = uhci->bus;

	/* Free device address, if assigned */
	if ( usb->address )
		usb_free_address ( bus, usb->address );
}

/**
 * Assign device address
 *
 * @v usb		USB device
 * @ret rc		Return status code
 */
static int uhci_device_address ( struct usb_device *usb ) {
	struct uhci_device *uhci = usb_get_hostdata ( usb );
	struct usb_bus *bus = uhci->bus;
	struct usb_endpoint *ep0 = usb_endpoint ( usb, USB_EP0_ADDRESS );
	struct uhci_endpoint *endpoint0 = usb_endpoint_get_hostdata ( ep0 );
	int address;
	int rc;

	/* Sanity checks */
	assert ( usb->address == 0 );
	assert ( ep0 != NULL );

	/* Allocate device address */
	address = usb_alloc_address ( bus );
	if ( address < 0 ) {
		rc = address;
		DBGC ( uhci, "UHCI %s could not allocate address: %s\n",
		       usb->name, strerror ( rc ) );
		goto err_alloc_address;
	}

	/* Set address */
	if ( ( rc = usb_set_address ( usb, address ) ) != 0 )
		goto err_set_address;

	/* Update device address */
	usb->address = address;
	endpoint0->ring.control |= UHCI_CONTROL_DEVICE ( address );

	return 0;

 err_set_address:
	usb_free_address ( bus, address );
 err_alloc_address:
	return rc;
}

/******************************************************************************
 *
 * Hub operations
 *
 ******************************************************************************
 */

/**
 * Open hub
 *
 * @v hub		USB hub
 * @ret rc		Return status code
 */
static int uhci_hub_open ( struct usb_hub *hub __unused ) {

	/* Nothing to do */
	return 0;
}

/**
 * Close hub
 *
 * @v hub		USB hub
 */
static void uhci_hub_close ( struct usb_hub *hub __unused ) {

	/* Nothing to do */
}

/******************************************************************************
 *
 * Root hub operations
 *
 ******************************************************************************
 */

/**
 * Open root hub
 *
 * @v hub		USB hub
 * @ret rc		Return status code
 */
static int uhci_root_open ( struct usb_hub *hub __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,
};
