summaryrefslogblamecommitdiffstats
path: root/src/drivers/usb/uhci.h
blob: 475268b0845a77ac0b16bdc2d72cbf9dc51f50da (plain) (tree)



























































































































































































































































































































































                                                                           
#ifndef _IPXE_UHCI_H
#define _IPXE_UHCI_H

/** @file
 *
 * USB Universal Host Controller Interface (UHCI) driver
 *
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <assert.h>
#include <ipxe/pci.h>
#include <ipxe/usb.h>

/** Minimum alignment required for data structures
 *
 * With the exception of the frame list (which is page-aligned), data
 * structures used by UHCI generally require 16-byte alignment.
 */
#define UHCI_ALIGN 16

/** Number of ports */
#define UHCI_PORTS 2

/** Maximum transfer size */
#define UHCI_MTU 1280

/** I/O BAR size */
#define UHCI_BAR_SIZE 0x14

/** USB command register */
#define UHCI_USBCMD 0x00

/** Max packet is 64 bytes */
#define UHCI_USBCMD_MAX64 0x0080

/** Host controller reset */
#define UHCI_USBCMD_HCRESET 0x0002

/** Run/stop */
#define UHCI_USBCMD_RUN 0x0001

/** USB status register */
#define UHCI_USBSTS 0x02

/** Host controller halted */
#define UHCI_USBSTS_HCHALTED 0x0020

/** USB interrupt */
#define UHCI_USBSTS_USBINT 0x0001

/** Frame list base address register */
#define UHCI_FLBASEADD 0x08

/** Port status and control register */
#define UHCI_PORTSC(port) ( 0x0e + ( (port) << 1 ) )

/** Port reset */
#define UHCI_PORTSC_PR 0x0200

/** Low-speed device attached */
#define UHCI_PORTSC_LS 0x0100

/** Port enabled/disabled change */
#define UHCI_PORTSC_PEC 0x0008

/** Port enabled */
#define UHCI_PORTSC_PED 0x0004

/** Connect status change */
#define UHCI_PORTSC_CSC 0x0002

/** Current connect status */
#define UHCI_PORTSC_CCS 0x0001

/** Port status change mask */
#define UHCI_PORTSC_CHANGE ( UHCI_PORTSC_CSC | UHCI_PORTSC_PEC )

/** Depth-first processing */
#define UHCI_LINK_DEPTH_FIRST 0x00000004UL

/** Queue head type */
#define UHCI_LINK_TYPE_QH 0x00000002UL

/** List terminator */
#define UHCI_LINK_TERMINATE 0x00000001UL

/** Number of frames in frame list */
#define UHCI_FRAMES 1024

/** A frame list */
struct uhci_frame_list {
	/** Link pointer */
	uint32_t link[UHCI_FRAMES];
} __attribute__ (( packed ));

/** A transfer descriptor */
struct uhci_transfer_descriptor {
	/** Link pointer */
	uint32_t link;
	/** Actual length */
	uint16_t actual;
	/** Status */
	uint8_t status;
	/** Flags */
	uint8_t flags;
	/** Control */
	uint32_t control;
	/** Buffer pointer */
	uint32_t data;
} __attribute__ (( packed ));

/** Length mask */
#define UHCI_LEN_MASK 0x7ff

/** Actual length */
#define UHCI_ACTUAL_LEN( actual ) ( ( (actual) + 1 ) & UHCI_LEN_MASK )

/** Active */
#define UHCI_STATUS_ACTIVE 0x80

/** Stalled */
#define UHCI_STATUS_STALLED 0x40

/** Data buffer error */
#define UHCI_STATUS_BUFFER 0x20

/** Babble detected */
#define UHCI_STATUS_BABBLE 0x10

/** NAK received */
#define UHCI_STATUS_NAK 0x08

/** CRC/timeout error */
#define UHCI_STATUS_CRC_TIMEOUT 0x04

/** Bitstuff error */
#define UHCI_STATUS_BITSTUFF 0x02

/** Short packet detect */
#define UHCI_FL_SPD 0x20

/** Error counter */
#define UHCI_FL_CERR( count ) ( (count) << 3 )

/** Error counter maximum value */
#define UHCI_FL_CERR_MAX UHCI_FL_CERR ( 3 )

/** Low speed device */
#define UHCI_FL_LS 0x04

/** Interrupt on completion */
#define UHCI_FL_IOC 0x01

/** Packet ID */
#define UHCI_CONTROL_PID( pid ) ( (pid) << 0 )

/** Packet ID mask */
#define UHCI_CONTROL_PID_MASK UHCI_CONTROL_PID ( 0xff )

/** Device address */
#define UHCI_CONTROL_DEVICE( address ) ( (address) << 8 )

/** Endpoint address */
#define UHCI_CONTROL_ENDPOINT( address ) ( (address) << 15 )

/** Data toggle */
#define UHCI_CONTROL_TOGGLE ( 1 << 19 )

