Plan 9 from Bell Labs’s /usr/web/sources/patch/applied/usberrleak/usbohci.c

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


/*
 * USB Open Host Controller Interface (OHCI) driver
 * from devohci.c provided by Charles Forsyth, 5 Aug 2006.
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"../port/error.h"

#include	"usb.h"

#define XPRINT	if(usbhdebug) print
#define XIPRINT	if(usbhdebug) iprint
#define XEPRINT	if(usbhdebug || ep->debug) print
#define XEIPRINT if(usbhdebug || ep->debug) iprint

#define	IPRINT(x) iprint x

static int usbhdebug = 0;
static int dcls;

enum {
	Ned = 63 + 32,
	Ntd = 256,
};

/*
 * USB packet definitions
 */
enum {
	Otoksetup = 0,
	Otokout = 1,
	Otokin = 2,

	/* port status - UHCI style */
	Suspend =	1<<12,
	PortReset =	1<<9,
	SlowDevice =	1<<8,
	ResumeDetect =	1<<6,
	PortEnableChange = 1<<3,	/* write 1 to clear */
	PortEnable = 	1<<2,
	ConnectStatusChange = 1<<1,	/* write 1 to clear */
	DevicePresent =	1<<0,
};

typedef struct Ctlr Ctlr;
typedef struct QTree QTree;

enum {
	ED_MPS_MASK = 0x7ff,
	ED_MPS_SHIFT = 16,
	ED_C_MASK  = 1,
	ED_C_SHIFT = 1,
	ED_F_BIT = 1 << 15,
	ED_S_MASK  = 1,
	ED_S_SHIFT = 13,
	ED_D_MASK  = 3,
	ED_D_SHIFT = 11,
	ED_H_MASK  = 1,
	ED_H_SHIFT = 0,
};

typedef struct Endptx Endptx;
typedef struct TD TD;

struct Endptx
{
	Lock;		/* for manipulating ed */
	ED	*ed;	/* Single endpoint descriptor */
	int	ntd;	/* Number of TDs in use */
	int	overruns;
};

struct TD {
	ulong	ctrl;
	ulong	cbp;
	ulong	nexttd;
	ulong	be;
	ushort	offsets[8];	/* Iso TDs only */
	/* driver specific; pad to multiple of 32 */
	TD*	next;
	Endpt	*ep;
	Block	*bp;
	ulong	flags;
	ulong	offset;		/* offset associated with end of data */
	ulong	bytes;		/* bytes in this TD */
	ulong	pad[2];
};

enum {
	TD_R_SHIFT = 18,
	TD_DP_MASK = 3,
	TD_DP_SHIFT = 19,
	TD_CC_MASK = 0xf,
	TD_CC_SHIFT = 28,
	TD_EC_MASK = 3,
	TD_EC_SHIFT = 26,

	TD_FLAGS_LAST = 1 << 0,
};

typedef struct HCCA HCCA;
struct HCCA {
	ulong	intrtable[32];
	ushort	framenumber;
	ushort	pad1;
	ulong	donehead;
	uchar	reserved[116];
};

/* OHCI registers */
typedef struct OHCI OHCI;
struct OHCI {
	/* control and status group */
/*00*/	ulong	revision;
	ulong	control;
	ulong	cmdsts;
	ulong	intrsts;

/*10*/	ulong	intrenable;
	ulong	intrdisable;
	/* memory pointer group */
	ulong	hcca;
	ulong	periodcurred;

/*20*/	ulong	ctlheaded;
	ulong	ctlcurred;
	ulong	bulkheaded;
	ulong	bulkcurred;

/*30*/	ulong	donehead;
	/* frame counter group */
	ulong	fminterval;
	ulong	fmremaining;
	ulong	fmnumber;

/*40*/	ulong	periodicstart;
	ulong	lsthreshold;
	/* root hub group */
	ulong	rhdesca;
	ulong	rhdescb;

/*50*/	ulong	rhsts;
	ulong	rhportsts[15];

/*90*/	ulong	pad25[20];

	/* unknown */
/*e0*/	ulong	hostueaddr;
	ulong	hostuests;
	ulong	hosttimeoutctrl;
	ulong	pad59;

/*f0*/	ulong	pad60;
	ulong	hostrevision;
	ulong	pad62[2];
/*100*/
};

/*
 * software structures
 */

static struct {
	int	bit;
	char	*name;
} portstatus[] = {
	{ Suspend,		"suspend", },
	{ PortReset,		"reset", },
	{ SlowDevice,		"lowspeed", },
	{ ResumeDetect,		"resume", },
	{ PortEnableChange,	"portchange", },
	{ PortEnable,		"enable", },
	{ ConnectStatusChange,	"statuschange", },
	{ DevicePresent,	"present", },
};

struct QTree {
	QLock;
	int	nel;
	int	depth;
	ulong*	bw;
	ED	**root;
};

/* device parameters */
static char *devstates[] = {
	[Disabled]		"Disabled",
	[Attached]		"Attached",
	[Enabled]		"Enabled",
};

struct Ctlr {
	Lock;	/* protects state shared with interrupt (eg, free list) */
	int	active;
	Pcidev*	pcidev;
	int	irq;
	ulong	tbdf;
	Ctlr*	next;
	int	nports;

	OHCI	*base;		/* equiv to io in uhci */
	HCCA	*uchcca;
	int	idgen;		/* version # to distinguish new connections */
	QLock	resetl;		/* lock controller during USB reset */

	struct {
		Lock;
		TD*	pool;
		TD*	free;
		int	alloced;
	} td;

	struct {
		QLock;
		ED*	pool;
		ED*	free;
		int	alloced;
	} ed;

	/* TODO: what happened to ctlq, etc. from uhci? */

	QTree*	tree;		/* tree for t Endpt i/o */

	struct {
		QLock;
		Endpt*	f;
	} activends;
};

enum {
	HcRevision =	0x00,
	HcControl =	0x01,
		HcfsMask =	3 << 6,
		HcfsReset =	0 << 6,
		HcfsResume =	1 << 6,
		HcfsOperational=2 << 6,
		HcfsSuspend =	3 << 6,
		Ble =	1 << 5,
		Cle =	1 << 4,
		Ie =	1 << 3,
		Ple =	1 << 2,
		Cbsr_MASK = 3,
	HcCommandStatus = 0x02,
		Ocr =	1 << 3,
		Blf =	1 << 2,
		Clf =	1 << 1,
		Hcr =	1 << 0,
	HcIntrStatus =	0x03,
	HcIntrEnable =	0x04,
		Mie =	1 << 31,
		Oc =	1 << 30,
		Rhsc =	1 << 6,
		Fno =	1 << 5,
		Ue =	1 << 4,
		Rd =	1 << 3,
		Sf =	1 << 2,
		Wdh =	1 << 1,
		So =	1 << 0,
	HcIntrDisable =	0x05,
	HcFmIntvl =	0x0d,
		HcFmIntvl_FSMaxpack_MASK = 0x7fff,
		HcFmIntvl_FSMaxpack_SHIFT = 16,
	HcFmRemaining =	0x0e,
	HcFmNumber =	0x0f,
	HcLSThreshold =	0x11,
	HcRhDescA =	0x12,
		HcRhDescA_POTPGT_MASK =	0xff << 24,
		HcRhDescA_POTPGT_SHIFT =	24,
	HcRhDescB =	0x13,
	HcRhStatus =	0x14,
		Lps =	1 << 0,
		Cgp =	1 << 0,
		Oci =	1 << 1,
		Drwe =	1 << 15,
		Srwe =	1 << 15,
		LpsC =	1 << 16,
		Sgp =	1 << 16,
		Ccic =	1 << 17,
		Crwe =	1 << 31,
	HcRhPortStatus1 = 0x15,
		Ccs =	1 << 0,
		Cpe =	1 << 0,
		Pes =	1 << 1,
		Spe =	1 << 1,
		Pss =	1 << 2,
		Poci =	1 << 3,
		Prs =	1 << 4,
		Spr =	1 << 4,
		Pps =	1 << 8,
		Spp=	1 << 8,
		Lsda =	1 << 9,
		Cpp =	1 << 9,
		Csc =	1 << 16,
		Pesc =	1 << 17,
		Pssc =	1 << 18,
		Ocic =	1 << 19,
		Prsc =	1 << 20,
	HcRhPortStatus2 =	0x16,

	L2NFRAME =	5,
	NFRAME =	1 << L2NFRAME,
	/* TODO: from UHCI; correct for OHCI? */
	FRAMESIZE = NFRAME*sizeof(ulong), /* fixed by hardware; aligned to same */
};

char *usbmode[] = {
[Ctlmode]=	"Ctl",
[Bulkmode] =	"Bulk",
[Intrmode] =	"Intr",
[Isomode] =	"Iso",
};

