Plan 9 from Bell Labs’s /usr/web/sources/plan9/sys/src/9/pc/usbuhci.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * USB Universal Host Controller Interface (sic) driver.
 *
 * BUGS:
 * - Too many delays and ilocks.
 * - bandwidth admission control must be done per-frame.
 * - interrupt endpoints should go on a tree like [oe]hci.
 * - must warn of power overruns.
 */

#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"../port/error.h"
#include	"../port/usb.h"

typedef struct Ctlio Ctlio;
typedef struct Ctlr Ctlr;
typedef struct Isoio Isoio;
typedef struct Qh Qh;
typedef struct Qhpool Qhpool;
typedef struct Qio Qio;
typedef struct Td Td;
typedef struct Tdpool Tdpool;

enum
{
	Resetdelay	= 100,		/* delay after a controller reset (ms) */
	Enabledelay	= 100,		/* waiting for a port to enable */
	Abortdelay	= 5,		/* delay after cancelling Tds (ms) */
	Incr		= 64,		/* for Td and Qh pools */

	Tdatomic	= 8,		/* max nb. of Tds per bulk I/O op. */

	/* Queue states (software) */
	Qidle		= 0,
	Qinstall,
	Qrun,
	Qdone,
	Qclose,
	Qfree,

	/*
	 * HW constants
	 */

	Nframes		= 1024,		/* 2ⁿ for xspanalloc; max 1024 */
	Align		= 16,		/* for data structures */

	/* Size of small buffer kept within Tds. (software) */
	/* Keep as a multiple of Align to maintain alignment of Tds in pool */
	Tdndata		= 1*Align,

	/* i/o space
	 * Some ports are short, some are long, some are byte.
	 * We use ins[bsl] and not vmap.
	 */
	Cmd		= 0,
		Crun		= 0x01,
		Chcreset	= 0x02,	/* host controller reset */
		Cgreset		= 0x04,	/* global reset */
		Cegsm		= 0x08,	/* enter global suspend */
		Cfgr		= 0x10,	/* forge global resume */
		Cdbg		= 0x20,	/* single step, debug */
		Cmaxp		= 0x80,	/* max packet */

	Status		= 2,
		Susbintr		= 0x01,	/* interrupt */
		Seintr		= 0x02, /* error interrupt */
		Sresume		= 0x04, /* resume detect */
		Shserr		= 0x08, /* host system error */
		Shcerr		= 0x10, /* host controller error */
		Shalted		= 0x20, /* controller halted */
		Sall		= 0x3F,

	Usbintr 		= 4,
		Itmout		= 0x01, /* timeout or crc */
		Iresume		= 0x02, /* resume interrupt enable */
		Ioc		= 0x04, /* interrupt on complete */
		Ishort		= 0x08, /* short packet interrupt */
		Iall		= 0x0F,
	Frnum		= 6,
	Flbaseadd 	= 8,
	SOFmod		= 0xC,		/* start of frame modifier register */

	Portsc0		= 0x10,
		PSpresent	= 0x0001,	/* device present */
		PSstatuschg	= 0x0002,	/* PSpresent changed */
		PSenable	= 0x0004,	/* device enabled */
		PSchange	= 0x0008,	/* PSenable changed */
		PSresume	= 0x0040,	/* resume detected */
		PSreserved1	= 0x0080,	/* always read as 1; reserved */
		PSslow		= 0x0100,	/* device has low speed */
		PSreset		= 0x0200,	/* port reset */
		PSsuspend	= 0x1000,	/* port suspended */

	/* Transfer descriptor link */
	Tdterm		= 0x1,		/* nil (terminate) */
	Tdlinkqh	= 0x2,			/* link refers to a QH */
	Tdvf		= 0x4,		/* run linked Tds first (depth-first)*/

	/* Transfer status bits */
	Tdbitstuff	= 0x00020000,	/* bit stuffing error */
	Tdcrcto		= 0x00040000,	/* crc or timeout error */
	Tdnak		= 0x00080000,	/* nak packet received */
	Tdbabble	= 0x00100000,	/* babble detected */
	Tddberr		= 0x00200000,	/* data buf. error */
	Tdstalled	= 0x00400000,	/* serious error to ep. */
	Tdactive		= 0x00800000,	/* enabled/in use by hw */
	/* Transfer control bits */
	Tdioc		= 0x01000000,	/* interrupt on complete */
	Tdiso		= 0x02000000,	/* isochronous select */
	Tdlow		= 0x04000000,	/* low speed device */
	Tderr1		= 0x08000000,	/* bit 0 of error counter */
	Tderr2		= 0x10000000,	/* bit 1 of error counter */
	Tdspd		= 0x20000000,	/* short packet detect */

	Tdlen		= 0x000003FF,	/* actual length field */

	Tdfatalerr	= Tdnak|Tdbabble|Tdstalled, /* hw retries others */
	Tderrors	= Tdfatalerr|Tdbitstuff|Tdcrcto|Tddberr,

	/* Transfer descriptor token bits */
	Tddata0		= 0,
	Tddata1		= 0x80000,	/* data toggle (1==DATA1) */
	Tdtokin		= 0x69,
	Tdtokout	= 0xE1,
	Tdtoksetup	= 0x2D,

	Tdmaxpkt	= 0x800,	/* max packet size */

	/* Queue head bits */
	QHterm		= 1<<0,		/* nil (terminate) */
	QHlinkqh		= 1<<1,		/* link refers to a QH */
	QHvf		= 1<<2,		/* vertical first (depth first) */
};

struct Ctlr
{
	Lock;			/* for ilock. qh lists and basic ctlr I/O */
	QLock	portlck;	/* for port resets/enable... */
	Pcidev*	pcidev;
	int	active;
	int	port;		/* I/O address */
	Qh*	qhs;		/* list of Qhs for this controller */
	Qh*	qh[Tmax];	/* Dummy Qhs to insert Qhs after */
	Isoio*	iso;		/* list of active iso I/O */
	ulong*	frames;		/* frame list (used by hw) */
	ulong	load;		/* max load for a single frame */
	ulong	isoload;		/* max iso load for a single frame */
	int	nintr;		/* number of interrupts attended */
	int	ntdintr;		/* number of intrs. with something to do */
	int	nqhintr;		/* number of intrs. for Qhs */
	int	nisointr;	/* number of intrs. for iso transfers */
};

struct Qio
{
	QLock;			/* for the entire I/O process */
	Rendez;			/* wait for completion */
	Qh*	qh;		/* Td list (field const after init) */
	int	usbid;		/* usb address for endpoint/device */
	int	toggle;		/* Tddata0/Tddata1 */
	int	tok;		/* Tdtoksetup, Tdtokin, Tdtokout */
	ulong	iotime;		/* time of last I/O */
	int	debug;		/* debug flag from the endpoint */
	char*	err;		/* error string */
};

struct Ctlio
{
	Qio;			/* a single Qio for each RPC */
	uchar*	data;		/* read from last ctl req. */
	int	ndata;		/* number of bytes read */
};

struct Isoio
{
	QLock;
	Rendez;			/* wait for space/completion/errors */
	int	usbid;		/* address used for device/endpoint */
	int	tok;		/* Tdtokin or Tdtokout */
	int	state;		/* Qrun -> Qdone -> Qrun... -> Qclose */
	int	nframes;	/* Nframes/ep->pollival */
	uchar*	data;		/* iso data buffers if not embedded */
	int	td0frno;	/* frame number for first Td */
	Td*	tdu;		/* next td for user I/O in tdps */
	Td*	tdi;		/* next td processed by interrupt */
	char*	err;		/* error string */
	int	nerrs;		/* nb of consecutive I/O errors */
	long	nleft;		/* number of bytes left from last write */
	int	debug;		/* debug flag from the endpoint */
	Isoio*	next;		/* in list of active Isoios */
	Td*	tdps[Nframes];	/* pointer to Td used for i-th frame or nil */
};

struct Tdpool
{
	Lock;
	Td*	free;
	int	nalloc;
	int	ninuse;
	int	nfree;
};

struct Qhpool
{
	Lock;
	Qh*	free;
	int	nalloc;
	int	ninuse;
	int	nfree;
};

/*
 * HW data structures
 */

/*
 * Queue header (known by hw).
 * 16-byte aligned. first two words used by hw.
 * They are taken from the pool upon endpoint opening and
 * queued after the dummy queue header for the endpoint type
 * in the controller. Actual I/O happens as Tds are linked into it.
 * The driver does I/O in lock-step.
 * The user builds a list of Tds and links it into the Qh,
 * then the Qh goes from Qidle to Qrun and nobody touches it until
 * it becomes Qdone at interrupt time.
 * At that point the user collects the Tds and it goes Qidle.
 * A premature cancel may set the state to Qclose and abort I/O.
 * The Ctlr lock protects change of state for Qhs in use.
 */
struct Qh
{
	ulong	link;		/* link to next horiz. item (eg. Qh) */
	ulong	elink;		/* link to element (eg. Td; updated by hw) */

