shithub: riscv

Download patch

ref: 5e6f1b5769e3e8c391178d82c570fbb566e9bbfb
parent: 990a985836be2f3bc98755999006cb188815aad9
author: cinap_lenrek <[email protected]>
date: Sun Jul 16 18:29:29 EDT 2017

usbxhci: commit work in progress xhci driver, no config yet

--- /dev/null
+++ b/sys/src/9/pc/usbxhci.c
@@ -1,0 +1,1287 @@
+#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"
+
+enum {
+	/* Capability Registers */
+	CAPLENGTH	= 0x00/4,	// 1
+	HCIVERSION	= 0x02/4,	// 2
+	HCSPARAMS1	= 0x04/4,
+	HCSPARAMS2	= 0x08/4,
+	HCSPARAMS3	= 0x0C/4,
+
+	HCCPARAMS	= 0x10/4,
+		AC64	= 1<<0,
+		BNC	= 1<<1,
+		CSZ	= 1<<2,
+		PPC	= 1<<3,
+		PIND	= 1<<4,
+		LHRC	= 1<<5,
+		LTC	= 1<<6,
+		NSS	= 1<<7,
+
+	DBOFF		= 0x14/4,
+	RTSOFF		= 0x18/4,
+
+	/* Operational Registers */
+	USBCMD		= 0x00/4,	/* USB Command Register */
+		RUNSTOP	= 1<<0,		/* Run/Stop - RW */
+		HCRST	= 1<<1,		/* Host Controller Reset - RW */
+		INTE	= 1<<2,		/* Interrupter Enable - RW */
+		HSEE	= 1<<3,		/* Host System Error Enable - RW */
+		LHCRST	= 1<<7,		/* Light Host Controller Reset - RO/RW */
+		CSS	= 1<<8,		/* Controller Save State - RW */
+		CRS	= 1<<9,		/* Controller Restore State - RW */
+		EWE	= 1<<10,	/* Enable Wrap Event - RW */
+		EU3S	= 1<<11,	/* Enable U3 MFINDEX Stop - RW */
+
+	USBSTS		= 0x04/4,	/* USB Status Register */
+		HCH	= 1<<0,		/* HCHalted - RO */
+		HSE	= 1<<2,		/* Host System Error - RW1C */
+		EINT	= 1<<3,		/* Event Interrupt - RW1C */
+		PCD	= 1<<4,		/* Port Change Detect - RW1C */
+		SSS	= 1<<8,		/* Save State Status - RO */
+		RSS	= 1<<9,		/* Restore State Status - RO */
+		SRE	= 1<<10,	/* Save/Restore Error - RW1C */
+		CNR	= 1<<11,	/* Controller Not Ready - RO */
+		HCE	= 1<<12,	/* Host Controller Error - RO */
+
+	PAGESIZE	= 0x08/4,	/* Page Size - RO */
+
+	DNCTRL		= 0x14/4,	/* Device Notification Control Register - RW */
+
+	CRCR		= 0x18/4,	/* Command Ring Control Register - RW */
+		RCS	= 1<<0,		/* Ring Cycle State - RW */
+		CS	= 1<<1,		/* Command Stop - RW1S */
+		CA	= 1<<2,		/* Command Abort - RW1S */
+		CRR	= 1<<3,		/* Command Ring Running - RO */
+
+	DCBAAP		= 0x30/4,	// 8
+
+	CONFIG		= 0x38/4,	/* Configure Register (MaxSlotEn[7:0]) */
+
+	/* Port Register Set */
+	PORTSC		= 0x00/4,	/* Port tatus and Control Register */
+		CCS	= 1<<0,		/* Current Connect Status - ROS */
+		PED	= 1<<1,		/* Port Enable/Disabled - RW1CS */
+		OCA	= 1<<3,		/* Over-current Active - RO */
+		PR	= 1<<4,		/* Port Reset - RW1S */
+		PLS	= 15<<5,	/* Port Link State - RWS */
+		PP	= 1<<9,		/* Port Power - RWS */
+		PS	= 15<<10,	/* Port Speed - ROS */
+		PIC	= 3<<14,	/* Port Indicator Control - RWS */
+		LWS	= 1<<16,	/* Port Link Write Strobe - RW */
+		CSC	= 1<<17,	/* Connect Status Change - RW1CS */
+		PEC	= 1<<18,	/* Port Enabled/Disabled Change - RW1CS */
+		WRC	= 1<<19,	/* Warm Port Reset Change - RW1CS */
+		OCC	= 1<<20,	/* Over-current Change - RW1CS */
+		PRC	= 1<<21,	/* Port Reset Change - RW1CS */
+		PLC	= 1<<22,	/* Port Link State Change - RW1CS */
+		CEC	= 1<<23,	/* Port Config Error Change - RW1CS */
+		CAS	= 1<<24,	/* Cold Attach Status - RO */
+		WCE	= 1<<25,	/* Wake on Connect Enable - RWS */
+		WDE	= 1<<26,	/* Wake on Disconnect Enable - RWS */
+		WOE	= 1<<27,	/* Wake on Over-current Enable - RWS */
+		DR	= 1<<30,	/* Device Removable - RO */
+		WPR	= 1<<31,	/* Warm Port Reset - RW1S */
+
+	PORTPMSC	= 0x04/4,
+	PORTLI		= 0x08/4,
+
+	/* Host Controller Runtime Register */
+	MFINDEX		= 0x0000/4,	/* Microframe Index */
+	IR0		= 0x0020/4,	/* Interrupt Register Set 0 */
+
+	/* Interrupter Registers */
+	IMAN		= 0x00/4,	/* Interrupter Management */
+	IMOD		= 0x04/4,	/* Interrupter Moderation */
+	ERSTSZ		= 0x08/4,	/* Event Ring Segment Table Size */
+	ERSTBA		= 0x10/4,	/* Event Ring Segment Table Base Address */
+	ERDP		= 0x18/4,	/* Event Ring Dequeue Pointer */
+
+	/* TRB types */
+	TR_RESERVED	= 0<<10,
+	TR_NORMAL	= 1<<10,
+	TR_SETUPSTAGE	= 2<<10,
+	TR_DATASTAGE	= 3<<10,
+	TR_STATUSSTAGE	= 4<<10,
+	TR_ISOCH	= 5<<10,
+	TR_LINK		= 6<<10,
+	TR_EVENTDATA	= 7<<10,
+	TR_NOOP		= 8<<10,
+
+	CR_ENABLESLOT	= 9<<10,
+	CR_DISABLESLOT	= 10<<10,
+	CR_ADDRESSDEV	= 11<<10,
+	CR_CONFIGEP	= 12<<10,
+	CR_EVALCTX	= 13<<10,
+	CR_RESETEP	= 14<<10,
+	CR_STOPEP	= 15<<10,
+	CR_SETTRDQP	= 16<<10,
+	CR_RESETDEV	= 17<<10,
+	CR_FORCECMD	= 18<<10,
+	CR_NEGBW	= 19<<10,
+	CR_SETLAT	= 20<<10,
+	CR_GETPORTBW	= 21<<10,
+	CR_FORCEHDR	= 22<<10,
+	CR_NOOP		= 23<<10,
+
+	ER_TRANSFER	= 32<<10,
+	ER_CMDCOMPL	= 33<<10,
+	ER_PORTSC	= 34<<10,
+	ER_BWREQ	= 35<<10,
+	ER_DOORBELL	= 36<<10,
+	ER_HCE		= 37<<10,
+	ER_DEVNOTE	= 38<<10,
+	ER_MFINDEXWRAP	= 39<<10,
+};
+
+typedef struct Ctlr Ctlr;
+typedef struct Wait Wait;
+typedef struct Ring Ring;
+typedef struct Slot Slot;
+typedef struct Epio Epio;
+
+struct Wait
+{
+	Wait	*next;
+	Ring	*ring;
+	u32int	*td;
+	u32int	er[4];
+	Rendez	*z;
+};
+
+struct Ring
+{
+	u32int	*base;
+
+	u32int	size;
+	u32int	mask;
+	u32int	shift;
+
+	u32int	rp;
+	u32int	wp;
+
+	struct {
+		u32int	*r;
+		u32int	v;
+	}	doorbell;
+
+	Wait	*pending;
+	Lock;
+};
+
+struct Slot
+{
+	int	id;
+
+	Ctlr	*ctlr;
+	Udev	*dev;
+
+	u32int	*ibase;
+	u32int	*obase;
+
+	/* endpoint rings */
+	int	nep;
+	Ring	epr[32];
+};
+
+struct Ctlr
+{
+	Pcidev	*pcidev;
+
+	u32int	*mmio;
+
+	u32int	*opr;	/* operational registers */
+	u32int	*prt;	/* port register set */
+	u32int	*rts;	/* runtime registers */
+	u32int	*dba;	/* doorbell array */
+
+	u64int	*dcba;	/* device context base array */
+
+	u64int	*sba;	/* scratchpad buffer array */
+	void	*sbp;	/* scratchpad buffer pages */
+
+	u32int	*erst[1];	/* event ring segment table */
+	Ring	er[1];		/* event ring segment */
+	Ring	cr[1];		/* command ring segment */
+
+	QLock	slotlock;
+	Slot	**slot;		/* slots by slot id */
+	
+	u32int	hccparams;
+
+	int	csz;
+	int	pagesize;
+	int	nscratch;
+	int	nintrs;
+	int	nslots;
+
+	void	(*setrptr)(u32int*, u64int);
+
+	void	*active;
+};
+
+struct Epio
+{
+	QLock;
+	Block	*cb;
+	Ring	*ring[2];	/* OREAD, OWRITE */
+};
+
+static char Ebadlen[] = "bad usb request length";
+static char Enotconfig[] = "usb endpoint not configured";
+
+static void
+setrptr32(u32int *reg, u64int pa)
+{
+	reg[0] = pa;
+	reg[1] = pa>>32;
+}
+
+static void
+setrptr64(u32int *reg, u64int pa)
+{
+	*((u64int*)reg) = pa;
+}
+
+static void
+freering(Ring *r)
+{
+	if(r == nil)
+		return;
+	if(r->base != nil)
+		free(r->base);
+	memset(r, 0, sizeof(*r));
+}
+
+static void
+initring(Ring *r, int shift)
+{
+	r->doorbell.v = 0;
+	r->doorbell.r = nil;
+	r->pending = nil;
+	r->shift = shift;
+	r->size = 1<<shift;
+	r->mask = r->size-1;
+	r->rp = r->wp = 0;
+	r->base = mallocalign(r->size*16, 64, 0, 64*1024);
+	if(r->base == nil){
+		freering(r);
+		error(Enomem);
+	}
+}
+
+static void
+init(Hci *hp)
+{
+	Ctlr *ctlr;
+	uchar *p;
+	int i;
+
+	ctlr = hp->aux;
+
+	ctlr->opr = &ctlr->mmio[(ctlr->mmio[CAPLENGTH]&0xFF)/4];
+	ctlr->prt = &ctlr->opr[0x400/4];
+	ctlr->dba = &ctlr->mmio[ctlr->mmio[DBOFF]/4];
+	ctlr->rts = &ctlr->mmio[ctlr->mmio[RTSOFF]/4];
+
+	while(ctlr->opr[USBSTS] & CNR)
+		tsleep(&up->sleep, return0, nil, 10);
+
+	ctlr->opr[USBCMD] = HCRST;
+	while((ctlr->opr[USBSTS] & (CNR|HCH)) != HCH)
+		tsleep(&up->sleep, return0, nil, 10);
+
+	pcisetbme(ctlr->pcidev);
+	pcisetpms(ctlr->pcidev, 0);
+	intrenable(ctlr->pcidev->intl, hp->interrupt, hp, ctlr->pcidev->tbdf, hp->type);
+
+	ctlr->hccparams = ctlr->mmio[HCCPARAMS];
+	ctlr->csz = (ctlr->hccparams & CSZ) != 0;
+	if(ctlr->hccparams & AC64)
+		ctlr->setrptr = setrptr64;
+	else
+		ctlr->setrptr = setrptr32;
+
+	ctlr->pagesize = ctlr->opr[PAGESIZE]<<12;
+
+	ctlr->nscratch = (ctlr->mmio[HCSPARAMS2] >> 27) & 0x1F;
+	ctlr->nintrs = (ctlr->mmio[HCSPARAMS1] >> 8) & 0x7FF;
+	ctlr->nslots = (ctlr->mmio[HCSPARAMS1] >> 0) & 0xFF;
+	hp->nports = (ctlr->mmio[HCSPARAMS1] >> 24) & 0xFF;
+
+	ctlr->slot = malloc((1+ctlr->nslots)*sizeof(ctlr->slot[0]));
+	ctlr->dcba = mallocalign((1+ctlr->nslots)*8, 64, 0, ctlr->pagesize);
+	if(ctlr->nscratch != 0){
+		ctlr->sba = mallocalign(ctlr->nscratch*8, 64, 0, ctlr->pagesize);
+		ctlr->sbp = mallocalign(ctlr->nscratch*ctlr->pagesize, ctlr->pagesize, 0, 0);
+		for(i=0, p = ctlr->sbp; i<ctlr->nscratch; i++, p += ctlr->pagesize){
+			memset(p, 0, ctlr->pagesize);
+			ctlr->sba[i] = PADDR(p);
+		}
+		ctlr->dcba[0] = PADDR(ctlr->sba);
+	} else {
+		ctlr->dcba[0] = 0;
+	}
+	for(i=1; i<=ctlr->nslots; i++)
+		ctlr->dcba[i] = 0;
+	ctlr->opr[CONFIG] = ctlr->nslots;	/* MaxSlotsEn */
+	coherence();
+	ctlr->setrptr(&ctlr->opr[DCBAAP], PADDR(ctlr->dcba));
+
+	initring(ctlr->cr, 8);		/* 256 entries */
+	ctlr->cr->doorbell.r = &ctlr->dba[0];
+	ctlr->cr->doorbell.v = 0;
+	coherence();
+	ctlr->setrptr(&ctlr->opr[CRCR], PADDR(ctlr->cr[0].base) | 1);
+
+	for(i=0; i<ctlr->nintrs; i++){
+		u32int *irs = &ctlr->rts[IR0 + i*8];
+
+		if(i >= nelem(ctlr->er)){
+			irs[ERSTSZ] = 0;	/* disable ring */
+			continue;
+		}
+
+		/* allocate and link into event ring segment table */
+		initring(&ctlr->er[i], 8);	/* 256 entries */
+		ctlr->erst[i] = mallocalign(16, 64, 0, 0);
+		*((u64int*)ctlr->erst[i]) = PADDR(ctlr->er[i].base);
+		ctlr->erst[i][2] = ctlr->er[i].size;
+		ctlr->erst[i][3] = 0;
+
+		irs[ERSTSZ] = 1;	/* just one segment */
+		coherence();
+		ctlr->setrptr(&irs[ERDP], PADDR(ctlr->er[i].base));
+		coherence();
+		ctlr->setrptr(&irs[ERSTBA], PADDR(ctlr->erst[i]));
+
+		irs[IMAN] = 3;
+		irs[IMOD] = 0;
+	}	
+
+	ctlr->opr[USBCMD] = RUNSTOP|INTE|HSEE;
+	while(ctlr->opr[USBSTS] & (CNR|HCH))
+		tsleep(&up->sleep, return0, nil, 10);
+}
+
+static void
+dump(Hci *)
+{
+}
+
+static void
+queuetd(Ring *ring, u32int c, u32int s, u64int p, Wait *w)
+{
+	u32int *td, x;
+
+	x = ring->wp++;
+	if((x & ring->mask) == ring->mask){
+		td = ring->base + 4*(x & ring->mask);
+		*(u64int*)td = PADDR(ring->base);
+		td[2] = 0;
+		td[3] = ((~x>>ring->shift)&1) | (1<<1) | TR_LINK;
+		x = ring->wp++;
+	}
+	td = ring->base + 4*(x & ring->mask);
+	if(w != nil){
+		memset(w->er, 0, 4*4);
+		w->ring = ring;
+		w->td = td;
+		w->z = &up->sleep;
+		w->next = ring->pending;
+		ring->pending = w;
+	}
+	*(u64int*)td = p;
+	td[2] = s;
+	td[3] = ((~x>>ring->shift)&1) | c;
+}
+
+static char *ccerrtab[] = {
+[2]	"Data Buffer Error",
+[3]	"Babble Detected Error",
+[4]	"USB Transaction Error",
+[5]	"TRB Error",
+[6]	"Stall Error",
+[7]	"Resume Error",
+[8]	"Bandwidth Error",
+[9]	"No Slots Available",
+[10]	"Invalid Stream Type",
+[11]	"Slot Not Enabled",
+[12]	"Endpoint Not Enabled",
+[13]	"Short Packet",
+[14]	"Ring Underrun",
+[15]	"Ring Overrun",
+[16]	"VF Event Ring Full",
+[17]	"Parameter Error",
+[18]	"Bandwidth Overrun Error",
+[19]	"Context State Error",
+[20]	"No Ping Response",
+[21]	"Event Ring Full",
+[22]	"Incompatible Device",
+[23]	"Missed Service Error",
+[24]	"Command Ring Stopped",
+[25]	"Command Aborted",
+[26]	"Stopped",
+[27]	"Stoppe - Length Invalid",
+[29]	"Max Exit Latency Too Large",
+[31]	"Isoch Buffer Overrun",
+[32]	"Event Lost Error",
+[33]	"Undefined Error",
+[34]	"Invalid Stream ID",
+[35]	"Secondary Bandwidth Error",
+[36]	"Split Transaction Error",
+};
+
+static char*
+ccerrstr(u32int cc)
+{
+	char *s;
+
+	if(cc == 1 || cc == 13)
+		return nil;
+	if(cc < nelem(ccerrtab) && ccerrtab[cc] != nil)
+		s = ccerrtab[cc];
+	else
+		s = "???";
+	return s;
+}
+
+static int
+waitdone(void *a)
+{
+	return ((Wait*)a)->z == nil;
+}
+
+static void
+kickring(Ring *r)
+{
+	coherence();
+	*r->doorbell.r = r->doorbell.v;
+}
+
+static char*
+waittd(Wait *w, u32int *er)
+{
+	while(!waitdone(w)){
+		while(waserror())
+			;
+		kickring(w->ring);
+		sleep(&up->sleep, waitdone, w);
+		poperror();
+	}
+	if(er != nil)
+		memmove(er, w->er, 4*4);
+	return ccerrstr(w->er[2]>>24);
+}
+
+static char*
+ctlrcmd(Ctlr *ctlr, u32int c, u32int s, u64int p, u32int *er)
+{
+	Wait w[1];
+
+	ilock(ctlr->cr);
+	queuetd(ctlr->cr, c, s, p, w);
+	iunlock(ctlr->cr);
+	return waittd(w, er);
+}
+
+static void
+completering(Ring *r, u32int *er)
+{
+	Wait *w, **wp;
+	u32int *td;
+	u64int pa;
+
+	pa = (*(u64int*)er) & ~15ULL;
+	ilock(r);
+
+	while((int)(r->wp - r->rp) > 0){
+		td = &r->base[4*(r->rp++ & r->mask)];
+		if((u64int)PADDR(td) == pa)
+			break;
+	}
+
+	wp = &r->pending;
+	while(w = *wp){
+		if((u64int)PADDR(w->td) == pa){
+			Rendez *z = w->z;
+
+			memmove(w->er, er, 4*4);
+			*wp = w->next;
+			w->next = nil;
+
+			if(z != nil){
+				w->z = nil;
+				wakeup(z);
+			}
+			break;
+		} else {
+			wp = &w->next;
+		}
+	}
+
+	iunlock(r);
+}
+
+static void
+interrupt(Ureg*, void *arg)
+{
+	Hci *hp = arg;
+	Ctlr *ctlr = hp->aux;
+	Ring *ring = ctlr->er;
+	Slot *slot;
+	u32int *irs, *td, x;
+
+	irs = &ctlr->rts[IR0];
+	x = irs[IMAN];
+	if(x & 1) irs[IMAN] = x & 3;
+
+	for(x = ring->rp;; x=++ring->rp){
+		td = ring->base + 4*(x & ring->mask);
+		if((((x>>ring->shift)^td[3])&1) == 0)
+			break;
+
+		if(0) iprint("xhci interrupt: event %ud: %ux %ux %ux %ux\n",
+			x, td[0], td[1], td[2], td[3]);
+
+		switch(td[3] & 0xFC00){
+		case ER_CMDCOMPL:
+			completering(ctlr->cr, td);
+			break;
+		case ER_TRANSFER:
+			x = td[3]>>24;
+			if(x == 0 || x > ctlr->nslots)
+				break;
+			slot = ctlr->slot[x];
+			if(slot == nil)
+				break;
+			completering(&slot->epr[(td[3]>>16)-1&31], td);
+			break;
+		case ER_PORTSC:
+		case ER_BWREQ:
+		case ER_DOORBELL:
+		case ER_HCE:
+		case ER_DEVNOTE:
+		case ER_MFINDEXWRAP:
+		default:
+			break;
+		}
+	}
+
+	ctlr->setrptr(&irs[ERDP], PADDR(td) | (1<<3));
+}
+
+static void
+freeslot(void *arg)
+{
+	Slot *slot;
+	Ctlr *ctlr;
+
+	if(arg == nil)
+		return;
+	slot = arg;
+	ctlr = slot->ctlr;
+	if(slot->id != 0){
+		qlock(&ctlr->slotlock);
+		ctlrcmd(ctlr, CR_DISABLESLOT | (slot->id<<24), 0, 0, nil);
+		ctlr->dcba[slot->id] = 0;
+		ctlr->slot[slot->id] = nil;
+		qunlock(&ctlr->slotlock);
+	}
+	freering(&slot->epr[0]);
+	free(slot->ibase);
+	free(slot->obase);
+	free(slot);
+}
+
+static Slot*
+allocslot(Ctlr *ctlr, Udev *dev)
+{
+	u32int r[4];
+	Slot *slot;
+	char *err;
+
+	slot = malloc(sizeof(Slot));
+	if(slot == nil)
+		error(Enomem);
+
+	slot->ctlr = ctlr;
+	slot->dev = dev;
+	slot->nep = 0;
+	slot->id = 0;
+
+	qlock(&ctlr->slotlock);
+	if(waserror()){
+		qunlock(&ctlr->slotlock);
+		freeslot(slot);
+		nexterror();
+	}
+	slot->ibase = mallocalign(32*33 << ctlr->csz, 64, 0, ctlr->pagesize);
+	slot->obase = mallocalign(32*32 << ctlr->csz, 64, 0, ctlr->pagesize);
+	if(slot->ibase == nil || slot->obase == nil)
+		error(Enomem);
+	if((err = ctlrcmd(ctlr, CR_ENABLESLOT, 0, 0, r)) != nil)
+		error(err);
+	slot->id = r[3]>>24;
+	if(slot->id <= 0 || slot->id > ctlr->nslots){
+		slot->id = 0;
+		error("bad slot id from controller");
+	}
+	poperror();
+
+	ctlr->dcba[slot->id] = PADDR(slot->obase);
+	ctlr->slot[slot->id] = slot;
+	qunlock(&ctlr->slotlock);
+
+	dev->aux = slot;
+	dev->free = freeslot;
+
+	return slot;
+}
+
+static void
+shutdown(Hci *)
+{
+}
+
+static void
+setdebug(Hci *, int)
+{
+}
+
+static void
+epclose(Ep *ep)
+{
+	Ctlr *ctlr;
+	Slot *slot;
+	Epio *io;
+
+	if(ep->dev->isroot)
+		return;
+
+	io = ep->aux;
+	if(io == nil)
+		return;
+	ep->aux = nil;
+
+	ctlr = ep->hp->aux;
+	slot = ep->dev->aux;
+
+	if(ep->nb > 0 && (io->ring[OREAD] != nil || io->ring[OWRITE] != nil)){
+		u32int *w;
+
+		/* input control context */
+		w = slot->ibase;
+		memset(w, 0, 32<<ctlr->csz);
+		w[1] = 1;
+		if(io->ring[OREAD] != nil){
+			w[0] |= 2 << ep->nb*2;
+			if(ep->nb*2+1 == slot->nep)
+				slot->nep--;
+		}
+		if(io->ring[OWRITE] != nil){
+			w[0] |= 1 << ep->nb*2;
+			if(ep->nb*2 == slot->nep)
+				slot->nep--;
+		}
+
+		/* (input) slot context */
+		w += 8<<ctlr->csz;
+		w[0] = (w[0] & ~(0x1F<<27)) | slot->nep<<27;
+
+		/* (input) ep context */
+		w += ep->nb*2*8<<ctlr->csz;
+		memset(w, 0, 2*32<<ctlr->csz);
+
+		ctlrcmd(ctlr, CR_CONFIGEP | (slot->id<<24), 0, PADDR(slot->ibase), nil);
+
+		freering(io->ring[OREAD]);
+		freering(io->ring[OWRITE]);
+	}
+	freeb(io->cb);
+	free(io);
+}
+
+static void
+initep(Ep *ep)
+{
+	Epio *io;
+	Ctlr *ctlr;
+	Slot *slot;
+	u32int *w;
+	int ival;
+	char *err;
+
+	io = ep->aux;
+	slot = ep->dev->aux;
+	ctlr = ep->hp->aux;
+
+	io->ring[OREAD] = io->ring[OWRITE] = nil;
+	if(ep->nb == 0){
+		io->ring[OWRITE] = &slot->epr[0];
+		return;
+	}
+
+	/* (input) control context */
+	w = slot->ibase;
+	memset(w, 0, 32<<ctlr->csz);
+	w[1] = 1;
+
+	if(waserror()){
+		freering(io->ring[OWRITE]), io->ring[OWRITE] = nil;
+		freering(io->ring[OREAD]), io->ring[OREAD] = nil;
+		nexterror();
+	}
+	if(ep->mode != OREAD){
+		initring(io->ring[OWRITE] = &slot->epr[ep->nb*2-1], 8);
+		io->ring[OWRITE]->doorbell.r = &ctlr->dba[slot->id];
+		io->ring[OWRITE]->doorbell.v = ep->nb*2;
+
+		w[1] |= 1 << ep->nb*2;
+		if(ep->nb*2 > slot->nep)
+			slot->nep = ep->nb*2;
+	}
+	if(ep->mode != OWRITE){
+		initring(io->ring[OREAD] = &slot->epr[ep->nb*2], 8);
+		io->ring[OREAD]->doorbell.r = &ctlr->dba[slot->id];
+		io->ring[OREAD]->doorbell.v = ep->nb*2+1;
+
+		w[1] |= 2 << ep->nb*2;
+		if(ep->nb*2+1 > slot->nep)
+			slot->nep = ep->nb*2+1;
+	}
+
+	/* (input) slot context */
+	w += 8<<ctlr->csz;
+	w[0] = (w[0] & ~(0x1F<<27)) | slot->nep<<27;
+
+	/* (input) ep context */
+	w += ep->nb*2*8<<ctlr->csz;
+	memset(w, 0, 2*32<<ctlr->csz);
+
+	if(ep->ttype == Tintr && (ep->dev->speed == Fullspeed || ep->dev->speed == Lowspeed)){
+		for(ival=3; ival < 11 && (1<<ival) < ep->pollival; ival++)
+			;
+	} else {
+		for(ival=0; ival < 15 && (1<<ival) < ep->pollival; ival++)
+			;
+	}
+
+	/* out */
+	if(io->ring[OWRITE] != nil){
+		w[0] = ival<<16;
+		w[1] = 3<<1 | ((ep->ttype - Tctl)|0)<<3 | ep->maxpkt<<16;
+		*((u64int*)&w[2]) = PADDR(io->ring[OWRITE]->base) | 1;
+		w[4] = 1;
+	}
+
+	/* in */
+	w += 8<<ctlr->csz;
+	if(io->ring[OREAD] != nil){
+		w[0] = ival<<16;
+		w[1] = 3<<1 | ((ep->ttype - Tctl)|4)<<3 | ep->maxpkt<<16;
+		*((u64int*)&w[2]) = PADDR(io->ring[OREAD]->base) | 1;
+		w[4] = 1;
+	}
+
+	if((err = ctlrcmd(ctlr, CR_CONFIGEP | (slot->id<<24), 0,
+		PADDR(slot->ibase), nil)) != nil){
+		error(err);
+	}
+	poperror();
+}
+
+static int
+speedid(int speed)
+{
+	switch(speed){
+	case Fullspeed:		return 1;
+	case Lowspeed:		return 2;
+	case Highspeed:		return 3;
+	case Superspeed:	return 4;
+	}
+	return 0;
+}
+
+static void
+epopen(Ep *ep)
+{
+	Ctlr *ctlr = ep->hp->aux;
+	Slot *slot, *hub;
+	Epio *io;
+	Udev *dev;
+	char *err;
+	u32int *w;
+	int i;
+
+	if(ep->dev->isroot)
+		return;
+
+	io = malloc(sizeof(Epio));
+	if(io == nil)
+		error(Enomem);
+	ep->aux = io;
+	if(waserror()){
+		epclose(ep);
+		nexterror();
+	}
+	dev = ep->dev;
+	slot = dev->aux;
+	if(slot != nil && slot->dev == dev){
+		initep(ep);
+		poperror();
+		return;
+	}
+
+	/* first open has to be control endpoint */
+	if(ep->nb != 0)
+		error(Egreg);
+
+	slot = allocslot(ctlr, dev);
+
+	/* allocate control ep 0 ring */
+	initring(io->ring[OWRITE] = &slot->epr[0], 8);
+	io->ring[OWRITE]->doorbell.r = &ctlr->dba[slot->id];
+	io->ring[OWRITE]->doorbell.v = 1;
+	slot->nep = 1;
+
+	/* (input) control context */
+	w = slot->ibase;
+	memset(w, 0, 3*32<<ctlr->csz);
+	w[1] = 3;	/* A0, A1 */
+		
+	/* (input) slot context */
+	w += 8<<ctlr->csz;
+	w[2] = w[3] = 0;
+	w[0] = dev->routestr | speedid(dev->speed)<<20 |
+		(dev->speed == Highspeed && dev->ishub != 0
+		||dev->speed < Highspeed && dev->ishub == 0)<<25 |
+		(dev->ishub != 0)<<26 | slot->nep<<27;
+	w[1] = dev->rootport<<16;
+
+	/* find the parent hub that this device is conected to */
+	qlock(&ctlr->slotlock);
+	for(i=1; i<=ctlr->nslots; i++){
+		hub = ctlr->slot[i];
+		if(hub == nil || hub->dev == nil || hub->dev->aux != hub)
+			continue;
+		if(hub == slot || hub->dev == dev)
+			continue;
+		if(!hub->dev->ishub)
+			continue;
+		if(hub->dev->addr != dev->hub)
+			continue;
+		if(hub->dev->rootport != dev->rootport)
+			continue;
+
+		if(dev->speed < Highspeed && hub->dev->speed == Highspeed)
+			w[2] = hub->id | dev->port<<8;
+		break;
+	}
+	qunlock(&ctlr->slotlock);
+
+	/* (input) ep context 0 */
+	w += 8<<ctlr->csz;
+	w[1] = 3<<1 | 4<<3 | ep->maxpkt<<16;
+	*((u64int*)&w[2]) = PADDR(io->ring[OWRITE]->base) | 1;
+
+	if((err = ctlrcmd(ctlr, CR_ADDRESSDEV | (slot->id<<24), 0,
+		PADDR(slot->ibase), nil)) != nil){
+		error(err);
+	}
+
+	/* (output) slot context */
+	w = slot->obase;
+	ep->dev->addr = w[3] & 0xFF;
+	poperror();
+}
+
+static void
+resetring(Ring *r)
+{
+	ilock(r);
+	r->rp--;	/* assume previous td halted */
+	while((int)(r->wp - r->rp) > 0){
+		u32int *td = &r->base[4*(--r->wp & r->mask)];
+		td[0] = td[1] = td[2] = 0;
+		td[3] = (r->wp>>r->shift)&1;
+	}
+	iunlock(r);
+}
+
+static int
+epunstall(Ep *ep, int mode)
+{
+	Ctlr *ctlr;
+	Slot *slot;
+	Epio *io;
+	u32int *w;
+	int dci, ret;
+
+	ret = 0;
+	io = ep->aux;
+	slot = ep->dev->aux;
+	ctlr = slot->ctlr;
+
+	if(ep->nb == 0)
+		dci = 1;
+	else
+		dci = ep->nb*2;	
+
+	/* (output) ep context */
+	w = slot->obase;
+	w += dci*8<<ctlr->csz;
+	if(mode != OREAD && io->ring[OWRITE] != nil){
+		switch(w[0]&7){
+		case 2:
+		case 4:
+			resetring(io->ring[OWRITE]);
+			ctlrcmd(ctlr, CR_RESETEP | (dci<<16) | (slot->id<<24), 0, 0, nil);
+			ret++;
+		}
+	}
+	w += 8<<ctlr->csz, dci++;
+	if(mode != OWRITE && io->ring[OREAD] != nil){
+		switch(w[0]&7){
+		case 2:
+		case 4:
+			resetring(io->ring[OREAD]);
+			ctlrcmd(ctlr, CR_RESETEP | (dci<<16) | (slot->id<<24), 0, 0, nil);
+			ret++;
+		}
+	}
+	return ret;
+}
+
+static long
+epread(Ep *ep, void *va, long n)
+{
+	Epio *io;
+	uchar *p;
+	Ring *ring;
+	char *err;
+	Wait ws[1];
+
+	p = va;
+	io = ep->aux;
+	if(ep->ttype == Tctl){
+		qlock(io);
+		if(io->cb == nil || BLEN(io->cb) == 0){
+			qunlock(io);
+			return 0;
+		}
+		if(n > BLEN(io->cb))
+			n = BLEN(io->cb);
+		memmove(p, io->cb->rp, n);
+		io->cb->rp += n;
+		qunlock(io);
+		return n;
+	}
+
+	if((uintptr)p <= KZERO){
+		Block *b;
+
+		b = allocb(n);
+		if(waserror()){
+			freeb(b);
+			nexterror();
+		}
+		n = epread(ep, b->rp, n);
+		memmove(p, b->rp, n);
+		freeb(b);
+		poperror();
+		return n;
+	}
+
+	epunstall(ep, OREAD);
+	ring = io->ring[OREAD];
+	ilock(ring);
+	queuetd(ring, TR_NORMAL | 1<<16 | 1<<5, n, PADDR(p), ws);
+	iunlock(ring);
+
+	if((err = waittd(ws, nil)) != nil)
+		error(err);
+
+	n -= (ws->er[2] & 0xFFFFFF);
+	if(n < 0)
+		n = 0;
+
+	return n;
+}
+
+static long
+epwrite(Ep *ep, void *va, long n)
+{
+	Wait ws[3];
+	Ring *ring;
+	Epio *io;
+	uchar *p;
+	char *err;
+
+	p = va;
+	io = ep->aux;
+	if(ep->ttype == Tctl){
+		int dir, len;
+
+		if(n < 8)
+			error(Eshort);
+
+		if(p[0] == 0x00 && p[1] == 0x05)
+			return n;
+
+		qlock(io);
+		if(waserror()){
+			qunlock(io);
+			nexterror();
+		}
+		if(io->cb != nil){
+			freeb(io->cb);
+			io->cb = nil;
+		}
+		len = GET2(&p[6]);
+		dir = (p[0] & Rd2h) != 0;
+		if(len > 0){
+			io->cb = allocb(len);		
+			if(dir == 0){	/* out */
+				assert(len >= n-8);
+				memmove(io->cb->wp, p+8, n-8);
+			} else {
+				memset(io->cb->wp, 0, len);
+				io->cb->wp += len;
+			}
+		}
+
+		epunstall(ep, OWRITE);
+		ring = io->ring[OWRITE];
+		ilock(ring);
+		queuetd(ring, TR_SETUPSTAGE | (len > 0 ? 2+dir : 0)<<16 | 1<<6 | 1<<5, 8,
+			p[0] | p[1]<<8 | GET2(&p[2])<<16 |
+			(u64int)(GET2(&p[4]) | len<<16)<<32, &ws[0]);
+		if(len > 0)
+			queuetd(ring, TR_DATASTAGE | dir<<16 | 0<<6 | 1<<5 | 0<<2, len,
+				PADDR(io->cb->rp), &ws[1]);
+		queuetd(ring, TR_STATUSSTAGE | (len == 0 || !dir)<<16 | 1<<5, 0, 0, &ws[2]);
+		iunlock(ring);
+
+		if((err = waittd(&ws[0], nil)) != nil)
+			error(err);
+		if(len > 0){
+			if((err = waittd(&ws[1], nil)) != nil)
+				error(err);
+			if(dir != 0){
+				io->cb->wp -= (ws[1].er[2] & 0xFFFFFF);
+				if(io->cb->wp < io->cb->rp)
+					io->cb->wp = io->cb->rp;
+			}
+		}
+		if((err = waittd(&ws[2], nil)) != nil)
+			error(err);
+		qunlock(io);
+		poperror();
+		return n;
+	}
+
+	if((uintptr)p <= KZERO){
+		Block *b;
+
+		b = allocb(n);
+		if(waserror()){
+			freeb(b);
+			nexterror();
+		}
+		memmove(b->wp, p, n);
+		n = epwrite(ep, b->wp, n);
+		freeb(b);
+		poperror();
+		return n;
+	}
+
+	epunstall(ep, OWRITE);
+	ring = io->ring[OWRITE];
+	ilock(ring);
+	queuetd(ring, TR_NORMAL | 1<<16 | 1<<5, n, PADDR(p), ws);
+	iunlock(ring);
+
+	if((err = waittd(ws, nil)) != nil)
+		error(err);
+
+	return n;
+}
+
+static char*
+seprintep(char *s, char*, Ep*)
+{
+	return s;
+}
+
+static int
+portstatus(Hci *hp, int port)
+{
+	Ctlr *ctlr = hp->aux;
+	u32int psc = ctlr->prt[PORTSC + (port-1)*4];
+	int ps = 0;
+
+	if(psc & CCS)	ps |= HPpresent;
+	if(psc & PED)	ps |= HPenable;
+	if(psc & OCA)	ps |= HPovercurrent;
+	if(psc & PR)	ps |= HPreset;
+	else {
+		switch((psc>>10)&0xF){
+		case 1:
+			/* full speed */
+			break;
+		case 2:
+			ps |= HPslow;
+			break;
+		case 3:
+			ps |= HPhigh;
+			break;
+		case 4:
+			/* super speed */
+			break;
+		}
+	}
+	if(psc & PP)	ps |= HPpower;
+
+	if(psc & CSC)	ps |= HPstatuschg;
+	if(psc & PRC)	ps |= HPchange;
+
+	return ps;
+}
+	
+static int
+portenable(Hci *, int, int)
+{
+	return 0;
+}
+
+static int
+portreset(Hci *hp, int port, int on)
+{
+	Ctlr *ctlr = hp->aux;
+	u32int *r = &ctlr->prt[PORTSC + (port-1)*4];
+
+	if(on){
+		*r |= PR;
+		tsleep(&up->sleep, return0, nil, 200);
+	}
+
+	return 0;
+}
+
+
+static Ctlr *ctlrs[Nhcis];
+
+static void
+scanpci(void)
+{
+	static int already = 0;
+	int i;
+	uintptr io;
+	Ctlr *ctlr;
+	Pcidev *p;
+	u32int *mmio; 
+
+	if(already)
+		return;
+	already = 1;
+	p = nil;
+	while ((p = pcimatch(p, 0, 0)) != nil) {
+		/*
+		 * Find XHCI controllers (Programming Interface = 0x30).
+		 */
+		if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || p->ccrp != 0x30)
+			continue;
+		io = p->mem[0].bar & ~0x0f;
+		if(io == 0)
+			continue;
+		mmio = vmap(io, p->mem[0].size);
+		if(mmio == nil){
+			print("usbxhci: cannot map registers\n");
+			continue;
+		}
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil){
+			print("usbxhci: no memory\n");
+			vunmap(mmio, p->mem[0].size);
+			continue;
+		}
+		print("usbxhci: %#x %#x: port %#p size %#x irq %d\n",
+			p->vid, p->did, mmio, p->mem[0].size, p->intl);
+		ctlr->active = nil;
+		ctlr->pcidev = p;
+		ctlr->mmio = mmio;
+		for(i = 0; i < nelem(ctlrs); i++)
+			if(ctlrs[i] == nil){
+				ctlrs[i] = ctlr;
+				break;
+			}
+		if(i >= nelem(ctlrs))
+			print("xhci: bug: more than %d controllers\n", nelem(ctlrs));
+	}
+}
+
+static int
+reset(Hci *hp)
+{
+	Ctlr *ctlr;
+	int i;
+
+	if(getconf("*nousbxhci"))
+		return -1;
+
+	fmtinstall(L'H', encodefmt);
+
+	scanpci();
+
+	/*
+	 * Any adapter matches if no hp->port is supplied,
+	 * otherwise the ports must match.
+	 */
+	for(i = 0; i < nelem(ctlrs) && ctlrs[i] != nil; i++){
+		ctlr = ctlrs[i];
+		if(ctlr->active == nil)
+		if(hp->port == 0 || hp->port == (uintptr)ctlr->mmio){
+			ctlr->active = hp;
+			goto Found;
+		}
+	}
+	return -1;
+
+Found:
+	hp->aux = ctlr;
+	hp->port = (uintptr)ctlr->mmio;
+	hp->irq = ctlr->pcidev->intl;
+	hp->tbdf = ctlr->pcidev->tbdf;
+
+	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 = "xhci";
+
+	return 0;
+}
+
+void
+usbxhcilink(void)
+{
+	addhcitype("xhci", reset);
+}
--- a/sys/src/9/port/devusb.c
+++ b/sys/src/9/port/devusb.c
@@ -160,6 +160,7 @@
 
 static char *spname[] =
 {
+	[Superspeed]	"super",
 	[Fullspeed]	"full",
 	[Lowspeed]	"low",
 	[Highspeed]	"high",
@@ -295,6 +296,7 @@
 	s = seprint(s, se, " hz %ld", ep->hz);
 	s = seprint(s, se, " hub %d", ep->dev->hub);
 	s = seprint(s, se, " port %d", ep->dev->port);
+	s = seprint(s, se, " addr %d", ep->dev->addr);
 	if(ep->inuse)
 		s = seprint(s, se, " busy");
 	else
@@ -374,29 +376,33 @@
 {
 	Udev *d;
 
-	if(ep != nil && decref(ep) == 0){
-		d = ep->dev;
-		deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
-		qlock(&epslck);
-		eps[ep->idx] = nil;
-		if(ep->idx == epmax-1)
-			epmax--;
-		if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
-			usbidgen--;
-		qunlock(&epslck);
-		if(d != nil){
-			qlock(ep->ep0);
-			d->eps[ep->nb] = nil;
-			qunlock(ep->ep0);
-		}
-		if(ep->ep0 != ep){
-			putep(ep->ep0);
-			ep->ep0 = nil;
-		}
-		free(ep->info);
-		free(ep->name);
-		free(ep);
+	if(ep == nil || decref(ep) > 0)
+		return;
+	d = ep->dev;
+	deprint("usb: ep%d.%d %#p released\n", d->nb, ep->nb, ep);
+	qlock(&epslck);
+	eps[ep->idx] = nil;
+	if(ep->idx == epmax-1)
+		epmax--;
+	if(ep == ep->ep0 && ep->dev != nil && ep->dev->nb == usbidgen)
+		usbidgen--;
+	qunlock(&epslck);
+	if(d != nil){
+		qlock(ep->ep0);
+		d->eps[ep->nb] = nil;
+		qunlock(ep->ep0);
 	}
+	if(ep->ep0 != ep){
+		putep(ep->ep0);
+		ep->ep0 = nil;
+	} else if(d != nil){
+		if(d->free != nil)
+			(*d->free)(d->aux);
+		free(d);
+	}
+	free(ep->info);
+	free(ep->name);
+	free(ep);
 }
 
 static void
@@ -460,11 +466,15 @@
 	ep = epalloc(hp);
 	d = ep->dev = smalloc(sizeof(Udev));
 	d->nb = newusbid(hp);
+	d->addr = 0;
 	d->eps[0] = ep;
 	ep->nb = 0;
 	ep->toggle[0] = ep->toggle[1] = 0;
 	d->ishub = ishub;
 	d->isroot = isroot;
+	d->rootport = 0;
+	d->routestr = 0;
+	d->depth = 0;
 	if(hp->highspeed != 0)
 		d->speed = Highspeed;
 	else
@@ -1156,8 +1166,11 @@
 		nep->dev->speed = l;
 		if(nep->dev->speed  != Lowspeed)
 			nep->maxpkt = 64;	/* assume full speed */
-		nep->dev->hub = d->nb;
+		nep->dev->hub = d->addr;
 		nep->dev->port = atoi(cb->f[2]);
+		nep->dev->depth = d->depth+1;
+		nep->dev->rootport = d->depth == 0 ? nep->dev->port : d->rootport;
+		nep->dev->routestr = d->routestr | ((nep->dev->port&15) << 4*d->depth) >> 4;
 		/* next read request will read
 		 * the name for the new endpoint
 		 */
@@ -1265,6 +1278,8 @@
 		break;
 	case CMaddress:
 		deprint("usb epctl %s\n", cb->f[0]);
+		if(ep->dev->addr == 0)
+			ep->dev->addr = ep->dev->nb;
 		ep->dev->state = Denabled;
 		break;
 	case CMdetach:
--- a/sys/src/9/port/usb.h
+++ b/sys/src/9/port/usb.h
@@ -41,6 +41,7 @@
 	Lowspeed,
 	Highspeed,
 	Nospeed,
+	Superspeed,
 
 	/* request type */
 	Rh2d = 0<<7,
@@ -184,8 +185,16 @@
 	int	ishub;		/* hubs can allocate devices */
 	int	isroot;		/* is a root hub */
 	int	speed;		/* Full/Low/High/No -speed */
-	int	hub;		/* dev number for the parent hub */
+	int	hub;		/* device address for the parent hub */
 	int	port;		/* port number in the parent hub */
+	int	addr;		/* device address */
+	int	depth;		/* hub depth from root port */
+	int	rootport;	/* port number on root hub */
+	int	routestr;	/* route string */
+
+	void	*aux;
+	void	(*free)(void*);
+
 	Ep*	eps[Ndeveps];	/* end points for this device (cached) */
 };