static char *ousbmode[] = {
[OREAD] = "r",
[OWRITE] = "w",
[ORDWR] = "rw",
};

int ohciinterrupts[Nmodes];

static Ctlr* ctlrhead;
static Ctlr* ctlrtail;

static	char	Estalled[] = "usb endpoint stalled";
static	char	EnotWritten[] = "usb write unfinished";
static	char	EnotRead[] = "usb read unfinished";
static	char	Eunderrun[] = "usb endpoint underrun";

static	QLock	usbhstate;	/* protects name space state */

static void	eptactivate(Ctlr *ub, Endpt *ep);
static void	eptdeactivate(Ctlr *ub, Endpt *e);
static long	read (Usbhost *, Endpt*, void*, long, vlong);
static void	scanpci(void);
static int	schedendpt(Ctlr *ub, Endpt *ep, int direction);
static void	unschedendpt(Ctlr *ub, Endpt *ep, int);
static long	write(Usbhost *, Endpt*, void*, long, vlong, int);
static long	qtd(Ctlr*, Endpt*, int, Block*, uchar*, uchar*, int, ulong);

void
printdata(void *pdata, int itemsize, int nitems)
{
	int i;
	uchar *p1;
	ushort *p2;
	ulong *p4;

	if(!usbhdebug)
		return;
	p1 = pdata;
	p2 = pdata;
	p4 = pdata;
	i = 0;
	for(;;){
		switch(itemsize){
		default:
			assert(0);
		case 1:
			print("%2.2ux ", *p1++);
			break;
		case 2:
			print("%4.4ux ", *p2++);
			break;
		case 4:
			print("%8.8lux ", *p4++);
			break;
		}
		if(++i >= nitems || (i & ((0x40 >> itemsize) - 1)) == 0){
			print("\n");
			if(i >= nitems)
				break;
		}
	}
}

/*
 * i left these in so that we could use the same
 * driver on several other platforms (in principle).
 * the processor on which it was originally developed
 * had an IO MMU and thus another address space.
 * it's nothing to do with USB as such.
 */
ulong
va2hcva(void *va)
{
	if(va == nil)
		return 0;
	return PADDR(va);
}

void *
hcva2va(ulong hcva)
{
	if(hcva == 0)
		return nil;
	return KADDR(hcva);
}

void *
va2ucva(void *va)
{
	return va;
}

void *
hcva2ucva(ulong hcva)
{
	if(hcva == 0)
		return nil;
	if(hcva & 0xf0000000){
		iprint("hcva2ucva: bad %#lux, called from %#p\n",
			hcva, getcallerpc(&hcva));
		return nil;
	}
	return KADDR(hcva);
}

#define	IOCACHED	0
#define	invalidatedcacheva(va)
#define	dcclean(p, n)

static void
EDinit(ED *ed, int mps, int f, int k, int s, int d, int en, int fa,
	TD *tail, TD *head, int c, int h, ED *next)
{
	/* check nothing is running? */
	ed->ctrl = (mps & ED_MPS_MASK) << ED_MPS_SHIFT
		| (f & 1) << 15
		| (k & 1) << 14
		| (s & ED_S_MASK) << ED_S_SHIFT
		| (d & 3) << 11		/* 00 is obtained from TD (used here) */
		| (en & 0xf) << 7
		| (fa & 0x7f);
	ed->tail =  va2hcva(tail) & ~0xF;
	ed->head = (va2hcva(head) & ~0xF)
		| (c & ED_C_MASK) << ED_C_SHIFT
		| (h & ED_H_MASK) << ED_H_SHIFT;
	ed->next = va2hcva(next) & ~0xF;
}

static void
EDsetS(ED *ed, int s)
{
	XIPRINT("EDsetS: %s speed\n", s == Lowspeed ? "low" : "high");
	if(s == Lowspeed)
		ed->ctrl |= 1 << ED_S_SHIFT;
	else
		ed->ctrl &= ~(1 << ED_S_SHIFT);
}

static void
EDsetMPS(ED *ed, int mps)
{
	ed->ctrl = (ed->ctrl & ~(ED_MPS_MASK << ED_MPS_SHIFT)) |
		(mps & ED_MPS_MASK) << ED_MPS_SHIFT;
}

static void
EDsetC(ED *ed, int c)
{
	ed->head = (ed->head & ~(ED_C_MASK << ED_C_SHIFT)) |
		(c & ED_C_MASK) << ED_C_SHIFT;
}

static void
EDsetH(ED *ed, int h)
{
	ed->head = (ed->head & ~(ED_H_MASK << ED_H_SHIFT)) |
		(h & ED_H_MASK) << ED_H_SHIFT;
}

static int
EDgetH(ED *ed)
{
	return (ed->head >> ED_H_SHIFT) & ED_H_MASK;
}

static int
EDgetC(ED *ed)
{
	return (ed->head >> ED_C_SHIFT) & ED_C_MASK;
}

static void
EDsetnext(ED *ed, void *va)
{
	ed->next = va2hcva(va) & ~0xF;
}

static ED *
EDgetnext(ED *ed)
{
	return hcva2ucva(ed->next & ~0xF);
}

static void
EDsettail(ED *ed, void *va)
{
	ed->tail = va2hcva(va) & ~0xF;
}

static TD *
EDgettail(ED *ed)
{
	return hcva2ucva(ed->tail & ~0xF);
}

static void
EDsethead(ED *ed, void *va)
{
	ed->head = (ed->head & 0xf) | (va2hcva(va) & ~0xF);
}

static TD *
EDgethead(ED *ed)
{
	return hcva2ucva(ed->head & ~0xF);
}

static ED *
EDalloc(Ctlr *ub)
{
	ED *t;

	qlock(&ub->ed);
	t = ub->ed.free;
	if(t == nil){
		qunlock(&ub->ed);
		return nil;
	}
	ub->ed.free = (ED *)t->next;
	ub->ed.alloced++;
	if (0)
		print("%d endpoints allocated\n", ub->ed.alloced);
	qunlock(&ub->ed);
	t->next = 0;
	return t;
}

void
TDsetnexttd(TD *td, TD *va)
{
	td->nexttd = va2hcva(va) & ~0xF;
}

TD *
TDgetnexttd(TD *td)
{
	return hcva2ucva(td->nexttd & ~0xF);
}

void
OHCIsetControlHeadED(OHCI *ohci, ED *va)
{
	ohci->ctlheaded = va2hcva(va) & ~0xF;
}

ED *
OHCIgetControlHeadED(OHCI *ohci)
{
	return hcva2ucva(ohci->ctlheaded);
}

void
OHCIsetBulkHeadED(OHCI *ohci, ED *va)
{
	ohci->bulkheaded = va2hcva(va) & ~0xF;
}

ED *
OHCIgetBulkHeadED(OHCI *ohci)
{
	return hcva2ucva(ohci->bulkheaded);
}

static TD *
TDalloc(Ctlr *ub, Endpt *ep, int musthave)		/* alloctd */
{
	TD *t;
	Endptx *epx;

	for(;;){
		ilock(ub);
		t = ub->td.free;
		if(t)
			break;
		iunlock(ub);
		if(up == nil){
			if(musthave)
				panic("TDalloc: out of descs");
			return nil;
		}
		tsleep(&up->sleep, return0, 0, 100);
	}

	ub->td.free = t->next;
	epx = ep->private;
	epx->ntd++;
	ub->td.alloced++;
	iunlock(ub);
	memset(t, 0, sizeof(TD));
	t->ep = ep;
	return t;
}

/* call under ilock */
static void
TDfree(Ctlr *ub, TD *t)					/* freetd */
{
	Endptx *epx;

	if(t == 0)
		return;

	if(t->ep){
		epx = t->ep->private;
		epx->ntd--;
	} else
		t->ep = nil;			/* redundant? */
	t->bp = nil;
	t->next = ub->td.free;
	ub->td.free = t;
	ub->td.alloced--;
}

static void
EDfree(Ctlr *ub, ED *t)
{
	TD *td, *next;

	if(t == 0)
		return;
	qlock(&ub->ed);
	t->next = (ulong)ub->ed.free;
	ub->ed.free = t;
	ub->ed.alloced--;
	if (0)
		print("%d endpoints allocated\n", ub->ed.alloced);
	ilock(ub);
	for(td = EDgethead(t); td; td = next){
		next = TDgetnexttd(td);
		TDfree(ub, td);
	}
	iunlock(ub);
	EDsethead(t, 0);
	EDsettail(t, 0);
	qunlock(&ub->ed);
}