	ulong	state;		/* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */
	Qio*	io;		/* for this queue */

	Qh*	next;		/* in active or free list */
	Td*	tds;		/* Td list in this Qh (initially, elink) */
	char*	tag;		/* debug and align, mostly */
	ulong	align;
};

/*
 * Transfer descriptor.
 * 16-byte aligned. first two words used by hw. Next 4 by sw.
 * We keep an embedded buffer for small I/O transfers.
 * They are taken from the pool when buffers are needed for I/O
 * and linked at the Qh/Isoio for the endpoint and direction requiring it.
 * The block keeps actual data. They are protected from races by
 * the queue or the pool keeping it. The owner of the link to the Td
 * is free to use it and can be the only one using it.
 */
struct Td
{
	ulong	link;		/* Link to next Td or Qh */
	ulong	csw;		/* control and status word (updated by hw) */
	ulong	token;		/* endpt, device, pid */
	ulong	buffer;		/* buffer pointer */

	Td*	next;		/* in qh or Isoio or free list */
	ulong	ndata;		/* bytes available/used at data */
	uchar*	data;		/* pointer to actual data */
	void*	buff;		/* allocated data, for large transfers */

	uchar	sbuff[Tdndata];	/* embedded buffer, for small transfers */
};

#define INB(x)		inb(ctlr->port+(x))
#define	INS(x)		ins(ctlr->port+(x))
#define INL(x)		inl(ctlr->port+(x))
#define OUTB(x, v)	outb(ctlr->port+(x), (v))
#define	OUTS(x, v)	outs(ctlr->port+(x), (v))
#define OUTL(x, v)	outl(ctlr->port+(x), (v))
#define TRUNC(x, sz)	((x) & ((sz)-1))
#define PTR(q)		((void*)KADDR((ulong)(q) & ~ (0xF|PCIWINDOW)))
#define QPTR(q)		((Qh*)PTR(q))
#define TPTR(q)		((Td*)PTR(q))
#define PORT(p)		(Portsc0 + 2*(p))
#define diprint		if(debug || iso->debug)print
#define ddiprint		if(debug>1 || iso->debug>1)print
#define dqprint		if(debug || (qh->io && qh->io->debug))print
#define ddqprint		if(debug>1 || (qh->io && qh->io->debug>1))print

static Ctlr* ctlrs[Nhcis];

static Tdpool tdpool;
static Qhpool qhpool;
static int debug;

static char* qhsname[] = { "idle", "install", "run", "done", "close", "FREE" };

static void
uhcicmd(Ctlr *ctlr, int c)
{
	OUTS(Cmd, c);
}

static void
uhcirun(Ctlr *ctlr, int on)
{
	int i;

	ddprint("uhci %#ux setting run to %d\n", ctlr->port, on);

	if(on)
		uhcicmd(ctlr, INS(Cmd)|Crun);
	else
		uhcicmd(ctlr, INS(Cmd) & ~Crun);
	for(i = 0; i < 100; i++)
		if(on == 0 && (INS(Status) & Shalted) != 0)
			break;
		else if(on != 0 && (INS(Status) & Shalted) == 0)
			break;
		else
			delay(1);
	if(i == 100)
		dprint("uhci %#x run cmd timed out\n", ctlr->port);
	ddprint("uhci %#ux cmd %#ux sts %#ux\n",
		ctlr->port, INS(Cmd), INS(Status));
}

static int
tdlen(Td *td)
{
	return (td->csw+1) & Tdlen;
}

static int
maxtdlen(Td *td)
{
	return ((td->token>>21)+1) & (Tdmaxpkt-1);
}

static int
tdtok(Td *td)
{
	return td->token & 0xFF;
}

static char*
seprinttd(char *s, char *se, Td *td)
{
	s = seprint(s, se, "%#p link %#ulx", td, td->link);
	if((td->link & Tdvf) != 0)
		s = seprint(s, se, "V");
	if((td->link & Tdterm) != 0)
		s = seprint(s, se, "T");
	if((td->link & Tdlinkqh) != 0)
		s = seprint(s, se, "Q");
	s = seprint(s, se, " csw %#ulx ", td->csw);
	if(td->csw & Tdactive)
		s = seprint(s, se, "a");
	if(td->csw & Tdiso)
		s = seprint(s, se, "I");
	if(td->csw & Tdioc)
		s = seprint(s, se, "i");
	if(td->csw & Tdlow)
		s = seprint(s, se, "l");
	if((td->csw & (Tderr1|Tderr2)) == 0)
		s = seprint(s, se, "z");
	if(td->csw & Tderrors)
		s = seprint(s, se, " err %#ulx", td->csw & Tderrors);
	if(td->csw & Tdstalled)
		s = seprint(s, se, "s");
	if(td->csw & Tddberr)
		s = seprint(s, se, "d");
	if(td->csw & Tdbabble)
		s = seprint(s, se, "b");
	if(td->csw & Tdnak)
		s = seprint(s, se, "n");
	if(td->csw & Tdcrcto)
		s = seprint(s, se, "c");
	if(td->csw & Tdbitstuff)
		s = seprint(s, se, "B");
	s = seprint(s, se, " stslen %d", tdlen(td));

	s = seprint(s, se, " token %#ulx", td->token);
	if(td->token == 0)		/* the BWS loopback Td, ignore rest */
		return s;
	s = seprint(s, se, " maxlen %d", maxtdlen(td));
	if(td->token & Tddata1)
		s = seprint(s, se, " d1");
	else
		s = seprint(s, se, " d0");
	s = seprint(s, se, " id %#ulx:", (td->token>>15) & Epmax);
	s = seprint(s, se, "%#ulx", (td->token>>8) & Devmax);
	switch(tdtok(td)){
	case Tdtokin:
		s = seprint(s, se, " in");
		break;
	case Tdtokout:
		s = seprint(s, se, " out");
		break;
	case Tdtoksetup:
		s = seprint(s, se, " setup");
		break;
	default:
		s = seprint(s, se, " BADPID");
	}
	s = seprint(s, se, "\n\t  buffer %#ulx data %#p", td->buffer, td->data);
	s = seprint(s, se, " ndata %uld sbuff %#p buff %#p",
		td->ndata, td->sbuff, td->buff);
	if(td->ndata > 0)
		s = seprintdata(s, se, td->data, td->ndata);
	return s;
}

static void
isodump(Isoio *iso, int all)
{
	char buf[256];
	Td *td;
	int i;

	print("iso %#p %s state %d nframes %d"
		" td0 %#p tdu %#p tdi %#p data %#p\n",
		iso, iso->tok == Tdtokin ? "in" : "out",
		iso->state, iso->nframes, iso->tdps[iso->td0frno],
		iso->tdu, iso->tdi, iso->data);
	if(iso->err != nil)
		print("\terr='%s'\n", iso->err);
	if(all == 0){
		seprinttd(buf, buf+sizeof(buf), iso->tdu);
		print("\ttdu %s\n", buf);
		seprinttd(buf, buf+sizeof(buf), iso->tdi);
		print("\ttdi %s\n", buf);
	}else{
		td = iso->tdps[iso->td0frno];
		for(i = 0; i < iso->nframes; i++){
			seprinttd(buf, buf+sizeof(buf), td);
			if(td == iso->tdi)
				print("i->");
			if(td == iso->tdu)
				print("u->");
			print("\t%s\n", buf);
			td = td->next;
		}
	}
}

static int
sameptr(void *p, ulong l)
{
	if(l & QHterm)
		return p == nil;
	return PTR(l) == p;
}

static void
dumptd(Td *td, char *pref)
{
	char buf[256];
	char *s;
	char *se;
	int i;

	i = 0;
	se = buf+sizeof(buf);
	for(; td != nil; td = td->next){
		s = seprinttd(buf, se, td);
		if(!sameptr(td->next, td->link))
			seprint(s, se, " next %#p != link %#ulx %#p",
				td->next, td->link, TPTR(td->link));
		print("%std %s\n", pref, buf);
		if(i++ > 20){
			print("...more tds...\n");
			break;
		}
	}
}

static void
qhdump(Qh *qh, char *pref)
{
	char buf[256];
	char *s;
	char *se;
	ulong td;
	int i;

	s = buf;
	se = buf+sizeof(buf);
	s = seprint(s, se, "%sqh %s %#p state %s link %#ulx", pref,
		qh->tag, qh, qhsname[qh->state], qh->link);
	if(!sameptr(qh->tds, qh->elink))
		s = seprint(s, se, " [tds %#p != elink %#ulx %#p]",
			qh->tds, qh->elink, TPTR(qh->elink));
	if(!sameptr(qh->next, qh->link))
		s = seprint(s, se, " [next %#p != link %#ulx %#p]",
			qh->next, qh->link, QPTR(qh->link));
	if((qh->link & Tdterm) != 0)
		s = seprint(s, se, "T");
	if((qh->link & Tdlinkqh) != 0)
		s = seprint(s, se, "Q");
	s = seprint(s, se, " elink %#ulx", qh->elink);
	if((qh->elink & Tdterm) != 0)
		s = seprint(s, se, "T");
	if((qh->elink & Tdlinkqh) != 0)
		s = seprint(s, se, "Q");
	s = seprint(s, se, " io %#p", qh->io);
	if(qh->io != nil && qh->io->err != nil)
		seprint(s, se, " err='%s'", qh->io->err);
	print("%s\n", buf);
	dumptd(qh->tds, "\t");
	if((qh->elink & QHterm) == 0){
		print("\thw tds:");
		i = 0;
		for(td = qh->elink; (td & Tdterm) == 0; td = TPTR(td)->link){
			print(" %#ulx", td);
			if(td == TPTR(td)->link)	/* BWS Td */
				break;
			if(i++ > 40){
				print("...");
				break;
			}
		}
		print("\n");
	}
}