/** Data length */
#define UHCI_CONTROL_LEN( len ) ( ( ( (len) - 1 ) & UHCI_LEN_MASK ) << 21 )

/** Check for data packet
 *
 * This check is based on the fact that only USB_PID_SETUP has bit 2
 * set.
 */
#define UHCI_DATA_PACKET( control ) ( ! ( control & 0x04 ) )

/** Check for short packet */
#define UHCI_SHORT_PACKET( control, actual ) \
	( ( ( (control) >> 21 ) ^ (actual) ) & UHCI_LEN_MASK )

/** USB legacy support register (in PCI configuration space) */
#define UHCI_USBLEGSUP 0xc0

/** USB legacy support default value */
#define UHCI_USBLEGSUP_DEFAULT 0x2000

/** A queue head */
struct uhci_queue_head {
	/** Horizontal link pointer */
	uint32_t link;
	/** Current transfer descriptor */
	uint32_t current;
} __attribute__ (( packed ));

/** A single UHCI transfer
 *
 * UHCI hardware is extremely simple, and requires software to build
 * the entire packet schedule (including manually handling all of the
 * data toggles).  The hardware requires at least 16 bytes of transfer
 * descriptors per 64 bytes of transmitted/received data.  We allocate
 * the transfer descriptors at the time that the transfer is enqueued,
 * to avoid the need to allocate unreasonably large blocks when the
 * endpoint is opened.
 */
struct uhci_transfer {
	/** Producer counter */
	unsigned int prod;
	/** Consumer counter */
	unsigned int cons;
	/** Completed data length */
	size_t len;

	/** Transfer descriptors */
	struct uhci_transfer_descriptor *desc;

	/** I/O buffer */
	struct io_buffer *iobuf;
};

/** Number of transfer descriptors in a ring
 *
 * This is a policy decision.
 */
#define UHCI_RING_COUNT 16

/** A transfer ring */
struct uhci_ring {
	/** Producer counter */
	unsigned int prod;
	/** Consumer counter */
	unsigned int cons;

	/** Maximum packet length */
	size_t mtu;
	/** Base flags
	 *
	 * This incorporates the CERR and LS bits
	 */
	uint8_t flags;
	/** Base control word
	 *
	 * This incorporates the device address, the endpoint address,
	 * and the data toggle for the next descriptor to be enqueued.
	 */
	uint32_t control;

	/** Transfers */
	struct uhci_transfer *xfer[UHCI_RING_COUNT];
	/** End of transfer ring (if non-empty) */
	struct uhci_transfer *end;

	/** Queue head */
	struct uhci_queue_head *head;
};

/**
 * Calculate space used in transfer ring
 *
 * @v ring		Transfer ring
 * @ret fill		Number of entries used
 */
static inline __attribute__ (( always_inline )) unsigned int
uhci_ring_fill ( struct uhci_ring *ring ) {
	unsigned int fill;

	fill = ( ring->prod - ring->cons );
	assert ( fill <= UHCI_RING_COUNT );
	return fill;
}

/**
 * Calculate space remaining in transfer ring
 *
 * @v ring		Transfer ring
 * @ret remaining	Number of entries remaining
 */
static inline __attribute__ (( always_inline )) unsigned int
uhci_ring_remaining ( struct uhci_ring *ring ) {
	unsigned int fill = uhci_ring_fill ( ring );

	return ( UHCI_RING_COUNT - fill );
}

/** Maximum time to wait for host controller to stop
 *
 * This is a policy decision.
 */
#define UHCI_STOP_MAX_WAIT_MS 100

/** Maximum time to wait for reset to complete
 *
 * This is a policy decision.
 */
#define UHCI_RESET_MAX_WAIT_MS 500

/** Maximum time to wait for a port to be enabled
 *
 * This is a policy decision.
 */
#define UHCI_PORT_ENABLE_MAX_WAIT_MS 500

/** A UHCI device */
struct uhci_device {
	/** Registers */
	unsigned long regs;

	/** EHCI companion controller bus:dev.fn address (if any) */
	unsigned int companion;

	/** Asynchronous queue head */
	struct uhci_queue_head *head;
	/** Frame list */
	struct uhci_frame_list *frame;

	/** List of all endpoints */
	struct list_head endpoints;
	/** Asynchronous schedule */
	struct list_head async;
	/** Periodic schedule
	 *
	 * Listed in decreasing order of endpoint interval.
	 */
	struct list_head periodic;

	/** USB bus */
	struct usb_bus *bus;
};

/** A UHCI endpoint */
struct uhci_endpoint {
	/** UHCI device */
	struct uhci_device *uhci;
	/** USB endpoint */
	struct usb_endpoint *ep;
	/** List of all endpoints */
	struct list_head list;
	/** Endpoint schedule */
	struct list_head schedule;

	/** Transfer ring */
	struct uhci_ring ring;
};

#endif /* _IPXE_UHCI_H */