static void
waitSOF(Ctlr *ub)
{
	/*
	 * wait for SOF - interlock with interrupt handler so
	 * done queue processed first.
	 */
	int frame = ub->uchcca->framenumber & 0x3f;

	do {
		delay(2);
	} while(frame == (ub->uchcca->framenumber & 0x3f));
}

static void
dumptd(TD *td, char *s)
{
	int i;
	Endpt *ep;

	ep = td->ep;
	print("\t%s: %#p ctrl %#.8lux cbp %#.8lux "
		"nexttd %#.8lux be %#.8lux, flags %#lux\n",
		s, td, td->ctrl, td->cbp, td->nexttd, td->be, td->flags);
	if(ep->epmode != Isomode){
		print("\t\tbytes: %ld\n", td->be + 1 - td->cbp);
		return;
	}
	print("\t\t%#ux %#ux %#ux %#ux %#ux %#ux %#ux %#ux\n",
		td->offsets[0], td->offsets[1], td->offsets[2], td->offsets[3],
		td->offsets[4], td->offsets[5], td->offsets[6], td->offsets[7]);
	print("\t\tbytes:");
	for(i = 0; i < td->ctrl >> 24 & 0x7; i++)
		print(" %d", (td->offsets[i+1]-td->offsets[i])&0xfff);
	print(" %ld\n", (td->be + 1 - td->offsets[i]) & 0xfff);
}

static void
dumped(ED *ed)
{
	TD *tailp, *td;

	tailp = EDgettail(ed);
	td = EDgethead(ed);
	print("dumpED %#p: ctrl %#lux tail %#lux head %#lux next %#lux\n",
		ed, ed->ctrl, ed->tail, ed->head, ed->next);
	if(tailp == td)
		return;
	do {
		dumptd(td, "td");
	} while((td = TDgetnexttd(td)) != tailp);
}

static void
dumpstatus(Ctlr *ub)
{
	ED *ed;

	print("dumpstatus %#p, frame %#ux:\n", ub, ub->uchcca->framenumber);
	print("control %#lux, cmdstat %#lux, intrsts %#lux\n",
		ub->base->control, ub->base->cmdsts, ub->base->intrsts);
	print("Control:\n");
	for(ed = OHCIgetControlHeadED(ub->base); ed; ed = EDgetnext(ed))
		dumped(ed);
	print("Bulk:\n");
	for(ed = OHCIgetBulkHeadED(ub->base); ed; ed = EDgetnext(ed))
		dumped(ed);
	print("Iso:\n");
	for(ed = ub->tree->root[0]; ed; ed = EDgetnext(ed))
		dumped(ed);
	print("frame %#ux:\n", ub->uchcca->framenumber);
}

/*
 * halt the ED and free input or output transfer descs
 * called when the relevant lock in the enclosing Endpt is held
 */

static void
EDcancel(Ctlr *ub, ED *ed, int dirin)
{
	int tddir, iso, n;
	TD *tailp, *headp, *td, *prev, *next;
	Endpt *ep;

	if(ed == nil)
		return;

	/* halt ED if not already halted */
	if(EDgetH(ed) != 1){
		EDsetH(ed, 1);
		waitSOF(ub);
	}

	SET(tddir);
	if((iso = ed->ctrl & ED_F_BIT) != 0)
		switch((ed->ctrl >> 11) & 0x3){
		default:
			panic("ED iso direction unset");
		case Otokin:	tddir = Dirin;	break;
		case Otokout:	tddir = Dirout;	break;
		}

	/* can now clean up TD list of ED */
	tailp = EDgettail(ed);
	headp = EDgethead(ed);
	n = 0;
	prev = nil;
	td = headp;
	while(td != tailp){
		ep = td->ep;
		if(iso == 0)
			switch((td->ctrl >> TD_DP_SHIFT) & TD_DP_MASK){
			default:
				panic("TD direction unset");
			case Otoksetup:	tddir = Dirout;	break;
			case Otokin:	tddir = Dirin;	break;
			case Otokout:	tddir = Dirout;	break;
			}
		else if(usbhdebug || ep->debug)
			print("EDcancel: buffered: %d, bytes %ld\n",
				ep->buffered, td->bytes);
		next = TDgetnexttd(td);
		if(dirin == 2 || dirin == tddir){
			XEPRINT("%d/%d: EDcancel %d\n", ep->dev->x, ep->x, tddir);
			/* Remove this sucker */
			ep->buffered -= td->bytes;
			if(ep->buffered < 0)
				ep->buffered = 0;
			ilock(ub);
			ep->dir[tddir].queued--;
			if(tddir == Dirout){
				freeb(td->bp);
				td->bp = nil;
			}
			if(prev)
				TDsetnexttd(prev, next);
			else
				EDsethead(ed, next);
			TDfree(ub, td);
			n++;
			iunlock(ub);
		}else{
			XEPRINT("%d/%d: EDcancel skip %d\n", ep->dev->x, ep->x,
				tddir);
			prev = td;
		}
		td = next;
	}
	XPRINT("EDcancel: %d\n", n);
}

static void
eptactivate(Ctlr *ub, Endpt *ep)
{
	Endptx *epx;

	qlock(&ub->activends);
	if(ep->active == 0){
		epx = ep->private;
		XEPRINT("%d/%d: activate\n", ep->dev->x, ep->x);
		ep->active = 1;
		/*
		 * set the right speed
		 */
		EDsetS(epx->ed, ep->dev->speed);
		switch(ep->epmode){
		case Ctlmode:
			/*
			 * chain the two descs together, and
			 * bind to beginning of control queue
			 */
			EDsetnext(epx->ed, OHCIgetControlHeadED(ub->base));
			OHCIsetControlHeadED(ub->base, epx->ed);
			/*
			 * prompt controller to absorb new queue on next pass
			 */
			ub->base->cmdsts |= Clf;
			XEPRINT("%d/%d: activated in control queue\n",
				ep->dev->x, ep->x);
			break;
		case Bulkmode:
			EDsetnext(epx->ed, OHCIgetBulkHeadED(ub->base));
			OHCIsetBulkHeadED(ub->base, epx->ed);
			ub->base->cmdsts |= Blf;
			XEPRINT("%d/%d: activated %s in bulk input queue\n",
				ep->dev->x, ep->x, ousbmode[ep->mode]);
			break;
		case Isomode:
			if(ep->mode != OWRITE)
				schedendpt(ub, ep, Dirin);
			if(ep->mode != OREAD)
				schedendpt(ub, ep, Dirout);
			ep->buffered = 0;
			ep->partial = 0;
			break;
		case Intrmode:
			if(ep->mode != OWRITE)
				schedendpt(ub, ep, Dirin);
			if(ep->mode != OREAD)
				schedendpt(ub, ep, Dirout);
			break;
		case Nomode:
			break;
		default:
			panic("eptactivate: wierd epmode %d\n", ep->epmode);
		}
		ep->dir[Dirin].xdone  = ep->dir[Dirin].xstarted =  0;
		ep->dir[Dirout].xdone = ep->dir[Dirout].xstarted = 0;
		ep->activef = ub->activends.f;
		ub->activends.f = ep;
	}
	qunlock(&ub->activends);
}

static void
EDpullfrombulk(Ctlr *ub, ED *ed)
{
	ED *this, *prev, *next;

	this = OHCIgetBulkHeadED(ub->base);
	ub->base->bulkcurred = 0;
	prev = nil;
	while(this != nil && this != ed){
		prev = this;
		this = EDgetnext(this);
	}
	if(this == nil){
		print("EDpullfrombulk: not found\n");
		return;
	}
	next = EDgetnext(this);
	if(prev == nil)
		OHCIsetBulkHeadED(ub->base, next);
	else
		EDsetnext(prev, next);
	EDsetnext(ed, nil);		/* wipe out next field */
}

static void
EDpullfromctl(Ctlr *ub, ED *ed)
{
	ED *this, *prev, *next;

	this = OHCIgetControlHeadED(ub->base);
	ub->base->ctlcurred = 0;
	prev = nil;
	while(this != nil && this != ed){
		prev = this;
		this = EDgetnext(this);
	}
	if(this == nil)
		panic("EDpullfromctl: not found\n");
	next = EDgetnext(this);
	if(prev == nil)
		OHCIsetControlHeadED(ub->base, next);
	else
		EDsetnext(prev, next);
	EDsetnext(ed, nil);		/* wipe out next field */
}