static void
xdump(Ctlr *ctlr, int doilock)
{
	Isoio *iso;
	Qh *qh;
	int i;

	if(doilock){
		if(ctlr == ctlrs[0]){
			lock(&tdpool);
			print("tds: alloc %d = inuse %d + free %d\n",
				tdpool.nalloc, tdpool.ninuse, tdpool.nfree);
			unlock(&tdpool);
			lock(&qhpool);
			print("qhs: alloc %d = inuse %d + free %d\n",
				qhpool.nalloc, qhpool.ninuse, qhpool.nfree);
			unlock(&qhpool);
		}
		ilock(ctlr);
	}
	print("uhci port %#x frames %#p nintr %d ntdintr %d",
		ctlr->port, ctlr->frames, ctlr->nintr, ctlr->ntdintr);
	print(" nqhintr %d nisointr %d\n", ctlr->nqhintr, ctlr->nisointr);
	print("cmd %#ux sts %#ux fl %#ulx ps1 %#ux ps2 %#ux frames[0] %#ulx\n",
		INS(Cmd), INS(Status),
		INL(Flbaseadd), INS(PORT(0)), INS(PORT(1)),
		ctlr->frames[0]);
	for(iso = ctlr->iso; iso != nil; iso = iso->next)
		isodump(iso, 1);
	i = 0;
	for(qh = ctlr->qhs; qh != nil; qh = qh->next){
		qhdump(qh, "");
		if(i++ > 20){
			print("qhloop\n");
			break;
		}
	}
	print("\n");
	if(doilock)
		iunlock(ctlr);
}

static void
dump(Hci *hp)
{
	xdump(hp->aux, 1);
}

static Td*
tdalloc(void)
{
	int i;
	Td *td;
	Td *pool;

	lock(&tdpool);
	if(tdpool.free == nil){
		ddprint("uhci: tdalloc %d Tds\n", Incr);
		pool = xspanalloc(Incr*sizeof(Td), Align, 0);
		if(pool == nil)
			panic("tdalloc");
		for(i=Incr; --i>=0;){
			pool[i].next = tdpool.free;
			tdpool.free = &pool[i];
		}
		tdpool.nalloc += Incr;
		tdpool.nfree += Incr;
	}
	td = tdpool.free;
	tdpool.free = td->next;
	tdpool.ninuse++;
	tdpool.nfree--;
	unlock(&tdpool);

	memset(td, 0, sizeof(Td));
	td->link = Tdterm;
	assert(((ulong)td & 0xF) == 0);
	return td;
}

static void
tdfree(Td *td)
{
	if(td == nil)
		return;
	free(td->buff);
	td->buff = nil;
	lock(&tdpool);
	td->next = tdpool.free;
	tdpool.free = td;
	tdpool.ninuse--;
	tdpool.nfree++;
	unlock(&tdpool);
}

static void
qhlinkqh(Qh* qh, Qh* next)
{
	if(next == nil)
		qh->link = QHterm;
	else{
		next->link = qh->link;
		next->next = qh->next;
		qh->link = PCIWADDR(next)|QHlinkqh;
	}
	qh->next = next;
}

static void
qhlinktd(Qh *qh, Td *td)
{
	qh->tds = td;
	if(td == nil)
		qh->elink = QHvf|QHterm;
	else
		qh->elink = PCIWADDR(td);
}

static void
tdlinktd(Td *td, Td *next)
{
	td->next = next;
	if(next == nil)
		td->link = Tdterm;
	else
		td->link = PCIWADDR(next)|Tdvf;
}

static Qh*
qhalloc(Ctlr *ctlr, Qh *prev, Qio *io, char *tag)
{
	int i;
	Qh *qh;
	Qh *pool;

	lock(&qhpool);
	if(qhpool.free == nil){
		ddprint("uhci: qhalloc %d Qhs\n", Incr);
		pool = xspanalloc(Incr*sizeof(Qh), Align, 0);
		if(pool == nil)
			panic("qhalloc");
		for(i=Incr; --i>=0;){
			pool[i].next = qhpool.free;
			qhpool.free = &pool[i];
		}
		qhpool.nalloc += Incr;
		qhpool.nfree += Incr;
	}
	qh = qhpool.free;
	qhpool.free = qh->next;
	qh->next = nil;
	qh->link = QHterm;
	qhpool.ninuse++;
	qhpool.nfree--;
	unlock(&qhpool);

	qh->tds = nil;
	qh->elink = QHterm;
	qh->state = Qidle;
	qh->io = io;
	qh->tag = nil;
	kstrdup(&qh->tag, tag);

	if(prev != nil){
		coherence();
		ilock(ctlr);
		qhlinkqh(prev, qh);
		iunlock(ctlr);
	}

	assert(((ulong)qh & 0xF) == 0);
	return qh;
}

static void
qhfree(Ctlr *ctlr, Qh *qh)
{
	Td *td;
	Td *ltd;
	Qh *q;

	if(qh == nil)
		return;

	ilock(ctlr);
	for(q = ctlr->qhs; q != nil; q = q->next)
		if(q->next == qh)
			break;
	if(q == nil)
		panic("qhfree: nil q");
	q->next = qh->next;
	q->link = qh->link;
	iunlock(ctlr);

	for(td = qh->tds; td != nil; td = ltd){
		ltd = td->next;
		tdfree(td);
	}
	lock(&qhpool);
	qh->state = Qfree;	/* paranoia */
	qh->next = qhpool.free;
	qh->tag = nil;
	qh->io = nil;
	qhpool.free = qh;
	qhpool.ninuse--;
	qhpool.nfree++;
	unlock(&qhpool);
	ddprint("qhfree: qh %#p\n", qh);
}

static char*
errmsg(int err)
{
	if(err == 0)
		return "ok";
	if(err & Tdcrcto)
		return "crc/timeout error";
	if(err & Tdbabble)
		return "babble detected";
	if(err & Tddberr)
		return "db error";
	if(err & Tdbitstuff)
		return "bit stuffing error";
	if(err & Tdstalled)
		return Estalled;
	return Eio;
}

static int
isocanread(void *a)
{
	Isoio *iso;

	iso = a;
	return iso->state == Qclose ||
		(iso->state == Qrun &&
		iso->tok == Tdtokin && iso->tdi != iso->tdu);
}

static int
isocanwrite(void *a)
{
	Isoio *iso;

	iso = a;
	return iso->state == Qclose ||
		(iso->state == Qrun &&
		iso->tok == Tdtokout && iso->tdu->next != iso->tdi);
}

static void
tdisoinit(Isoio *iso, Td *td, long count)
{
	td->ndata = count;
	td->token = ((count-1)<<21)| ((iso->usbid & 0x7FF)<<8) | iso->tok;
	td->csw = Tderr1|Tdiso|Tdactive|Tdioc;
}

/*
 * Process Iso i/o on interrupt. For writes update just error status.
 * For reads update tds to reflect data and also error status.
 * When tdi aproaches tdu, advance tdu; data may be lost.
 * (If nframes is << Nframes tdu might be far away but this avoids
 * races regarding frno.)
 * If we suffer errors for more than half the frames we stall.
 */
static void
isointerrupt(Ctlr *ctlr, Isoio* iso)
{
	Td *tdi;
	int err;
	int i;
	int nframes;

	tdi = iso->tdi;
	if((tdi->csw & Tdactive) != 0)		/* nothing new done */
		return;
	ctlr->nisointr++;
	ddiprint("isointr: iso %#p: tdi %#p tdu %#p\n", iso, tdi, iso->tdu);
	if(iso->state != Qrun && iso->state != Qdone)
		panic("isointr: iso state");
	if(debug > 1 || iso->debug > 1)
		isodump(iso, 0);

	nframes = iso->nframes / 2;		/* limit how many we look */
	if(nframes > 64)
		nframes = 64;

	for(i = 0; i < nframes && (tdi->csw & Tdactive) == 0; i++){
		tdi->csw &= ~Tdioc;
		err = tdi->csw & Tderrors;
		if(err == 0)
			iso->nerrs = 0;
		else if(iso->nerrs++ > iso->nframes/2)
			tdi->csw |= Tdstalled;
		if((tdi->csw & Tdstalled) != 0){
			if(iso->err == nil){
				iso->err = errmsg(err);
				diprint("isointerrupt: tdi %#p error %#ux %s\n",
					tdi, err, iso->err);
				diprint("ctlr load %uld\n", ctlr->load);
			}
			tdi->ndata = 0;
		}else
			tdi->ndata = tdlen(tdi);

		if(tdi->next == iso->tdu || tdi->next->next == iso->tdu){
			memset(iso->tdu->data, 0, maxtdlen(iso->tdu));
			tdisoinit(iso, iso->tdu, maxtdlen(iso->tdu));
			iso->tdu = iso->tdu->next;
			iso->nleft = 0;
		}
		tdi = tdi->next;
	}
	ddiprint("isointr: %d frames processed\n", nframes);
	if(i == nframes)
		tdi->csw |= Tdioc;
	iso->tdi = tdi;
	if(isocanwrite(iso) || isocanread(iso)){
		diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
			iso->tdi, iso->tdu);
		wakeup(iso);
	}

}