static void
eptdeactivate(Ctlr *ub, Endpt *ep)
{
	ulong ctrl;
	Endpt **l;
	Endptx *epx;

	/* could be O(1) but not worth it yet */
	qlock(&ub->activends);
	if(ep->active){
		epx = ep->private;
		XEPRINT("ohci: eptdeactivate %d/%d\n", ep->dev->x, ep->x);
		ep->active = 0;
		for(l = &ub->activends.f; *l != ep; l = &(*l)->activef)
			if(*l == nil){
				qunlock(&ub->activends);
				panic("usb eptdeactivate");
			}
		*l = ep->activef;
		/* pull it from the appropriate queue */
		ctrl = ub->base->control;
		switch(ep->epmode){
		case Ctlmode:
			if(ctrl & Cle){
				ub->base->control &= ~Cle;
				waitSOF(ub);
			}
			EDpullfromctl(ub, epx->ed);
			if(ctrl & Cle){
				ub->base->control |= Cle;
				/*
				 * don't fill it if there is nothing in it -
				 * shouldn't be necessary according to the
				 * spec., but practice is different
				 */
				if(OHCIgetControlHeadED(ub->base))
					ub->base->cmdsts |= Clf;
			}
			break;
		case Bulkmode:
			if(ctrl & Ble){
				ub->base->control &= ~Ble;
				waitSOF(ub);
			}
			EDpullfrombulk(ub, epx->ed);
			if(ctrl & Ble){
				ub->base->control |= Ble;
				/*
				 * don't fill it if there is nothing in it -
				 * shouldn't be necessary according to the
				 * spec., but practice is different
				 */
				if(OHCIgetBulkHeadED(ub->base))
					ub->base->cmdsts |= Blf;
			}
			break;
		case Intrmode:
		case Isomode:
			if(ep->mode != OWRITE)
				unschedendpt(ub, ep, Dirin);
			if(ep->mode != OREAD)
				unschedendpt(ub, ep, Dirout);
			waitSOF(ub);
			break;
		case Nomode:
			break;
		default:
			panic("eptdeactivate: wierd in.epmode %d\n",
				ep->epmode);
		}
	}
	qunlock(&ub->activends);
}

static void
kickappropriatequeue(Ctlr *ub, Endpt *ep, int)
{
	switch(ep->epmode){
	case Nomode:
		break;
	case Ctlmode:
		ub->base->cmdsts |= Clf;
		break;
	case Bulkmode:
		ub->base->cmdsts |= Blf;
		break;
	case Intrmode:
	case Isomode:
		/* no kicking required */
		break;
	default:
		panic("wierd epmode %d\n", ep->epmode);
	}
}

static void
eptenable(Ctlr *ub, Endpt *ep, int dirin)
{
	ED *ed;
	Endptx *epx;

	epx = ep->private;
	ed = epx->ed;
	if(EDgetH(ed) == 1){
		EDsetH(ed, 0);
		kickappropriatequeue(ub, ep, dirin);
		if(ep->epmode == Isomode || ep->epmode == Intrmode)
			waitSOF(ub);
	}
}

/*
 * return smallest power of 2 >= n
 */
static int
flog2(int n)
{
	int i;

	for(i = 0; (1 << i) < n; i++)
		;
	return i;
}

/*
 * return smallest power of 2 <= n
 */
static int
flog2lower(int n)
{
	int i;

	for(i = 0; (1 << (i + 1)) <= n; i++)
		;
	return i;
}

static int
pickschedq(QTree *qt, int pollms, ulong bw, ulong limit)
{
	int i, j, d, upperb, q;
	ulong best, worst, total;

	d = flog2lower(pollms);
	if(d > qt->depth)
		d = qt->depth;
	q = -1;
	worst = 0;
	best = ~0;
	upperb = (1 << (d+1)) - 1;
	for(i = (1 << d) - 1; i < upperb; i++){
		total = qt->bw[0];
		for(j = i; j > 0; j = (j - 1) / 2)
			total += qt->bw[j];
		if(total < best){
			best = total;
			q = i;
		}
		if(total > worst)
			worst = total;
	}
	if(worst + bw >= limit)
		return -1;
	return q;
}

static int
schedendpt(Ctlr *ub, Endpt *ep, int dirin)
{
	int q;
	ED *ed;
	Endptx *epx;

	epx = ep->private;
	qlock(ub->tree);
	/* TO DO: bus bandwidth limit */
	q = pickschedq(ub->tree, ep->pollms, ep->bw, ~0);
	XEPRINT("schedendpt, dir %d Q index %d, ms %d, bw %ld\n",
		dirin, q, ep->pollms, ep->bw);
	if(q < 0){
		qunlock(ub->tree);
		return -1;
	}
	ub->tree->bw[q] += ep->bw;
	ed = ub->tree->root[q];
	ep->sched = q;
	EDsetnext(epx->ed, EDgetnext(ed));
	EDsetnext(ed, epx->ed);
	XEPRINT("%d/%d: sched on q %d pollms %d\n",
		ep->dev->x, ep->x, q, ep->pollms);
	qunlock(ub->tree);
	return 0;
}

static void
unschedendpt(Ctlr *ub, Endpt *ep, int dirin)
{
	int q;
	ED *prev, *this, *next;
	Endptx *epx;

	epx = ep->private;
	if((q = ep->sched) < 0)
		return;
	qlock(ub->tree);
	ub->tree->bw[q] -= ep->bw;

	prev = ub->tree->root[q];
	this = EDgetnext(prev);
	while(this != nil && this != epx->ed){
		prev = this;
		this = EDgetnext(this);
	}
	if(this == nil)
		print("unschedendpt %d %d: not found\n", dirin, q);
	else{
		next = EDgetnext(this);
		EDsetnext(prev, next);
	}
	qunlock(ub->tree);
}

/* at entry, *e is partly populated */
static void
epalloc(Usbhost *uh, Endpt *ep)
{
	int id;
	Endptx *epx;
	Ctlr *ctlr;
	Udev *d;
	TD *dtd;

	XEPRINT("ohci: epalloc from devusb\n");
	ctlr = uh->ctlr;
	id = ep->id;
	d = ep->dev;

	epx = malloc(sizeof(Endptx));
	memset(epx, 0, sizeof(Endptx));
	ep->private = epx;

	dtd = nil;
	if(waserror()){
		XEPRINT("ohci: epalloc error\n");
		EDfree(ctlr, epx->ed);
		epx->ed = nil;
		TDfree(ctlr, dtd);
		nexterror();
	}
	if(epx->ed)
		error("usb: already allocated");
	if((epx->ed = EDalloc(ctlr)) == nil)
		error(Enomem);
	ep->bw = 1;			/* all looks the same currently */
	if((dtd = TDalloc(ctlr, ep, 0)) == nil)
		error(Enomem);
	EDinit(epx->ed, ep->maxpkt, 0, 0, 0, 0, id, d->id, dtd, dtd, 0, 0, 0);
	XEPRINT("ohci: epalloc done\n");
	poperror();
}

static void
epfree(Usbhost *uh, Endpt *ep)
{
	Endptx *epx;
	Ctlr *ctlr;

	epx = ep->private;
	ctlr = uh->ctlr;
	XEPRINT("ohci: epfree %d/%d from devusb\n", ep->dev->x, ep->x);

	if(ep->active)
		panic("epfree: active");
	EDfree(ctlr, epx->ed);
	epx->ed = nil;
	free(epx);
	ep->private = nil;
}

static void
epopen(Usbhost *uh, Endpt *ep)
{
	Ctlr *ctlr;

	XEPRINT("ohci: epopen %d/%d from devusb\n", ep->dev->x, ep->x);
	ctlr = uh->ctlr;
	if((ep->epmode == Isomode || ep->epmode == Intrmode) && ep->active)
		error("already open");
	eptactivate(ctlr, ep);
}

static int
setfrnum(Ctlr *ub, Endpt *ep)
{
	short frnum, d;
	static int adj;

	/* adjust frnum as necessary... */
	frnum = ub->base->fmnumber + (ep->buffered*1000)/ep->bw;
	d = frnum - ep->frnum;
	if(d < -100 || d > 100){
		/* We'd play in the past */
		if(0 && d > 1000)
			/* We're more than a second off: */
			print("d %d, done %d, started %d, buffered %d\n", d,
				ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted,
				ep->buffered);
		if(ep->dir[Dirout].xdone == ep->dir[Dirout].xstarted)
			ep->buffered = adj = 0;
		if(0 && (adj++ & 0xff) == 0)
			print("adj %d %d\n", d, ep->buffered);
		ep->frnum = ub->base->fmnumber + 10 + (ep->buffered*1000)/ep->bw;
		ep->partial = 0;
		return 1;
	}
	return 0;
}

static int
ceptdone(void *arg)
{
	Endpt *ep;

	ep = arg;
	return ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted >= 0
		|| ep->dir[Dirout].err;
}