/*
 * Process a Qh upon interrupt. There's one per ongoing user I/O.
 * User process releases resources later, that is not done here.
 * We may find in this order one or more Tds:
 * - none/many non active and completed Tds
 * - none/one (usually(!) not active) and failed Td
 * - none/many active Tds.
 * Upon errors the entire transfer is aborted and error reported.
 * Otherwise, the transfer is complete only when all Tds are done or
 * when a read with less than maxpkt is found.
 * Use the software list and not qh->elink to avoid races.
 * We could use qh->elink to see if there's something new or not.
 */
static void
qhinterrupt(Ctlr *ctlr, Qh *qh)
{
	Td *td;
	int err;

	ctlr->nqhintr++;
	if(qh->state != Qrun)
		panic("qhinterrupt: qh state");
	if(qh->tds == nil)
		panic("qhinterrupt: no tds");
	if((qh->tds->csw & Tdactive) == 0)
		ddqprint("qhinterrupt port %#ux qh %#p p0 %#x p1 %#x\n",
			ctlr->port, qh, INS(PORT(0)), INS(PORT(1)));
	for(td = qh->tds; td != nil; td = td->next){
		if(td->csw & Tdactive)
			return;
		td->csw &= ~Tdioc;
		if((td->csw & Tdstalled) != 0){
			err = td->csw & Tderrors;
			/* just stalled is end of xfer but not an error */
			if(err != Tdstalled && qh->io->err == nil){
				qh->io->err = errmsg(td->csw & Tderrors);
				dqprint("qhinterrupt: td %#p error %#ux %s\n",
					td, err, qh->io->err);
				dqprint("ctlr load %uld\n", ctlr->load);
			}
			break;
		}
		if((td->csw & Tdnak) != 0){	/* retransmit; not serious */
			td->csw &= ~Tdnak;
			if(td->next == nil)
				td->csw |= Tdioc;
		}
		td->ndata = tdlen(td);
		if(td->ndata < maxtdlen(td)){	/* EOT */
			td = td->next;
			break;
		}
	}

	/*
	 * Done. Make void the Tds not used (errors or EOT) and wakeup epio.
	 */
	qh->elink = QHterm;
	for(; td != nil; td = td->next)
		td->ndata = 0;
	qh->state = Qdone;
	wakeup(qh->io);
}

static void
interrupt(Ureg*, void *a)
{
	Hci *hp;
	Ctlr *ctlr;
	int frptr;
	int frno;
	Qh *qh;
	Isoio *iso;
	int sts;
	int cmd;

	hp = a;
	ctlr = hp->aux;
	ilock(ctlr);
	ctlr->nintr++;
	sts = INS(Status);
	if((sts & Sall) == 0){		/* not for us; sharing irq */
		iunlock(ctlr);
		return;
	}
	OUTS(Status, sts & Sall);
	cmd = INS(Cmd);
	if(cmd & Crun == 0){
		print("uhci %#ux: not running: uhci bug?\n", ctlr->port);
		/* BUG: should abort everything in this case */
	}
	if(debug > 1){
		frptr = INL(Flbaseadd);
		frno = INL(Frnum);
		frno = TRUNC(frno, Nframes);
		print("cmd %#ux sts %#ux frptr %#ux frno %d\n",
			cmd, sts, frptr, frno);
	}
	ctlr->ntdintr++;
	/*
	 * Will we know in USB 3.0 who the interrupt was for?.
	 * Do they still teach indexing in CS?
	 * This is Intel's doing.
	 */
	for(iso = ctlr->iso; iso != nil; iso = iso->next)
		if(iso->state == Qrun || iso->state == Qdone)
			isointerrupt(ctlr, iso);
	for(qh = ctlr->qhs; qh != nil; qh = qh->next)
		if(qh->state == Qrun)
			qhinterrupt(ctlr, qh);
		else if(qh->state == Qclose)
			qhlinktd(qh, nil);
	iunlock(ctlr);
}

/*
 * iso->tdu is the next place to put data. When it gets full
 * it is activated and tdu advanced.
 */
static long
putsamples(Isoio *iso, uchar *b, long count)
{
	long tot;
	long n;

	for(tot = 0; isocanwrite(iso) && tot < count; tot += n){
		n = count-tot;
		if(n > maxtdlen(iso->tdu) - iso->nleft)
			n = maxtdlen(iso->tdu) - iso->nleft;
		memmove(iso->tdu->data+iso->nleft, b+tot, n);
		iso->nleft += n;
		if(iso->nleft == maxtdlen(iso->tdu)){
			tdisoinit(iso, iso->tdu, iso->nleft);
			iso->nleft = 0;
			iso->tdu = iso->tdu->next;
		}
	}
	return tot;
}

/*
 * Queue data for writing and return error status from
 * last writes done, to maintain buffered data.
 */
static long
episowrite(Ep *ep, Isoio *iso, void *a, long count)
{
	Ctlr *ctlr;
	uchar *b;
	int tot;
	int nw;
	char *err;

	iso->debug = ep->debug;
	diprint("uhci: episowrite: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);

	ctlr = ep->hp->aux;
	qlock(iso);
	if(waserror()){
		qunlock(iso);
		nexterror();
	}
	ilock(ctlr);
	if(iso->state == Qclose){
		iunlock(ctlr);
		error(iso->err ? iso->err : Eio);
	}
	iso->state = Qrun;
	b = a;
	for(tot = 0; tot < count; tot += nw){
		while(isocanwrite(iso) == 0){
			iunlock(ctlr);
			diprint("uhci: episowrite: %#p sleep\n", iso);
			if(waserror()){
				if(iso->err == nil)
					iso->err = "I/O timed out";
				ilock(ctlr);
				break;
			}
			tsleep(iso, isocanwrite, iso, ep->tmout);
			poperror();
			ilock(ctlr);
		}
		err = iso->err;
		iso->err = nil;
		if(iso->state == Qclose || err != nil){
			iunlock(ctlr);
			error(err ? err : Eio);
		}
		if(iso->state != Qrun)
			panic("episowrite: iso not running");
		iunlock(ctlr);		/* We could page fault here */
		nw = putsamples(iso, b+tot, count-tot);
		ilock(ctlr);
	}
	if(iso->state != Qclose)
		iso->state = Qdone;
	iunlock(ctlr);
	err = iso->err;		/* in case it failed early */
	iso->err = nil;
	qunlock(iso);
	poperror();
	if(err != nil)
		error(err);
	diprint("uhci: episowrite: %#p %d bytes\n", iso, tot);
	return tot;
}

/*
 * Available data is kept at tdu and following tds, up to tdi (excluded).
 */
static long
episoread(Ep *ep, Isoio *iso, void *a, int count)
{
	Ctlr *ctlr;
	uchar *b;
	int nr;
	int tot;
	Td *tdu;

	iso->debug = ep->debug;
	diprint("uhci: episoread: %#p ep%d.%d\n", iso, ep->dev->nb, ep->nb);

	b = a;
	ctlr = ep->hp->aux;
	qlock(iso);
	if(waserror()){
		qunlock(iso);
		nexterror();
	}
	iso->err = nil;
	iso->nerrs = 0;
	ilock(ctlr);
	if(iso->state == Qclose){
		iunlock(ctlr);
		error(iso->err ? iso->err : Eio);
	}
	iso->state = Qrun;
	while(isocanread(iso) == 0){
		iunlock(ctlr);
		diprint("uhci: episoread: %#p sleep\n", iso);
		if(waserror()){
			if(iso->err == nil)
				iso->err = "I/O timed out";
			ilock(ctlr);
			break;
		}
		tsleep(iso, isocanread, iso, ep->tmout);
		poperror();
		ilock(ctlr);
	}
	if(iso->state == Qclose){
		iunlock(ctlr);
		error(iso->err ? iso->err : Eio);
	}
	iso->state = Qdone;
	assert(iso->tdu != iso->tdi);

	for(tot = 0; iso->tdi != iso->tdu && tot < count; tot += nr){
		tdu = iso->tdu;
		if(tdu->csw & Tdactive){
			diprint("uhci: episoread: %#p tdu active\n", iso);
			break;
		}
		nr = tdu->ndata;
		if(tot + nr > count)
			nr = count - tot;
		if(nr == 0)
			print("uhci: ep%d.%d: too many polls\n",
				ep->dev->nb, ep->nb);
		else{
			iunlock(ctlr);		/* We could page fault here */
			memmove(b+tot, tdu->data, nr);
			ilock(ctlr);
			if(nr < tdu->ndata)
				memmove(tdu->data, tdu->data+nr, tdu->ndata - nr);
			tdu->ndata -= nr;
		}
		if(tdu->ndata == 0){
			tdisoinit(iso, tdu, ep->maxpkt);
			iso->tdu = tdu->next;
		}
	}
	iunlock(ctlr);
	qunlock(iso);
	poperror();
	diprint("uhci: episoread: %#p %d bytes err '%s'\n", iso, tot, iso->err);
	if(iso->err != nil)
		error(iso->err);
	return tot;
}

static int
nexttoggle(int tog)
{
	if(tog == Tddata0)
		return Tddata1;
	else
		return Tddata0;
}

static Td*
epgettd(Ep *ep, Qio *io, int flags, void *a, int count)
{
	Td *td;
	int tok;

	if(ep->maxpkt < count)
		error("maxpkt too short");
	td = tdalloc();
	if(count <= Tdndata)
		td->data = td->sbuff;
	else
		td->data = td->buff = smalloc(ep->maxpkt);
	td->buffer = PCIWADDR(td->data);
	td->ndata = count;
	if(a != nil && count > 0)
		memmove(td->data, a, count);
	td->csw = Tderr2|Tderr1|flags;
	if(ep->dev->speed == Lowspeed)
		td->csw |= Tdlow;
	tok = io->tok | io->toggle;
	io->toggle = nexttoggle(io->toggle);
	td->token = ((count-1)<<21) | ((io->usbid&0x7FF)<<8) | tok;

	return td;
}

/*
 * Try to get them idle
 */
static void
aborttds(Qh *qh)
{
	Td *td;

	qh->state = Qdone;
	qh->elink = QHterm;
	for(td = qh->tds; td != nil; td = td->next){
		if(td->csw & Tdactive)
			td->ndata = 0;
		td->csw &= ~(Tdactive|Tdioc);
	}
}

static int
epiodone(void *a)
{
	Qh *qh;

	qh = a;
	return qh->state != Qrun;
}

static void
epiowait(Ctlr *ctlr, Qio *io, int tmout, ulong load)
{
	Qh *qh;
	int timedout;

	qh = io->qh;
	ddqprint("uhci io %#p sleep on qh %#p state %uld\n", io, qh, qh->state);
	timedout = 0;
	if(waserror()){
		dqprint("uhci io %#p qh %#p timed out\n", io, qh);
		timedout++;
	}else{
		if(tmout == 0)
			sleep(io, epiodone, qh);
		else
			tsleep(io, epiodone, qh, tmout);
		poperror();
	}
	ilock(ctlr);
	if(qh->state == Qrun)
		timedout = 1;
	else if(qh->state != Qdone && qh->state != Qclose)
		panic("epio: queue not done and not closed");
	if(timedout){
		aborttds(io->qh);
		io->err = "request timed out";
		iunlock(ctlr);
		if(!waserror()){
			tsleep(&up->sleep, return0, 0, Abortdelay);
			poperror();
		}
		ilock(ctlr);
	}
	if(qh->state != Qclose)
		qh->state = Qidle;
	qhlinktd(qh, nil);
	ctlr->load -= load;
	iunlock(ctlr);
}

/*
 * Non iso I/O.
 * To make it work for control transfers, the caller may
 * lock the Qio for the entire control transfer.
 */
static long
epio(Ep *ep, Qio *io, void *a, long count, int mustlock)
{
	Td *td, *ltd, *td0, *ntd;
	Ctlr *ctlr;
	Qh* qh;
	long n, tot;
	char buf[128];
	uchar *c;
	int saved, ntds, tmout;
	ulong load;
	char *err;

	qh = io->qh;
	ctlr = ep->hp->aux;
	io->debug = ep->debug;
	tmout = ep->tmout;
	ddeprint("epio: %s ep%d.%d io %#p count %ld load %uld\n",
		io->tok == Tdtokin ? "in" : "out",
		ep->dev->nb, ep->nb, io, count, ctlr->load);
	if((debug > 1 || ep->debug > 1) && io->tok != Tdtokin){
		seprintdata(buf, buf+sizeof(buf), a, count);
		print("uchi epio: user data: %s\n", buf);
	}
	if(mustlock){
		qlock(io);
		if(waserror()){
			qunlock(io);
			nexterror();
		}
	}
	io->err = nil;
	ilock(ctlr);
	if(qh->state == Qclose){	/* Tds released by cancelio */
		iunlock(ctlr);
		error(io->err ? io->err : Eio);
	}
	if(qh->state != Qidle)
		panic("epio: qh not idle");
	qh->state = Qinstall;
	iunlock(ctlr);

	c = a;
	td0 = ltd = nil;
	load = tot = 0;
	do{
		n = ep->maxpkt;
		if(count-tot < n)
			n = count-tot;
		if(c != nil && io->tok != Tdtokin)
			td = epgettd(ep, io, Tdactive, c+tot, n);
		else
			td = epgettd(ep, io, Tdactive|Tdspd, nil, n);
		if(td0 == nil)
			td0 = td;
		else
			tdlinktd(ltd, td);
		ltd = td;
		tot += n;
		load += ep->load;
	}while(tot < count);
	if(td0 == nil || ltd == nil)
		panic("epio: no td");

	ltd->csw |= Tdioc;	/* the last one interrupts */
	ddeprint("uhci: load %uld ctlr load %uld\n", load, ctlr->load);
	ilock(ctlr);
	if(qh->state != Qclose){
		io->iotime = TK2MS(MACHP(0)->ticks);
		qh->state = Qrun;
		coherence();
		qhlinktd(qh, td0);
		ctlr->load += load;
	}
	iunlock(ctlr);

	epiowait(ctlr, io, tmout, load);

	if(debug > 1 || ep->debug > 1)
		dumptd(td0, "epio: got tds: ");

	tot = 0;
	c = a;
	saved = 0;
	ntds = 0;
	for(td = td0; td != nil; td = ntd){
		ntds++;
		/*
		 * Use td tok, not io tok, because of setup packets.
		 * Also, if the Td was stalled or active (previous Td
		 * was a short packet), we must save the toggle as it is.
		 */
		if(td->csw & (Tdstalled|Tdactive)){
			if(saved++ == 0)
				io->toggle = td->token & Tddata1;
		}else{
			tot += td->ndata;
			if(c != nil && tdtok(td) == Tdtokin && td->ndata > 0){
				memmove(c, td->data, td->ndata);
				c += td->ndata;
			}
		}
		ntd = td->next;
		tdfree(td);
	}
	err = io->err;
	if(mustlock){
		qunlock(io);
		poperror();
	}
	ddeprint("epio: io %#p: %d tds: return %ld err '%s'\n",
		io, ntds, tot, err);
	if(err != nil)
		error(err);
	if(tot < 0)
		error(Eio);
	return tot;
}

/*
 * halt condition was cleared on the endpoint. update our toggles.
 */
static void
clrhalt(Ep *ep)
{
	Qio *io;

	ep->clrhalt = 0;
	switch(ep->ttype){
	case Tbulk:
	case Tintr:
		io = ep->aux;
		if(ep->mode != OREAD){
			qlock(&io[OWRITE]);
			io[OWRITE].toggle = Tddata0;
			deprint("ep clrhalt for io %#p\n", io+OWRITE);
			qunlock(&io[OWRITE]);
		}
		if(ep->mode != OWRITE){
			qlock(&io[OREAD]);
			io[OREAD].toggle = Tddata0;
			deprint("ep clrhalt for io %#p\n", io+OREAD);
			qunlock(&io[OREAD]);
		}
		break;
	}
}

static long
epread(Ep *ep, void *a, long count)
{
	Ctlio *cio;
	Qio *io;
	Isoio *iso;
	char buf[160];
	ulong delta;

	ddeprint("uhci: epread\n");
	if(ep->aux == nil)
		panic("epread: not open");

	switch(ep->ttype){
	case Tctl:
		cio = ep->aux;
		qlock(cio);
		if(waserror()){
			qunlock(cio);
			nexterror();
		}
		ddeprint("epread ctl ndata %d\n", cio->ndata);
		if(cio->ndata < 0)
			error("request expected");
		else if(cio->ndata == 0){
			cio->ndata = -1;
			count = 0;
		}else{
			if(count > cio->ndata)
				count = cio->ndata;
			if(count > 0)
				memmove(a, cio->data, count);
			/* BUG for big transfers */
			free(cio->data);
			cio->data = nil;
			cio->ndata = 0;	/* signal EOF next time */
		}
		qunlock(cio);
		poperror();
		if(debug>1 || ep->debug){
			seprintdata(buf, buf+sizeof(buf), a, count);
			print("epread: %s\n", buf);
		}
		return count;
	case Tbulk:
		io = ep->aux;
		if(ep->clrhalt)
			clrhalt(ep);
		return epio(ep, &io[OREAD], a, count, 1);
	case Tintr:
		io = ep->aux;
		delta = TK2MS(MACHP(0)->ticks) - io[OREAD].iotime + 1;
		if(delta < ep->pollival / 2)
			tsleep(&up->sleep, return0, 0, ep->pollival/2 - delta);
		if(ep->clrhalt)
			clrhalt(ep);
		return epio(ep, &io[OREAD], a, count, 1);
	case Tiso:
		iso = ep->aux;
		return episoread(ep, iso, a, count);
	default:
		panic("epread: bad ep ttype %d", ep->ttype);
	}
	return -1;
}

/*
 * Control transfers are one setup write (data0)
 * plus zero or more reads/writes (data1, data0, ...)
 * plus a final write/read with data1 to ack.
 * For both host to device and device to host we perform
 * the entire transfer when the user writes the request,
 * and keep any data read from the device for a later read.
 * We call epio three times instead of placing all Tds at
 * the same time because doing so leads to crc/tmout errors
 * for some devices.
 * Upon errors on the data phase we must still run the status
 * phase or the device may cease responding in the future.
 */
static long
epctlio(Ep *ep, Ctlio *cio, void *a, long count)
{
	uchar *c;
	long len;

	ddeprint("epctlio: cio %#p ep%d.%d count %ld\n",
		cio, ep->dev->nb, ep->nb, count);
	if(count < Rsetuplen)
		error("short usb comand");
	qlock(cio);
	free(cio->data);
	cio->data = nil;
	cio->ndata = 0;
	if(waserror()){
		qunlock(cio);
		free(cio->data);
		cio->data = nil;
		cio->ndata = 0;
		nexterror();
	}

	/* set the address if unset and out of configuration state */
	if(ep->dev->state != Dconfig && ep->dev->state != Dreset)
		if(cio->usbid == 0)
			cio->usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb&Devmax);
	c = a;
	cio->tok = Tdtoksetup;
	cio->toggle = Tddata0;
	if(epio(ep, cio, a, Rsetuplen, 0) < Rsetuplen)
		error(Eio);
	a = c + Rsetuplen;
	count -= Rsetuplen;

	cio->toggle = Tddata1;
	if(c[Rtype] & Rd2h){
		cio->tok = Tdtokin;
		len = GET2(c+Rcount);
		if(len <= 0)
			error("bad length in d2h request");
		if(len > Maxctllen)
			error("d2h data too large to fit in uhci");
		a = cio->data = smalloc(len+1);
	}else{
		cio->tok = Tdtokout;
		len = count;
	}
	if(len > 0)
		if(waserror())
			len = -1;
		else{
			len = epio(ep, cio, a, len, 0);
			poperror();
		}
	if(c[Rtype] & Rd2h){
		count = Rsetuplen;
		cio->ndata = len;
		cio->tok = Tdtokout;
	}else{
		if(len < 0)
			count = -1;
		else
			count = Rsetuplen + len;
		cio->tok = Tdtokin;
	}
	cio->toggle = Tddata1;
	epio(ep, cio, nil, 0, 0);
	qunlock(cio);
	poperror();
	ddeprint("epctlio cio %#p return %ld\n", cio, count);
	return count;
}