static void
epclose(Usbhost *uh, Endpt *ep)
{
	Ctlr *ctlr;
	int xdone, part;
	Endptx *epx;

	XEPRINT("ohci: epclose %d/%d from devusb, %d buffered\n",
		ep->dev->x, ep->x, ep->buffered);
	ctlr = uh->ctlr;
	epx = ep->private;
	if(ep->epmode == Isomode && ep->active){
		qlock(&ep->wlock);
		if(ep->partial && setfrnum(ctlr, ep) == 0){
			part = ep->partial;
			memset(ep->bpartial->wp, 0, ep->maxpkt - ep->partial);
			ep->bpartial->wp = ep->bpartial->rp + ep->maxpkt;
			qtd(uh->ctlr, ep, Dirout, nil, ep->bpartial->rp,
				ep->bpartial->wp, Otokout, TD_FLAGS_LAST);
			XEPRINT("epclose: wrote partial block %d\n", part);
			ep->partial = 0;
		}
		qunlock(&ep->wlock);
		XEPRINT("epclose: wait for outstanding TDs, xdone %d"
			", xstarted %d, buffered %d, queued %d\n",
			ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted,
			ep->buffered, ep->dir[Dirout].queued);
		while(ep->dir[Dirout].err == nil
		&& (xdone = ep->dir[Dirout].xdone) - ep->dir[Dirout].xstarted < 0){
			tsleep(&ep->dir[Dirout].rend, ceptdone, ep, 500);
			if(xdone == ep->dir[Dirout].xdone){
				print("no progress\n");
				break;
			}
		}
		if(ep->dir[Dirout].err)
			XEPRINT("error: %s\n", ep->dir[Dirout].err);
		if(ep->buffered)
			XEPRINT("epclose: done waiting, xdone %d, xstarted %d, "
				"buffered %d, queued %d\n",
				ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted,
				ep->buffered, ep->dir[Dirout].queued);
	}
	lock(epx);
	EDcancel(ctlr, epx->ed, 2);
	unlock(epx);
	if(ep->epmode == Isomode && ep->buffered)
		XEPRINT("epclose: after cancelling, xdone %d, xstarted %d"
			", buffered %d, queued %d\n",
			ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted,
			ep->buffered, ep->dir[Dirout].queued);
	eptdeactivate(ctlr, ep);
}

static void
epmaxpkt(Usbhost *, Endpt *ep)
{
	Endptx *epx;

	epx = ep->private;
	XEPRINT("ohci: epmaxpkt %d/%d: %d\n",
		ep->dev->x, ep->x, ep->maxpkt);
	EDsetMPS(epx->ed, ep->maxpkt);
}

static void
epmode(Usbhost *uh, Endpt *ep)
{
	int tok, reactivate;
	Ctlr *ctlr;
	Endptx *epx;

	epx = ep->private;
	ctlr = uh->ctlr;
	XEPRINT("ohci: epmode %d/%d %s → %s\n",
		ep->dev->x, ep->x, usbmode[ep->epmode], usbmode[ep->epnewmode]);
	reactivate = 0;
	if(ep->epnewmode != ep->epmode)
		if(reactivate = ep->active){
			XEPRINT("ohci: epmode %d/%d: already open\n",
				ep->dev->x, ep->x);
			eptdeactivate(ctlr, ep);
		}
	EDsetS(epx->ed, ep->dev->speed);
	switch(ep->epnewmode){
	default:
		panic("devusb is sick");
	case Intrmode:
//		ep->debug++;
		ep->bw = ep->maxpkt*1000/ep->pollms;	/* bytes/sec */
		XEPRINT("ohci: epmode %d/%d %s, intr: maxpkt %d, pollms %d, bw %ld\n",
			ep->dev->x, ep->x, ousbmode[ep->mode],
			ep->maxpkt, ep->pollms, ep->bw);
		break;
	case Isomode:
//		ep->debug++;
		ep->rem = 999;
		switch(ep->mode){
		default:
			panic("ep mode");
		case ORDWR:
			error("iso unidirectional only");
		case OREAD:
			tok = Otokin;
			error("iso read not implemented");
			break;
		case OWRITE:
			tok = Otokout;
			break;
		}
		XEPRINT("ohci: epmode %d/%d %s, iso: maxpkt %d, pollms %d, hz %d, samp %d\n",
			ep->dev->x, ep->x, ousbmode[ep->mode],
			ep->maxpkt, ep->pollms, ep->hz, ep->samplesz);
		ep->bw = ep->hz * ep->samplesz;		/* bytes/sec */
		/* Use Iso TDs: */
		epx->ed->ctrl |= ED_F_BIT;
		/* Set direction in ED, no room in an Iso TD for this */
		epx->ed->ctrl &= ~(ED_D_MASK << ED_D_SHIFT);
		epx->ed->ctrl |= tok << ED_D_SHIFT;
		break;
	case Bulkmode:
//		ep->debug++;
		/*
		 * Each Bulk device gets a queue head hanging off the
		 * bulk queue head
		 */
		break;
	case Ctlmode:
		break;
	}
	ep->epmode = ep->epnewmode;
	epmaxpkt(uh, ep);
	if(reactivate){
		XEPRINT("ohci: epmode %d/%d: reactivate\n", ep->dev->x, ep->x);
		eptactivate(ctlr, ep);
	}
}

static long
qtd(Ctlr *ub, Endpt *ep, int dirin , Block *bp, uchar *base, uchar *limit,
	int pid, ulong flags)
{
	int fc, mps;
	ulong x;
	uchar *p;
	ED *ed;
	Endptx *epx;
	TD *dummytd, *td;

	epx = ep->private;
	ed = epx->ed;
	td = hcva2ucva(ed->tail);
	td->flags = flags;
	if(ep->epmode == Isomode){
		x = va2hcva(base);
		td->cbp = x & ~0xfff;
		x &= 0xfff;
		p = base;
		setfrnum(ub, ep);
		td->ctrl = ep->frnum & 0xffff;
		fc = 0;
		for(;;){
			/* Calculate number of samples in next packet */
			mps = (ep->hz + ep->rem)/1000;
			/* rem is the partial sample left over */
			ep->rem += ep->hz - 1000*mps;
			mps *= ep->samplesz;
			if(mps > ep->maxpkt)
				panic("Packet size");
			if(ep->partial == 0 && mps > limit - p){
				/* Save this data for later ... */
				ep->partial = limit - p;
				if(fc-- == 0)
					return p - base;	/* No TD */
				/* We do have a TD, send this one off normally */
				td->flags |= TD_FLAGS_LAST;
				break;
			}else if(mps >= limit - p){
				td->flags |= TD_FLAGS_LAST;
				mps = limit - p;
				ep->partial = 0;
			}
			td->offsets[fc] = 0xe000 | x;
			x += mps;
			p += mps;
			ep->frnum++;
			if(fc == 7 || limit - p == 0)
				break;
			fc++;
		}
		td->ctrl |= fc << 24;
	}else{
		td->cbp = va2hcva(base);
		td->ctrl = (pid & TD_DP_MASK) << TD_DP_SHIFT;
		p = base;
		mps = 0x2000 - ((ulong)p & 0xfff);
		if(mps > ep->maxpkt)
			mps = ep->maxpkt;
		if(mps >= limit - p){
			mps = limit - base;
			td->flags |= TD_FLAGS_LAST;
		}
		p += mps;
	}
	td->be = va2hcva(p == nil? nil: p - 1);
	td->ep = ep;
	td->bytes = p - base;
	ep->buffered += td->bytes;
	td->bp = bp;
	if(td->flags & TD_FLAGS_LAST)
		ep->dir[dirin].xstarted++;
	if(dirin == Dirout && bp)
		_xinc(&bp->ref);
	dummytd = TDalloc(ub, ep, 1);
	TDsetnexttd(td, dummytd);
	ep->dir[dirin].queued++;
	EDsettail(ed, dummytd);
	if(usbhdebug || ep->debug)
		dumptd(td, "qtd: before");
	kickappropriatequeue(ub, ep, dirin);
	return p - base;
}

Block*
allocrcvb(long size)
{
	Block *b;
	int asize;

	asize = ROUND(size, dcls) + dcls - 1;
	/*
	 * allocate enough to align rp to dcls, and have an integral number
	 * of cache lines in the buffer
	 */
	while(waserror())
		tsleep(&up->sleep, return0, 0, 100);
	b = allocb(asize);
	poperror();
	/*
	 * align the rp and wp
	 */
	b->rp = b->wp = (uchar *)ROUND((ulong)b->rp, dcls);
	/*
	 * invalidate the cache lines which enclose the buffer
	 */
	if(IOCACHED){
		uchar *p;

		p = b->rp;
		while(size > 0){
			invalidatedcacheva((ulong)p);
			p += dcls;
			size -= dcls;
		}
	}
	return b;
}

/*
 * build the periodic scheduling tree:
 * framesize must be a multiple of the tree size
 */
static QTree *
mkqhtree(Ctlr *ub)
{
	int i, n, d, o, leaf0, depth;
	ED **tree;
	QTree *qt;

	depth = flog2(32);
	n = (1 << (depth+1)) - 1;
	qt = mallocz(sizeof(*qt), 1);
	if(qt == nil)
		return nil;
	qt->nel = n;
	qt->depth = depth;
	qt->bw = mallocz(n * sizeof(qt->bw), 1);
	if(qt->bw == nil){
		free(qt);
		return nil;
	}
	tree = mallocz(n * sizeof(ED *), 1);
	if(tree == nil){
		free(qt->bw);
		free(qt);
		return nil;
	}
	for(i = 0; i < n; i++)
		if((tree[i] = EDalloc(ub)) == nil)
			break;

	if(i < n){
		int j;

		for(j = 0; j < i; j++)
			EDfree(ub, tree[j]);
		free(tree);
		free(qt->bw);
		free(qt);
		return nil;
	}

	qt->root = tree;
	EDinit(qt->root[0], 8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);

	for(i = 1; i < n; i++)
		EDinit(tree[i], 8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, tree[(i-1)/2]);

	/* distribute leaves evenly round the frame list */
	leaf0 = n / 2;
	for(i = 0; i < 32; i++){
		o = 0;
		for(d = 0; d < depth; d++){
			o <<= 1;
			if(i & (1 << d))
				o |= 1;
		}
		if(leaf0 + o >= n){
			print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n);
			break;
		}
		ub->uchcca->intrtable[i] = va2hcva(tree[leaf0 + o]);
	}
	return qt;
}

static void
portreset(Usbhost *uh, int port)
{
	Ctlr *ctlr;

	XIPRINT("ohci: portreset(port %d) from devusb\n", port);
	ctlr = uh->ctlr;
	/* should check that device not being configured on other port? */
	qlock(&ctlr->resetl);
	if(waserror()){
		qunlock(&ctlr->resetl);
		nexterror();
	}
	ilock(ctlr);
	ctlr->base->rhportsts[port - 1] = Spp | Spr;
	while((ctlr->base->rhportsts[port - 1] & Prsc) == 0){
		iunlock(ctlr);
		XIPRINT("ohci: portreset, wait for reset complete\n");
		ilock(ctlr);
	}
	ctlr->base->rhportsts[port - 1] = Prsc;
	iunlock(ctlr);
	poperror();
	qunlock(&ctlr->resetl);
}

static void
portenable(Usbhost *uh, int port, int on)
{
	Ctlr *ctlr;

	XIPRINT("ohci: portenable(port %d, on %d) from devusb\n", port, on);
	ctlr = uh->ctlr;
	/* should check that device not being configured on other port? */
	qlock(&ctlr->resetl);
	if(waserror()){
		qunlock(&ctlr->resetl);
		nexterror();
	}
	ilock(ctlr);
	if(on)
		ctlr->base->rhportsts[port - 1] = Spe | Spp;
	else
		ctlr->base->rhportsts[port - 1] = Cpe;
	iunlock(ctlr);
	poperror();
	qunlock(&ctlr->resetl);
}

static int
getportstatus(Ctlr *ub, int port)
{
	int v;
	ulong ohcistatus;

	ohcistatus = ub->base->rhportsts[port - 1];
	v = 0;
	if(ohcistatus & Ccs)
		v |= DevicePresent;
	if(ohcistatus & Pes)
		v |= PortEnable;
	if(ohcistatus & Pss)
		v |= Suspend;
	if(ohcistatus & Prs)
		v |= PortReset;
	else {
		/* port is not in reset; these potential writes are ok */
		if(ohcistatus & Csc){
			/* TODO: could notify usbd equivalent here */
			v |= ConnectStatusChange;
			ub->base->rhportsts[port - 1] = Csc;
		}
		if(ohcistatus & Pesc){
			/* TODO: could notify usbd equivalent here */
			v |= PortEnableChange;
			ub->base->rhportsts[port - 1] = Pesc;
		}
	}
	if(ohcistatus & Lsda)
		v |= SlowDevice;
	if(ohcistatus & Ccs)
		XIPRINT("portstatus(%d) = OHCI %#.8lux UHCI %#.8ux\n",
			port, ohcistatus, v);
	return v;
}

/* print any interesting stuff here for debugging purposes */
static void
usbdebug(Usbhost *uh, char *s, char *se)
{
	Udev *dev;
	Endpt *ep;
	int n, i, j;

	n = 0;
	for(i = 0; i < MaxUsbDev; i++)
		if(uh->dev[i])
			n++;
	s = seprint(s, se, "OHCI, %d devices\n", n);
	for(i = 0; i < MaxUsbDev; i++){
		if((dev = uh->dev[i]) == nil)
			continue;
		s = seprint(s, se, "dev %#6.6lux, %d epts\n", dev->csp, dev->npt);
		for(j = 0; j < nelem(dev->ep); j++){
			if((ep = dev->ep[j]) == nil)
				continue;
			if(ep->epmode >= 0 && ep->epmode < Nmodes)
				s = seprint(s, se, "ept %d/%d: %s %#6.6lux "
					"maxpkt %d %s\n",
					dev->x, ep->x, usbmode[ep->epmode],
					ep->csp, ep->maxpkt, ep->active ? "active" : "idle");
			else{
				s = seprint(s, se, "ept %d/%d: bad mode %#6.6lux\n",
					dev->x, ep->x, ep->csp);
				continue;
			}
			
			switch(ep->epmode){
			case Nomode:
				break;
			case Ctlmode:
				break;
			case Bulkmode:
				break;
			case Intrmode:
				s = seprint(s, se, "\t%d ms\n",
					ep->pollms);
				break;
			case Isomode:
				s = seprint(s, se, "\t%d ms, remain %d, "
					"partial %d, buffered %d, "
					"xdone %d, xstarted %d, err %s\n",
					ep->pollms, ep->remain, ep->partial,
					ep->buffered,
					ep->dir[ep->mode == OREAD].xdone,
					ep->dir[ep->mode == OREAD].xstarted,
					ep->dir[ep->mode == OREAD].err
					? ep->dir[ep->mode == OREAD].err : "no");
				break;
			}
		}
	}
}

/* this is called several times every few seconds, possibly due to usbd */
static void
portinfo(Usbhost *uh, char *s, char *se)
{
	int x, i, j;
	Ctlr *ctlr;

	XIPRINT("ohci: portinfo from devusb\n");
	ctlr = uh->ctlr;
	for(i = 1; i <= ctlr->nports; i++){
		ilock(ctlr);
		x = getportstatus(ctlr, i);
		iunlock(ctlr);
		s = seprint(s, se, "%d %ux", i, x);
		for(j = 0; j < nelem(portstatus); j++)
			if((x & portstatus[j].bit) != 0)
				s = seprint(s, se, " %s", portstatus[j].name);
		s = seprint(s, se, "\n");
	}
}