static long
epwrite(Ep *ep, void *a, long count)
{
	Ctlio *cio;
	Isoio *iso;
	Qio *io;
	ulong delta;
	char *b;
	int tot;
	int nw;

	ddeprint("uhci: epwrite ep%d.%d\n", ep->dev->nb, ep->nb);
	if(ep->aux == nil)
		panic("uhci: epwrite: not open");
	switch(ep->ttype){
	case Tctl:
		cio = ep->aux;
		return epctlio(ep, cio, a, count);
	case Tbulk:
		io = ep->aux;
		if(ep->clrhalt)
			clrhalt(ep);
		/*
		 * Put at most Tdatomic Tds (512 bytes) at a time.
		 * Otherwise some devices produce babble errors.
		 */
		b = a;
		for(tot = 0; tot < count ; tot += nw){
			nw = count - tot;
			if(nw > Tdatomic * ep->maxpkt)
				nw = Tdatomic * ep->maxpkt;
			nw = epio(ep, &io[OWRITE], b+tot, nw, 1);
		}
		return tot;
	case Tintr:
		io = ep->aux;
		delta = TK2MS(MACHP(0)->ticks) - io[OWRITE].iotime + 1;
		if(delta < ep->pollival)
			tsleep(&up->sleep, return0, 0, ep->pollival - delta);
		if(ep->clrhalt)
			clrhalt(ep);
		return epio(ep, &io[OWRITE], a, count, 1);
	case Tiso:
		iso = ep->aux;
		return episowrite(ep, iso, a, count);
	default:
		panic("uhci: epwrite: bad ep ttype %d", ep->ttype);
	}
	return -1;
}

static void
isoopen(Ep *ep)
{
	Ctlr *ctlr;
	Isoio *iso;
	int frno;
	int i;
	Td* td;
	Td* ltd;
	int size;
	int left;

	if(ep->mode == ORDWR)
		error("iso i/o is half-duplex");
	ctlr = ep->hp->aux;
	iso = ep->aux;
	iso->debug = ep->debug;
	iso->next = nil;			/* paranoia */
	if(ep->mode == OREAD)
		iso->tok = Tdtokin;
	else
		iso->tok = Tdtokout;
	iso->usbid = ((ep->nb & Epmax)<<7)|(ep->dev->nb & Devmax);
	iso->state = Qidle;
	iso->nframes = Nframes/ep->pollival;
	if(iso->nframes < 3)
		error("uhci isoopen bug");	/* we need at least 3 tds */

	ilock(ctlr);
	if(ctlr->load + ep->load > 800)
		print("usb: uhci: bandwidth may be exceeded\n");
	ctlr->load += ep->load;
	ctlr->isoload += ep->load;
	dprint("uhci: load %uld isoload %uld\n", ctlr->load, ctlr->isoload);
	iunlock(ctlr);

	/*
	 * From here on this cannot raise errors
	 * unless we catch them and release here all memory allocated.
	 */
	if(ep->maxpkt > Tdndata)
		iso->data = smalloc(iso->nframes*ep->maxpkt);
	ilock(ctlr);
	frno = INS(Frnum) + 10;			/* start 10ms ahead */
	frno = TRUNC(frno, Nframes);
	iunlock(ctlr);
	iso->td0frno = frno;
	ltd = nil;
	left = 0;
	for(i = 0; i < iso->nframes; i++){
		td = iso->tdps[frno] = tdalloc();
		if(ep->mode == OREAD)
			size = ep->maxpkt;
		else{
			size = (ep->hz+left) * ep->pollival / 1000;
			size *= ep->samplesz;
			left = (ep->hz+left) * ep->pollival % 1000;
			if(size > ep->maxpkt){
				print("uhci: ep%d.%d: size > maxpkt\n",
					ep->dev->nb, ep->nb);
				print("size = %d max = %ld\n", size, ep->maxpkt);
				size = ep->maxpkt;
			}
		}
		if(size > Tdndata)
			td->data = iso->data + i * ep->maxpkt;
		else
			td->data = td->sbuff;
		td->buffer = PCIWADDR(td->data);
		tdisoinit(iso, td, size);
		if(ltd != nil)
			ltd->next = td;
		ltd = td;
		frno = TRUNC(frno+ep->pollival, Nframes);
	}
	ltd->next = iso->tdps[iso->td0frno];
	iso->tdi = iso->tdps[iso->td0frno];
	iso->tdu = iso->tdi;	/* read: right now; write: 1s ahead */
	ilock(ctlr);
	frno = iso->td0frno;
	for(i = 0; i < iso->nframes; i++){
		iso->tdps[frno]->link = ctlr->frames[frno];
		frno = TRUNC(frno+ep->pollival, Nframes);
	}
	coherence();
	frno = iso->td0frno;
	for(i = 0; i < iso->nframes; i++){
		ctlr->frames[frno] = PCIWADDR(iso->tdps[frno]);
		frno = TRUNC(frno+ep->pollival, Nframes);
	}
	iso->next = ctlr->iso;
	ctlr->iso = iso;
	iso->state = Qdone;
	iunlock(ctlr);
	if(debug > 1 || iso->debug >1)
		isodump(iso, 0);
}

/*
 * Allocate the endpoint and set it up for I/O
 * in the controller. This must follow what's said
 * in Ep regarding configuration, including perhaps
 * the saved toggles (saved on a previous close of
 * the endpoint data file by epclose).
 */