void
interrupt(Ureg *, void *arg)
{
	int dirin, cc;
	ulong ctrl, status;
	uchar *p;
	Block *bp;
	Ctlr *ub;
	Endpt *ep;
	Endptx *epx;
	TD *donetd, *nexttd;
	Usbhost *eh;

	XIPRINT("ohci: interrupt\n");
	eh = arg;
	ub = eh->ctlr;
	status = ub->base->intrsts;
	status &= ub->base->intrenable;
	status &= Oc | Rhsc | Fno
		| Ue
		| Rd | Sf | Wdh
		| So;
	if(status & Wdh){
		/* LSb of donehead has bit that says there are other interrupts */
		donetd = hcva2ucva(ub->uchcca->donehead & ~0xf);
		XIPRINT("donetd %#p\n", donetd);
	}else
		donetd = 0;
	ub->base->intrsts = status;
	status &= ~Wdh;
	while(donetd){
		ctrl = donetd->ctrl;
		ep = donetd->ep;
		bp = donetd->bp;
		donetd->bp = nil;
		epx = ep->private;

		ohciinterrupts[ep->epmode]++;
		dirin = ((ctrl >> TD_DP_SHIFT) & TD_DP_MASK) == Otokin;
		ep->buffered -= donetd->bytes;
		if(ep->epmode == Isomode){
			dirin = Dirout;
			if(ep->buffered < 0){
				print("intr: buffered %d bytes %ld\n",
					ep->buffered, donetd->bytes);
				ep->buffered = 0;
			}
		}
		cc = (ctrl >> TD_CC_SHIFT) & TD_CC_MASK;
		if((usbhdebug || ep->debug) && (cc != 0 && cc != 9)){
			print("%d/%d: cc %d frnum %#lux\n",
				ep->dev->x, ep->x, cc, ub->base->fmnumber);
			dumptd(donetd, "after");
		}
		switch(cc){
		case 8:			/* Overrun, Not an error */
			epx->overruns++;
			/* fall through to no error code */
		case 0:			/* noerror */
			if((donetd->flags & TD_FLAGS_LAST) == 0)
				break;
			if(dirin){
				if(bp){
					p = hcva2va(donetd->be + 1);
					if(p < bp->wp)
						print("interrupt: bp: rp %#p"
							", wp %#p→%#p\n",
							bp->rp, bp->wp, p);
					bp->wp = p;
				}
			}
			ep->dir[dirin].xdone++;
			wakeup(&ep->dir[dirin].rend);
			break;
		case 9:			/* underrun */
			if(bp){
				p = hcva2va(donetd->cbp);
				XEIPRINT("interrupt: bp: rp %#p, wp "
					"%#p→%#p\n", bp->rp, bp->wp, p);
				bp->wp = p;
			}
			if((donetd->flags & TD_FLAGS_LAST) == 0){
				XEIPRINT("Underrun\n");
				ep->dir[dirin].err = Eunderrun;
			}
			ep->dir[dirin].xdone++;
			wakeup(&ep->dir[dirin].rend);
			break;
		case 1:			/* CRC */
			ep->dir[dirin].err = "CRC error";
			goto error;
		case 2:			/* Bitstuff */
			ep->dir[dirin].err = "Bitstuff error";
			goto error;
		case 3:
			ep->dir[dirin].err = "data toggle mismatch";
			goto error;
		case 4:			/* Stall */
			ep->dir[dirin].err = Estalled;
			goto error;
		case 5:			/* No response */
			ep->dir[dirin].err = "No response";
			goto error;
		case 6:			/* PIDcheck */
			ep->dir[dirin].err = "PIDcheck";
			goto error;
		case 7:			/* UnexpectedPID */
			ep->dir[dirin].err = "badPID";
			goto error;
		error:
			XEPRINT("fail %d (%lud)\n", cc,
				(ctrl >> TD_EC_SHIFT) & TD_EC_MASK);
			ep->dir[dirin].xdone++;
			wakeup(&ep->dir[dirin].rend);
			break;
		default:
			panic("cc %ud unimplemented\n", cc);
		}
		ep->dir[dirin].queued--;
		/* Clean up blocks used for transfers */
		if(dirin == Dirout)
			freeb(bp);
		nexttd = TDgetnexttd(donetd);
		TDfree(ub, donetd);
		donetd = nexttd;
	}
	if(status & Sf){
		if (0)
			XIPRINT(("sof!!\n"));
		// wakeup(&ub->sofr);	/* sofr doesn't exist anywhere! */
		status &= ~Sf;
	}
	if(status & Ue){
		ulong curred;

		// usbhdbg();		/* TODO */
		curred = ub->base->periodcurred;
		print("usbh: unrecoverable error frame %#.8lux ed %#.8lux, "
			"ints %d %d %d %d\n",
			ub->base->fmnumber, curred,
			ohciinterrupts[1], ohciinterrupts[2],
			ohciinterrupts[3], ohciinterrupts[4]);
		if(curred)
			dumped(hcva2ucva(curred));
	}
	if(status)
		IPRINT(("interrupt: unhandled interrupt %#.8lux\n", status));
}

static void
usbhattach(Ctlr *ub)		/* TODO: is unused now, but it fiddles ctlr */
{
	ulong ctrl;

	if(ub == nil || ub->base == 0)
		error(Enodev);
	ctrl = ub->base->control;
	if((ctrl & HcfsMask) != HcfsOperational){
		ctrl = (ctrl & ~HcfsMask) | HcfsOperational;
		ub->base->control = ctrl;
		ub->base->rhsts = Sgp;
	}
}

static int
reptdone(void *arg)
{
	Endpt *ep;

	ep = arg;
	return ep->dir[Dirin].err
		/* Expression crafted to deal with wrap around: */
		|| ep->dir[Dirin].xdone - ep->dir[Dirin].xstarted >= 0;
}

Block *
breadusbh(Ctlr *ub, Endpt *ep, long n)		/* guts of read() */
{
	long in, l;
	uchar *p;
	Block *bp;
	Endptx *epx;

	epx = ep->private;
	qlock(&ep->rlock);
	EDsetC(epx->ed, ep->rdata01);
	XEPRINT("breadusbh(%d/%d, %ld, dt %d)\n", ep->dev->x, ep->x, n, ep->rdata01);
	eptenable(ub, ep, Dirin);
	if(waserror()){
		EDcancel(ub, epx->ed, Dirin);
		ep->dir[Dirin].err = nil;
		qunlock(&ep->rlock);
		nexterror();
	}
	if(ep->dir[Dirin].err != nil)
		error("usb: can't happen");
	bp = allocrcvb(n);
	in = n;
	if(in > bp->lim - bp->wp){
		print("usb: read larger than block\n");
		in = bp->lim - bp->wp;
	}
	p = bp->rp;
	do{
		l = qtd(ub, ep, Dirin, bp, p, p+in, Otokin, 0);
		p += l;
		in -= l;
	}while(in > 0);
	sleep(&ep->dir[Dirin].rend, reptdone, ep);
	if(ep->dir[Dirin].err){
		EDcancel(ub, epx->ed, Dirin);
		if(ep->dir[Dirin].err == Eunderrun)
			ep->dir[Dirin].err = nil;
		else
			error(ep->dir[Dirin].err);
	}
	XEPRINT("breadusbh(%d/%d, %ld) returned %ld\n", ep->dev->x, ep->x, n,
		BLEN(bp));
	poperror();
	qunlock(&ep->rlock);
	ep->rdata01 = EDgetC(epx->ed);
	return bp;
}

static long
read(Usbhost *uh, Endpt *ep, void *a, long n, vlong off) /* TODO off */
{
	long l;
	Block *bp;
	Ctlr *ub;

	XEPRINT("ohci: read from devusb\n");
	USED(off);
	ub = uh->ctlr;
	XEPRINT("%d/%d: read %#p %ld\n", ep->dev->x, ep->x, a, n);
	bp = breadusbh(ub, ep, n);
	l = BLEN(bp);
	memmove(a, bp->rp, l);
	printdata(bp->rp, 1, l);
	XEPRINT("ohci: read %ld\n\n", l);
	freeb(bp);
	return l;
}

static int
weptdone(void *arg)
{
	Endpt *ep;

	ep = arg;
	/*
	 * success when all operations are done or when less than
	 * a second is buffered in iso connections
	 */
	return	ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted >= 0
		|| (ep->epmode == Isomode && ep->buffered <= ep->bw)
		|| ep->dir[Dirout].err;
}

/* TODO: honour off */
static long
write(Usbhost *uh, Endpt *ep, void *a, long n, vlong off, int tok)
{
	long m;
	short frnum;
	uchar *p = a;
	Block *b;
	Ctlr *ub;
	Endptx *epx;

	XEPRINT("ohci: write(addr %p, bytes %ld, off %lld, tok %d) from devusb\n",
		a, n, off, tok);
	epx = ep->private;
	ub = uh->ctlr;
	qlock(&ep->wlock);
	XEPRINT("%d/%d: write %#p %ld %s\n", ep->dev->x, ep->x, a, n,
		tok == Otoksetup? "setup": "out");
	if(ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted > 0){
		print("done > started, %d %d\n",
			ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted);
		ep->dir[Dirout].xdone = ep->dir[Dirout].xstarted;
	}
	if(waserror()){
		lock(epx);
		EDcancel(ub, epx->ed, Dirout);
		unlock(epx);
		ep->dir[Dirout].err = nil;
		qunlock(&ep->wlock);
		nexterror();
	}
	eptenable(ub, ep, Dirout);
	EDsetC(epx->ed, ep->wdata01);
	if(ep->dir[Dirout].err)
		error(ep->dir[Dirout].err);
	if((m = n) == 0 || p == nil)
		qtd(ub, ep, Dirout, 0, 0, 0, tok, TD_FLAGS_LAST);
	else{
		b = allocb(m+ep->partial);
		if(ep->partial){
			memmove(b->wp, ep->bpartial->rp, ep->partial);
			b->wp += ep->partial;
			ep->partial = 0;
		}
		validaddr((uintptr)p, m, 0);		/* DEBUG */
		memmove(b->wp, a, m);
		b->wp += m;
		printdata(b->rp, 1, m);
		m = BLEN(b);
		dcclean(b->rp, m);
		if(ep->epmode == Isomode && ep->buffered <= ep->bw<<1){
			sleep(&ep->dir[Dirout].rend, weptdone, ep);
			if(ep->dir[Dirout].err){
				freeb(b);
				error(ep->dir[Dirout].err);
			}
		}
		while(m > 0){
			int l;

			l = qtd(ub, ep, Dirout, b, b->rp, b->wp, tok, 0);
			b->rp += l;
			m -= l;
			tok = Otokout;
			if(ep->partial){
				/* We have some data to save */
				if(ep->bpartial == nil)
					ep->bpartial = allocb(ep->maxpkt);
				if(ep->partial != m)
					print("curious: %d != %ld\n",
						ep->partial, m);
				memmove(ep->bpartial->rp, b->rp, ep->partial);
				ep->bpartial->wp = ep->bpartial->rp + ep->partial;
				break;
			}
		}
		freeb(b); /* qtd calls incref; this undoes the one too many */
	}
	if(ep->epmode != Isomode){
		sleep(&ep->dir[Dirout].rend, weptdone, ep);
		if(ep->dir[Dirout].err)
			error(ep->dir[Dirout].err);
	}else if(0 && (frnum = ep->frnum - ub->base->fmnumber) < 0)
			print("too late %d\n", frnum);
	poperror();
	qunlock(&ep->wlock);
	XEPRINT("ohci: wrote %ld\n\n", n);
	ep->wdata01 = EDgetC(epx->ed);
	return n;
}

static void
init(Usbhost*)
{
	XIPRINT("ohci: init from devusb\n");
}

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

	if(already)
		return;
	already = 1;
	p = nil;
	while(p = pcimatch(p, 0, 0)) {
		/*
		 * Find OHCI controllers (Programming Interface = 0x10).
		 */
		if(p->ccrb != Pcibcserial || p->ccru != Pciscusb ||
		    p->ccrp != 0x10)
			continue;
		mem = p->mem[0].bar & ~0x0F;
		XPRINT("usbohci: %x/%x port %#lux size %#x irq %d\n",
			p->vid, p->did, mem, p->mem[0].size, p->intl);
		if(mem == 0){
			print("usbohci: failed to map registers\n");
			continue;
		}
		if(p->intl == 0xFF || p->intl == 0) {
			print("usbohci: no irq assigned for port %#lux\n", mem);
			continue;
		}

		ctlr = malloc(sizeof(Ctlr));
		ctlr->pcidev = p;
		ctlr->base = vmap(mem, p->mem[0].size);
		XPRINT("scanpci: ctlr %#p, base %#p\n", ctlr, ctlr->base);
		pcisetbme(p);
		pcisetpms(p, 0);
		if(ctlrhead != nil)
			ctlrtail->next = ctlr;
		else
			ctlrhead = ctlr;
		ctlrtail = ctlr;
	}
}

static int
reset(Usbhost *uh)
{
	int i, linesize;
	ulong io, fminterval, ctrl;
	Ctlr *ctlr;
	HCCA *atmp;
	OHCI *ohci;
	Pcidev *p;
	QTree *qt;

	/*
	 * data cache line size; probably doesn't matter on pc
	 * except that it must be a power of 2 for xspanalloc.
	 */
	dcls = 32;

	scanpci();

	/*
	 * Any adapter matches if no uh->port is supplied,
	 * otherwise the ports must match.
	 */
	for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
		if(ctlr->active)
			continue;
		if(uh->port == 0 || uh->port == (uintptr)ctlr->base){
			ctlr->active = 1;
			break;
		}
	}
	if(ctlr == nil)
		return -1;

	io = (uintptr)ctlr->base;	/* TODO: correct? */
	ohci = ctlr->base;

	XPRINT("OHCI ctlr %#p, base %#p\n", ctlr, ohci);

	p = ctlr->pcidev;

	uh->ctlr = ctlr;
	uh->port = io;
	uh->irq = p->intl;
	uh->tbdf = p->tbdf;

	XPRINT("OHCI revision %ld.%ld\n", (ohci->revision >> 4) & 0xf,
		ohci->revision & 0xf);
	XPRINT("Host revision %ld.%ld\n", (ohci->hostrevision >> 4) & 0xf,
		ohci->hostrevision & 0xf);
	ctlr->nports = ohci->rhdesca & 0xff;
	XPRINT("HcControl %#.8lux, %d ports\n", ohci->control, ctlr->nports);
	delay(100);  /* anything greater than 50 should ensure reset is done */
	if(ohci->control == ~0){
		ctlrhead = nil;
		return -1;
	}

	/*
	 * usually enter here in reset, wait till its through,
	 * then do our own so we are on known timing conditions.
	 */

	ohci->control = 0;
	delay(100);

	fminterval = ohci->fminterval;

	/* legacy support register: turn off lunacy mode */
	pcicfgw16(p, 0xc0, 0x2000);

	/*
	 * transfer descs need at least 16 byte alignment, but
	 * align to dcache line size since the access will always be uncached.
	 * linesize must be a power of 2 for xspanalloc.
	 */
	linesize = dcls;
	if(linesize < 0x20)
		linesize = 0x20;

	ctlr->td.pool = va2ucva(xspanalloc(Ntd * sizeof(TD), linesize, 0));
	if(ctlr->td.pool == nil)
		panic("usbohci: no memory for TD pool");
	for(i = Ntd - 1; --i >= 0;){
		ctlr->td.pool[i].next = ctlr->td.free;
		ctlr->td.free = &ctlr->td.pool[i];
	}
	ctlr->td.alloced = 0;

	ctlr->ed.pool = va2ucva(xspanalloc(Ned*sizeof(ED), linesize, 0));
	if(ctlr->ed.pool == nil)
		panic("usbohci: no memory for ED pool");
	for(i = Ned - 1; --i >= 0;){
		ctlr->ed.pool[i].next = (ulong)ctlr->ed.free;
		ctlr->ed.free = &ctlr->ed.pool[i];
	}
	ctlr->ed.alloced = 0;

	atmp = xspanalloc(sizeof(HCCA), 256, 0);
	if(atmp == nil)
		panic("usbhreset: no memory for HCCA");
	memset(atmp, 0, sizeof(*atmp));
	ctlr->uchcca = atmp;

	qt = mkqhtree(ctlr);
	if(qt == nil){
		panic("usb: can't allocate scheduling tree");
		return -1;
	}
	ctlr->tree = qt;

	ctlr->base = ohci;

	/* time to move to rest then suspend mode. */
	ohci->cmdsts = 1;         /* reset the block */
	while(ohci->cmdsts == 1)
		continue;  /* wait till reset complete, OHCI says 10us max. */
	/*
	 * now that soft reset is done we are in suspend state.
	 * Setup registers which take in suspend state
	 * (will only be here for 2ms).
	 */

	ohci->hcca = va2hcva(ctlr->uchcca);
	OHCIsetControlHeadED(ctlr->base, 0);
	ctlr->base->ctlcurred = 0;
	OHCIsetBulkHeadED(ctlr->base, 0);
	ctlr->base->bulkcurred = 0;

	ohci->intrenable = Mie | Wdh | Ue;
	ohci->control |= Cle | Ble | Ple | Ie | HcfsOperational;

	/* set frame after operational */
	ohci->fminterval = (fminterval &
		~(HcFmIntvl_FSMaxpack_MASK << HcFmIntvl_FSMaxpack_SHIFT)) |
		5120 << HcFmIntvl_FSMaxpack_SHIFT;
	ohci->rhdesca = 1 << 9;

	for(i = 0; i < ctlr->nports; i++)
		ohci->rhportsts[i] = Spp | Spr;

	delay(100);

	ctrl = ohci->control;
	if((ctrl & HcfsMask) != HcfsOperational){
		XIPRINT("ohci: reset, take ctlr out of Suspend\n");
		ctrl = (ctrl & ~HcfsMask) | HcfsOperational;
		ohci->control = ctrl;
		ohci->rhsts = Sgp;
	}

	p = ctlr->pcidev;
	ctlr->irq = p->intl;
	ctlr->tbdf = p->tbdf;

	/*
	 * Linkage to the generic USB driver.
	 */
	uh->init = init;
	uh->interrupt = interrupt;

	uh->debug = usbdebug;
	uh->portinfo = portinfo;
	uh->portreset = portreset;
	uh->portenable = portenable;

	uh->epalloc = epalloc;
	uh->epfree = epfree;
	uh->epopen = epopen;
	uh->epclose = epclose;
	uh->epmode = epmode;
	uh->epmaxpkt = epmaxpkt;

	uh->read = read;
	uh->write = write;

	uh->tokin = Otokin;
	uh->tokout = Otokout;
	uh->toksetup = Otoksetup;

	return 0;
}

void
usbohcilink(void)
{
	addusbtype("ohci", 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].