static void
epopen(Ep *ep)
{
	Ctlr *ctlr;
	Qh *cqh;
	Qio *io;
	Ctlio *cio;
	int usbid;

	ctlr = ep->hp->aux;
	deprint("uhci: epopen ep%d.%d\n", ep->dev->nb, ep->nb);
	if(ep->aux != nil)
		panic("uhci: epopen called with open ep");
	if(waserror()){
		free(ep->aux);
		ep->aux = nil;
		nexterror();
	}
	if(ep->maxpkt > Tdmaxpkt){
		print("uhci: maxkpkt too large: using %d\n", Tdmaxpkt);
		ep->maxpkt = Tdmaxpkt;
	}
	cqh = ctlr->qh[ep->ttype];
	switch(ep->ttype){
	case Tnone:
		error("endpoint not configured");
	case Tiso:
		ep->aux = smalloc(sizeof(Isoio));
		isoopen(ep);
		break;
	case Tctl:
		cio = ep->aux = smalloc(sizeof(Ctlio));
		cio->debug = ep->debug;
		cio->ndata = -1;
		cio->data = nil;
		if(ep->dev->isroot != 0 && ep->nb == 0)	/* root hub */
			break;
		cio->qh = qhalloc(ctlr, cqh, cio, "epc");
		break;
	case Tbulk:
	case Tintr:
		io = ep->aux = smalloc(sizeof(Qio)*2);
		io[OREAD].debug = io[OWRITE].debug = ep->debug;
		usbid = ((ep->nb&Epmax)<<7)|(ep->dev->nb &Devmax);
		if(ep->mode != OREAD){
			if(ep->toggle[OWRITE] != 0)
				io[OWRITE].toggle = Tddata1;
			else
				io[OWRITE].toggle = Tddata0;
			io[OWRITE].tok = Tdtokout;
			io[OWRITE].qh = qhalloc(ctlr, cqh, io+OWRITE, "epw");
			io[OWRITE].usbid = usbid;
		}
		if(ep->mode != OWRITE){
			if(ep->toggle[OREAD] != 0)
				io[OREAD].toggle = Tddata1;
			else
				io[OREAD].toggle = Tddata0;
			io[OREAD].tok = Tdtokin;
			io[OREAD].qh = qhalloc(ctlr, cqh, io+OREAD, "epr");
			io[OREAD].usbid = usbid;
		}
		break;
	}
	if(debug>1 || ep->debug)
		dump(ep->hp);
	deprint("uhci: epopen done\n");
	poperror();
}

static void
cancelio(Ctlr *ctlr, Qio *io)
{
	Qh *qh;

	ilock(ctlr);
	qh = io->qh;
	if(io == nil || io->qh == nil || io->qh->state == Qclose){
		iunlock(ctlr);
		return;
	}
	dqprint("uhci: cancelio for qh %#p state %s\n",
		qh, qhsname[qh->state]);
	aborttds(qh);
	qh->state = Qclose;
	iunlock(ctlr);
	if(!waserror()){
		tsleep(&up->sleep, return0, 0, Abortdelay);
		poperror();
	}

	wakeup(io);
	qlock(io);
	/* wait for epio if running */
	qunlock(io);

	qhfree(ctlr, qh);
	io->qh = nil;
}

static void
cancelisoio(Ctlr *ctlr, Isoio *iso, int pollival, ulong load)
{
	Isoio **il;
	ulong *lp;
	int i;
	int frno;
	Td *td;

	ilock(ctlr);
	if(iso->state == Qclose){
		iunlock(ctlr);
		return;
	}
	if(iso->state != Qrun && iso->state != Qdone)
		panic("bad iso state");
	iso->state = Qclose;
	if(ctlr->isoload < load)
		panic("uhci: low isoload");
	ctlr->isoload -= load;
	ctlr->load -= load;
	for(il = &ctlr->iso; *il != nil; il = &(*il)->next)
		if(*il == iso)
			break;
	if(*il == nil)
		panic("isocancel: not found");
	*il = iso->next;
	frno = iso->td0frno;
	for(i = 0; i < iso->nframes; i++){
		td = iso->tdps[frno];
		td->csw &= ~(Tdioc|Tdactive);
		for(lp=&ctlr->frames[frno]; !(*lp & Tdterm);
					lp = &TPTR(*lp)->link)
			if(TPTR(*lp) == td)
				break;
		if(*lp & Tdterm)
			panic("cancelisoio: td not found");
		*lp = td->link;
		frno = TRUNC(frno+pollival, Nframes);
	}
	iunlock(ctlr);

	/*
	 * wakeup anyone waiting for I/O and
	 * wait to be sure no I/O is in progress in the controller.
	 * and then wait to be sure episo-io is no longer running.
	 */
	wakeup(iso);
	diprint("cancelisoio iso %#p waiting for I/O to cease\n", iso);
	tsleep(&up->sleep, return0, 0, 5);
	qlock(iso);
	qunlock(iso);
	diprint("cancelisoio iso %#p releasing iso\n", iso);

	frno = iso->td0frno;
	for(i = 0; i < iso->nframes; i++){
		tdfree(iso->tdps[frno]);
		iso->tdps[frno] = nil;
		frno = TRUNC(frno+pollival, Nframes);
	}
	free(iso->data);
	iso->data = nil;
}

static void
epclose(Ep *ep)
{
	Ctlr *ctlr;
	Ctlio *cio;
	Isoio *iso;
	Qio *io;

	ctlr = ep->hp->aux;
	deprint("uhci: epclose ep%d.%d\n", ep->dev->nb, ep->nb);

	if(ep->aux == nil)
		panic("uhci: epclose called with closed ep");
	switch(ep->ttype){
	case Tctl:
		cio = ep->aux;
		cancelio(ctlr, cio);
		free(cio->data);
		cio->data = nil;
		break;
	case Tbulk:
	case Tintr:
		io = ep->aux;
		ep->toggle[OREAD] = ep->toggle[OWRITE] = 0;
		if(ep->mode != OWRITE){
			cancelio(ctlr, &io[OREAD]);
			if(io[OREAD].toggle == Tddata1)
				ep->toggle[OREAD] = 1;
		}
		if(ep->mode != OREAD){
			cancelio(ctlr, &io[OWRITE]);
			if(io[OWRITE].toggle == Tddata1)
				ep->toggle[OWRITE] = 1;
		}
		break;
	case Tiso:
		iso = ep->aux;
		cancelisoio(ctlr, iso, ep->pollival, ep->load);
		break;
	default:
		panic("epclose: bad ttype %d", ep->ttype);
	}

	free(ep->aux);
	ep->aux = nil;

}

static char*
seprintep(char *s, char *e, Ep *ep)
{
	Ctlio *cio;
	Qio *io;
	Isoio *iso;
	Ctlr *ctlr;

	ctlr = ep->hp->aux;
	ilock(ctlr);
	if(ep->aux == nil){
		*s = 0;
		iunlock(ctlr);
		return s;
	}
	switch(ep->ttype){
	case Tctl:
		cio = ep->aux;
		s = seprint(s,e,"cio %#p qh %#p"
			" id %#x tog %#x tok %#x err %s\n",
			cio, cio->qh, cio->usbid, cio->toggle,
			cio->tok, cio->err);
		break;
	case Tbulk:
	case Tintr:
		io = ep->aux;
		if(ep->mode != OWRITE)
			s = seprint(s,e,"r: qh %#p id %#x tog %#x tok %#x err %s\n",
				io[OREAD].qh, io[OREAD].usbid, io[OREAD].toggle,
				io[OREAD].tok, io[OREAD].err);
		if(ep->mode != OREAD)
			s = seprint(s,e,"w: qh %#p id %#x tog %#x tok %#x err %s\n",
				io[OWRITE].qh, io[OWRITE].usbid, io[OWRITE].toggle,
				io[OWRITE].tok, io[OWRITE].err);
		break;
	case Tiso:
		iso = ep->aux;
		s = seprint(s,e,"iso %#p id %#x tok %#x tdu %#p tdi %#p err %s\n",
			iso, iso->usbid, iso->tok, iso->tdu, iso->tdi, iso->err);
		break;
	}
	iunlock(ctlr);
	return s;
}

static int
portenable(Hci *hp, int port, int on)
{
	int s;
	int ioport;
	Ctlr *ctlr;

	ctlr = hp->aux;
	dprint("uhci: %#x port %d enable=%d\n", ctlr->port, port, on);
	ioport = PORT(port-1);
	qlock(&ctlr->portlck);
	if(waserror()){
		qunlock(&ctlr->portlck);
		nexterror();
	}
	ilock(ctlr);
	s = INS(ioport);
	if(on)
		OUTS(ioport, s | PSenable);
	else
		OUTS(ioport, s & ~PSenable);
	microdelay(64);
	iunlock(ctlr);
	tsleep(&up->sleep, return0, 0, Enabledelay);
	dprint("uhci %#ux port %d enable=%d: sts %#x\n",
		ctlr->port, port, on, INS(ioport));
	qunlock(&ctlr->portlck);
	poperror();
	return 0;
}

static int
portreset(Hci *hp, int port, int on)
{
	int i, p;
	Ctlr *ctlr;

	if(on == 0)
		return 0;
	ctlr = hp->aux;
	dprint("uhci: %#ux port %d reset\n", ctlr->port, port);
	p = PORT(port-1);
	ilock(ctlr);
	OUTS(p, PSreset);
	delay(50);
	OUTS(p, INS(p) & ~PSreset);
	OUTS(p, INS(p) | PSenable);
	microdelay(64);
	for(i=0; i<1000 && (INS(p) & PSenable) == 0; i++)
		;
	OUTS(p, (INS(p) & ~PSreset)|PSenable);
	iunlock(ctlr);
	dprint("uhci %#ux after port %d reset: sts %#x\n",
		ctlr->port, port, INS(p));
	return 0;
}

static int
portstatus(Hci *hp, int port)
{
	int s;
	int r;
	int ioport;
	Ctlr *ctlr;

	ctlr = hp->aux;
	ioport = PORT(port-1);
	qlock(&ctlr->portlck);
	if(waserror()){
		iunlock(ctlr);
		qunlock(&ctlr->portlck);
		nexterror();
	}
	ilock(ctlr);
	s = INS(ioport);
	if(s & (PSstatuschg | PSchange)){
		OUTS(ioport, s);
		ddprint("uhci %#ux port %d status %#x\n", ctlr->port, port, s);
	}
	iunlock(ctlr);
	qunlock(&ctlr->portlck);
	poperror();

	/*
	 * We must return status bits as a
	 * get port status hub request would do.
	 */
	r = 0;
	if(s & PSpresent)
		r |= HPpresent;
	if(s & PSenable)
		r |= HPenable;
	if(s & PSsuspend)
		r |= HPsuspend;
	if(s & PSreset)
		r |= HPreset;
	if(s & PSslow)
		r |= HPslow;
	if(s & PSstatuschg)
		r |= HPstatuschg;
	if(s & PSchange)
		r |= HPchange;
	return r;
}

static void
scanpci(void)
{
	static int already = 0;
	int io;
	int i;
	Ctlr *ctlr;
	Pcidev *p;

	if(already)
		return;
	already = 1;
	p = nil;
	while(p = pcimatch(p, 0, 0)){
		/*
		 * Find UHCI controllers (Programming Interface = 0).
		 */
		if(p->ccrb != Pcibcserial || p->ccru != Pciscusb)
			continue;
		switch(p->ccrp){
		case 0:
			io = p->mem[4].bar & ~0x0F;
			break;
		default:
			continue;
		}
		if(io == 0){
			print("usbuhci: %#x %#x: failed to map registers\n",
				p->vid, p->did);
			continue;
		}
		if(ioalloc(io, p->mem[4].size, 0, "usbuhci") < 0){
			print("usbuhci: port %#ux in use\n", io);
			continue;
		}
		if(p->intl == 0xFF || p->intl == 0){
			print("usbuhci: no irq assigned for port %#ux\n", io);
			continue;
		}

		dprint("uhci: %#x %#x: port %#ux size %#x irq %d\n",
			p->vid, p->did, io, p->mem[4].size, p->intl);

		ctlr = malloc(sizeof(Ctlr));
		if (ctlr == nil)
			panic("uhci: out of memory");
		ctlr->pcidev = p;
		ctlr->port = io;
		for(i = 0; i < Nhcis; i++)
			if(ctlrs[i] == nil){
				ctlrs[i] = ctlr;
				break;
			}
		if(i == Nhcis)
			print("uhci: bug: no more controllers\n");
	}
}

static void
uhcimeminit(Ctlr *ctlr)
{
	Td* td;
	Qh *qh;
	int frsize;
	int i;

	ctlr->qhs = ctlr->qh[Tctl] = qhalloc(ctlr, nil, nil, "CTL");
	ctlr->qh[Tintr] = qhalloc(ctlr, ctlr->qh[Tctl], nil, "INT");
	ctlr->qh[Tbulk] = qhalloc(ctlr, ctlr->qh[Tintr], nil, "BLK");

	/* idle Td from dummy Qh at the end. looped back to itself */
	/* This is a workaround for PIIX4 errata 29773804.pdf */
	qh = qhalloc(ctlr, ctlr->qh[Tbulk], nil, "BWS");
	td = tdalloc();
	td->link = PCIWADDR(td);
	qhlinktd(qh, td);

	/* loop (hw only) from the last qh back to control xfers.
	 * this may be done only for some of them. Disable until ehci comes.
	 */
	if(0)
	qh->link = PCIWADDR(ctlr->qhs);

	frsize = Nframes*sizeof(ulong);
	ctlr->frames = xspanalloc(frsize, frsize, 0);
	if(ctlr->frames == nil)
		panic("uhci reset: no memory");

	ctlr->iso = nil;
	for(i = 0; i < Nframes; i++)
		ctlr->frames[i] = PCIWADDR(ctlr->qhs)|QHlinkqh;
	OUTL(Flbaseadd, PCIWADDR(ctlr->frames));
	OUTS(Frnum, 0);
	dprint("uhci %#ux flb %#ulx frno %#ux\n", ctlr->port,
		INL(Flbaseadd), INS(Frnum));
}

static void
init(Hci *hp)
{
	Ctlr *ctlr;
	int sts;
	int i;

	ctlr = hp->aux;
	dprint("uhci %#ux init\n", ctlr->port);
	coherence();
	ilock(ctlr);
	OUTS(Usbintr, Itmout|Iresume|Ioc|Ishort);
	uhcirun(ctlr, 1);
	dprint("uhci: init: cmd %#ux sts %#ux sof %#ux",
		INS(Cmd), INS(Status), INS(SOFmod));
	dprint(" flb %#ulx frno %#ux psc0 %#ux psc1 %#ux",
		INL(Flbaseadd), INS(Frnum), INS(PORT(0)), INS(PORT(1)));
	/* guess other ports */
	for(i = 2; i < 6; i++){
		sts = INS(PORT(i));
		if(sts != 0xFFFF && (sts & PSreserved1) == 1){
			dprint(" psc%d %#ux", i, sts);
			hp->nports++;
		}else
			break;
	}
	for(i = 0; i < hp->nports; i++)
		OUTS(PORT(i), 0);
	iunlock(ctlr);
}

static void
uhcireset(Ctlr *ctlr)
{
	int i;
	int sof;

	ilock(ctlr);
	dprint("uhci %#ux reset\n", ctlr->port);

	/*
	 * Turn off legacy mode. Some controllers won't
	 * interrupt us as expected otherwise.
	 */
	uhcirun(ctlr, 0);
	pcicfgw16(ctlr->pcidev, 0xc0, 0x2000);

	OUTS(Usbintr, 0);
	sof = INB(SOFmod);
	uhcicmd(ctlr, Cgreset);			/* global reset */
	delay(Resetdelay);
	uhcicmd(ctlr, 0);			/* all halt */
	uhcicmd(ctlr, Chcreset);			/* controller reset */
	for(i = 0; i < 100; i++){
		if((INS(Cmd) & Chcreset) == 0)
			break;
		delay(1);
	}
	if(i == 100)
		print("uhci %#x controller reset timed out\n", ctlr->port);
	OUTB(SOFmod, sof);
	iunlock(ctlr);
}

static void
setdebug(Hci*, int d)
{
	debug = d;
}

static void
shutdown(Hci *hp)
{
	Ctlr *ctlr;

	ctlr = hp->aux;

	ilock(ctlr);
	uhcirun(ctlr, 0);
	delay(100);
	iunlock(ctlr);
}

static int
reset(Hci *hp)
{
	static Lock resetlck;
	int i;
	Ctlr *ctlr;
	Pcidev *p;

	if(getconf("*nousbuhci"))
		return -1;

	ilock(&resetlck);
	scanpci();

	/*
	 * Any adapter matches if no hp->port is supplied,
	 * otherwise the ports must match.
	 */
	ctlr = nil;
	for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
		ctlr = ctlrs[i];
		if(ctlr->active == 0)
		if(hp->port == 0 || hp->port == ctlr->port){
			ctlr->active = 1;
			break;
		}
	}
	iunlock(&resetlck);
	if(ctlrs[i] == nil || i == Nhcis)
		return -1;

	p = ctlr->pcidev;
	hp->aux = ctlr;
	hp->port = ctlr->port;
	hp->irq = p->intl;
	hp->tbdf = p->tbdf;
	hp->nports = 2;			/* default */

	uhcireset(ctlr);
	uhcimeminit(ctlr);

	/*
	 * Linkage to the generic HCI driver.
	 */
	hp->init = init;
	hp->dump = dump;
	hp->interrupt = interrupt;
	hp->epopen = epopen;
	hp->epclose = epclose;
	hp->epread = epread;
	hp->epwrite = epwrite;
	hp->seprintep = seprintep;
	hp->portenable = portenable;
	hp->portreset = portreset;
	hp->portstatus = portstatus;
	hp->shutdown = shutdown;
	hp->debug = setdebug;
	hp->type = "uhci";
	return 0;
}

void
usbuhcilink(void)
{
	addhcitype("uhci", reset);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].