summaryrefslogtreecommitdiff
path: root/sys/src/9/port/usbehci.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/port/usbehci.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/port/usbehci.c')
-rwxr-xr-xsys/src/9/port/usbehci.c3204
1 files changed, 3204 insertions, 0 deletions
diff --git a/sys/src/9/port/usbehci.c b/sys/src/9/port/usbehci.c
new file mode 100755
index 000000000..e6ff879e3
--- /dev/null
+++ b/sys/src/9/port/usbehci.c
@@ -0,0 +1,3204 @@
+/*
+ * USB Enhanced Host Controller Interface (EHCI) driver
+ * High speed USB 2.0.
+ *
+ * Note that all of our unlock routines call coherence.
+ *
+ * BUGS:
+ * - Too many delays and ilocks.
+ * - bandwidth admission control must be done per-frame.
+ * - requires polling (some controllers miss interrupts).
+ * - 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"
+#include "usbehci.h"
+#include "uncached.h"
+
+#define diprint if(ehcidebug || iso->debug)print
+#define ddiprint if(ehcidebug>1 || iso->debug>1)print
+#define dqprint if(ehcidebug || (qh->io && qh->io->debug))print
+#define ddqprint if(ehcidebug>1 || (qh->io && qh->io->debug>1))print
+
+#define TRUNC(x, sz) ((x) & ((sz)-1))
+#define LPTR(q) ((ulong*)KADDR((q) & ~0x1F))
+
+typedef struct Ctlio Ctlio;
+typedef union Ed Ed;
+typedef struct Edpool Edpool;
+typedef struct Itd Itd;
+typedef struct Qio Qio;
+typedef struct Qtd Qtd;
+typedef struct Sitd Sitd;
+typedef struct Td Td;
+
+/*
+ * EHCI interface registers and bits
+ */
+enum
+{
+ /* Queue states (software) */
+ Qidle = 0,
+ Qinstall,
+ Qrun,
+ Qdone,
+ Qclose,
+ Qfree,
+
+ Enabledelay = 100, /* waiting for a port to enable */
+ Abortdelay = 5, /* delay after cancelling Tds (ms) */
+
+ Incr = 64, /* for pools of Tds, Qhs, etc. */
+ Align = 128, /* in bytes for all those descriptors */
+
+ /* Keep them as a power of 2, lower than ctlr->nframes */
+ /* Also, keep Nisoframes >= Nintrleafs */
+ Nintrleafs = 32, /* nb. of leaf frames in intr. tree */
+ Nisoframes = 64, /* nb. of iso frames (in window) */
+
+ /*
+ * HW constants
+ */
+
+ /* Itd bits (csw[]) */
+ Itdactive = 0x80000000, /* execution enabled */
+ Itddberr = 0x40000000, /* data buffer error */
+ Itdbabble = 0x20000000, /* babble error */
+ Itdtrerr = 0x10000000, /* transaction error */
+ Itdlenshift = 16, /* transaction length */
+ Itdlenmask = 0xFFF,
+ Itdioc = 0x00008000, /* interrupt on complete */
+ Itdpgshift = 12, /* page select field */
+ Itdoffshift = 0, /* transaction offset */
+ /* Itd bits, buffer[] */
+ Itdepshift = 8, /* endpoint address (buffer[0]) */
+ Itddevshift = 0, /* device address (buffer[0]) */
+ Itdin = 0x800, /* is input (buffer[1]) */
+ Itdout = 0,
+ Itdmaxpktshift = 0, /* max packet (buffer[1]) */
+ Itdntdsshift = 0, /* nb. of tds per µframe (buffer[2]) */
+
+ Itderrors = Itddberr|Itdbabble|Itdtrerr,
+
+ /* Sitd bits (epc) */
+ Stdin = 0x80000000, /* input direction */
+ Stdportshift = 24, /* hub port number */
+ Stdhubshift = 16, /* hub address */
+ Stdepshift = 8, /* endpoint address */
+ Stddevshift = 0, /* device address */
+ /* Sitd bits (mfs) */
+ Stdssmshift = 0, /* split start mask */
+ Stdscmshift = 8, /* split complete mask */
+ /* Sitd bits (csw) */
+ Stdioc = 0x80000000, /* interrupt on complete */
+ Stdpg = 0x40000000, /* page select */
+ Stdlenshift = 16, /* total bytes to transfer */
+ Stdlenmask = 0x3FF,
+ Stdactive = 0x00000080, /* active */
+ Stderr = 0x00000040, /* tr. translator error */
+ Stddberr = 0x00000020, /* data buffer error */
+ Stdbabble = 0x00000010, /* babble error */
+ Stdtrerr = 0x00000008, /* transaction error */
+ Stdmmf = 0x00000004, /* missed µframe */
+ Stddcs = 0x00000002, /* do complete split */
+
+ Stderrors = Stderr|Stddberr|Stdbabble|Stdtrerr|Stdmmf,
+
+ /* Sitd bits buffer[1] */
+ Stdtpall = 0x00000000, /* all payload here (188 bytes) */
+ Stdtpbegin = 0x00000008, /* first payload for fs trans. */
+ Stdtcntmask = 0x00000007, /* T-count */
+
+ /* Td bits (csw) */
+ Tddata1 = 0x80000000, /* data toggle 1 */
+ Tddata0 = 0x00000000, /* data toggle 0 */
+ Tdlenshift = 16, /* total bytes to transfer */
+ Tdlenmask = 0x7FFF,
+ Tdmaxpkt = 0x5000, /* max buffer for a Td */
+ Tdioc = 0x00008000, /* interrupt on complete */
+ Tdpgshift = 12, /* current page */
+ Tdpgmask = 7,
+ Tderr1 = 0x00000400, /* bit 0 of error counter */
+ Tderr2 = 0x00000800, /* bit 1 of error counter */
+ Tdtokout = 0x00000000, /* direction out */
+ Tdtokin = 0x00000100, /* direction in */
+ Tdtoksetup = 0x00000200, /* setup packet */
+ Tdtok = 0x00000300, /* token bits */
+ Tdactive = 0x00000080, /* active */
+ Tdhalt = 0x00000040, /* halted */
+ Tddberr = 0x00000020, /* data buffer error */
+ Tdbabble = 0x00000010, /* babble error */
+ Tdtrerr = 0x00000008, /* transaction error */
+ Tdmmf = 0x00000004, /* missed µframe */
+ Tddcs = 0x00000002, /* do complete split */
+ Tdping = 0x00000001, /* do ping */
+
+ Tderrors = Tdhalt|Tddberr|Tdbabble|Tdtrerr|Tdmmf,
+
+ /* Qh bits (eps0) */
+ Qhrlcmask = 0xF, /* nak reload count */
+ Qhrlcshift = 28, /* nak reload count */
+ Qhnhctl = 0x08000000, /* not-high speed ctl */
+ Qhmplmask = 0x7FF, /* max packet */
+ Qhmplshift = 16,
+ Qhhrl = 0x00008000, /* head of reclamation list */
+ Qhdtc = 0x00004000, /* data toggle ctl. */
+ Qhint = 0x00000080, /* inactivate on next transition */
+ Qhspeedmask = 0x00003000, /* speed bits */
+ Qhfull = 0x00000000, /* full speed */
+ Qhlow = 0x00001000, /* low speed */
+ Qhhigh = 0x00002000, /* high speed */
+
+ /* Qh bits (eps1) */
+ Qhmultshift = 30, /* multiple tds per µframe */
+ Qhmultmask = 3,
+ Qhportshift = 23, /* hub port number */
+ Qhhubshift = 16, /* hub address */
+ Qhscmshift = 8, /* split completion mask bits */
+ Qhismshift = 0, /* interrupt sched. mask bits */
+};
+
+/*
+ * Endpoint tree (software)
+ */
+struct Qtree
+{
+ int nel;
+ int depth;
+ ulong* bw;
+ Qh** root;
+};
+
+/*
+ * One per endpoint per direction, to control I/O.
+ */
+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; /* last I/O time; to hold interrupt polls */
+ int debug; /* debug flag from the endpoint */
+ char* err; /* error string */
+ char* tag; /* debug (no room in Qh for this) */
+ ulong bw;
+};
+
+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; /* number of frames ([S]Itds) used */
+ uchar* data; /* iso data buffers if not embedded */
+ char* err; /* error string */
+ int nerrs; /* nb of consecutive I/O errors */
+ ulong maxsize; /* ntds * ep->maxpkt */
+ long nleft; /* number of bytes left from last write */
+ int debug; /* debug flag from the endpoint */
+ int hs; /* is high speed? */
+ Isoio* next; /* in list of active Isoios */
+ ulong td0frno; /* first frame used in ctlr */
+ union{
+ Itd* tdi; /* next td processed by interrupt */
+ Sitd* stdi;
+ };
+ union{
+ Itd* tdu; /* next td for user I/O in tdps */
+ Sitd* stdu;
+ };
+ union{
+ Itd** itdps; /* itdps[i]: ptr to Itd for i-th frame or nil */
+ Sitd** sitdps; /* sitdps[i]: ptr to Sitd for i-th frame or nil */
+ ulong** tdps; /* same thing, as seen by hw */
+ };
+};
+
+struct Edpool
+{
+ Lock;
+ Ed* free;
+ int nalloc;
+ int ninuse;
+ int nfree;
+};
+
+/*
+ * We use the 64-bit version for Itd, Sitd, Td, and Qh.
+ * If the ehci is 64-bit capable it assumes we are using those
+ * structures even when the system is 32 bits.
+ */
+
+/*
+ * Iso transfer descriptor. hw: 92 bytes, 108 bytes total
+ * aligned to 32.
+ */
+struct Itd
+{
+ ulong link; /* to next hw struct */
+ ulong csw[8]; /* sts/length/pg/off. updated by hw */
+ ulong buffer[7]; /* buffer pointers, addrs, maxsz */
+ ulong xbuffer[7]; /* high 32 bits of buffer for 64-bits */
+
+ ulong _pad0; /* pad to next cache line */
+ /* cache-line boundary here */
+
+ /* software */
+ Itd* next;
+ ulong ndata; /* number of bytes in data */
+ ulong mdata; /* max number of bytes in data */
+ uchar* data;
+};
+
+/*
+ * Split transaction iso transfer descriptor.
+ * hw: 36 bytes, 52 bytes total. aligned to 32.
+ */
+struct Sitd
+{
+ ulong link; /* to next hw struct */
+ ulong epc; /* static endpoint state. addrs */
+ ulong mfs; /* static endpoint state. µ-frame sched. */
+ ulong csw; /* transfer state. updated by hw */
+ ulong buffer[2]; /* buf. ptr/offset. offset updated by hw */
+ /* buf ptr/TP/Tcnt. TP/Tcnt updated by hw */
+ ulong blink; /* back pointer */
+ /* cache-line boundary after xbuffer[0] */
+ ulong xbuffer[2]; /* high 32 bits of buffer for 64-bits */
+
+ /* software */
+ Sitd* next;
+ ulong ndata; /* number of bytes in data */
+ ulong mdata; /* max number of bytes in data */
+ uchar* data;
+};
+
+/*
+ * Queue element transfer descriptor.
+ * hw: first 52 bytes, total 68+sbuff bytes. aligned to 32 bytes.
+ */
+struct Td
+{
+ ulong nlink; /* to next Td */
+ ulong alink; /* alternate link to next Td */
+ ulong csw; /* cmd/sts. updated by hw */
+ ulong buffer[5]; /* buf ptrs. offset updated by hw */
+ /* cache-line boundary here */
+ ulong xbuffer[5]; /* high 32 bits of buffer for 64-bits */
+
+ /* software */
+ Td* next; /* in qh or Isoio or free list */
+ ulong ndata; /* bytes available/used at data */
+ uchar* data; /* pointer to actual data */
+ uchar* buff; /* allocated data buffer or nil */
+ uchar sbuff[1]; /* first byte of embedded buffer */
+};
+
+/*
+ * Queue head. Aligned to 32 bytes.
+ * hw: first 68 bytes, 92 total.
+ */
+struct Qh
+{
+ ulong link; /* to next Qh in round robin */
+ ulong eps0; /* static endpoint state. addrs */
+ ulong eps1; /* static endpoint state. µ-frame sched. */
+
+ /* updated by hw */
+ ulong tclink; /* current Td (No Term bit here!) */
+ ulong nlink; /* to next Td */
+ ulong alink; /* alternate link to next Td */
+ ulong csw; /* cmd/sts. updated by hw */
+ /* cache-line boundary after buffer[0] */
+ ulong buffer[5]; /* buf ptrs. offset updated by hw */
+ ulong xbuffer[5]; /* high 32 bits of buffer for 64-bits */
+
+ /* software */
+ Qh* next; /* in controller list/tree of Qhs */
+ int state; /* Qidle -> Qinstall -> Qrun -> Qdone | Qclose */
+ Qio* io; /* for this queue */
+ Td* tds; /* for this queue */
+ int sched; /* slot for for intr. Qhs */
+ Qh* inext; /* next in list of intr. qhs */
+};
+
+/*
+ * We can avoid frame span traversal nodes if we don't span frames.
+ * Just schedule transfers that can fit on the current frame and
+ * wait a little bit otherwise.
+ */
+
+/*
+ * Software. Ehci descriptors provided by pool.
+ * There are soo few because we avoid using Fstn.
+ */
+union Ed
+{
+ Ed* next; /* in free list */
+ Qh qh;
+ Td td;
+ Itd itd;
+ Sitd sitd;
+ uchar align[Align];
+};
+
+int ehcidebug;
+
+static Edpool edpool;
+static char Ebug[] = "not yet implemented";
+static char* qhsname[] = { "idle", "install", "run", "done", "close", "FREE" };
+
+Ecapio* ehcidebugcapio;
+int ehcidebugport;
+
+void
+ehcirun(Ctlr *ctlr, int on)
+{
+ int i;
+ Eopio *opio;
+
+ ddprint("ehci %#p %s\n", ctlr->capio, on ? "starting" : "halting");
+ opio = ctlr->opio;
+ if(on)
+ opio->cmd |= Crun;
+ else
+ opio->cmd = Cstop;
+ coherence();
+ for(i = 0; i < 100; i++)
+ if(on == 0 && (opio->sts & Shalted) != 0)
+ break;
+ else if(on != 0 && (opio->sts & Shalted) == 0)
+ break;
+ else
+ delay(1);
+ if(i == 100)
+ print("ehci %#p %s cmd timed out\n",
+ ctlr->capio, on ? "run" : "halt");
+ ddprint("ehci %#p cmd %#lux sts %#lux\n",
+ ctlr->capio, opio->cmd, opio->sts);
+}
+
+static void*
+edalloc(void)
+{
+ Ed *ed, *pool;
+ int i;
+
+ lock(&edpool);
+ if(edpool.free == nil){
+ pool = xspanalloc(Incr*sizeof(Ed), Align, 0);
+ if(pool == nil)
+ panic("edalloc");
+ for(i=Incr; --i>=0;){
+ pool[i].next = edpool.free;
+ edpool.free = &pool[i];
+ }
+ edpool.nalloc += Incr;
+ edpool.nfree += Incr;
+ dprint("ehci: edalloc: %d eds\n", edpool.nalloc);
+ }
+ ed = edpool.free;
+ edpool.free = ed->next;
+ edpool.ninuse++;
+ edpool.nfree--;
+ unlock(&edpool);
+
+ memset(ed, 0, sizeof(Ed)); /* safety */
+ assert(((ulong)ed & 0xF) == 0);
+ return ed;
+}
+
+static void
+edfree(void *a)
+{
+ Ed *ed;
+
+ ed = a;
+ lock(&edpool);
+ ed->next = edpool.free;
+ edpool.free = ed;
+ edpool.ninuse--;
+ edpool.nfree++;
+ unlock(&edpool);
+}
+
+/*
+ * Allocate and do some initialization.
+ * Free after releasing buffers used.
+ */
+
+static Itd*
+itdalloc(void)
+{
+ Itd *td;
+
+ td = edalloc();
+ td->link = Lterm;
+ return td;
+}
+
+static void
+itdfree(Itd *td)
+{
+ edfree(td);
+}
+
+static Sitd*
+sitdalloc(void)
+{
+ Sitd *td;
+
+ td = edalloc();
+ td->link = td->blink = Lterm;
+ return td;
+}
+
+static void
+sitdfree(Sitd *td)
+{
+ edfree(td);
+}
+
+static Td*
+tdalloc(void)
+{
+ Td *td;
+
+ td = edalloc();
+ td->nlink = td->alink = Lterm;
+ return td;
+}
+
+static void
+tdfree(Td *td)
+{
+ if(td == nil)
+ return;
+ free(td->buff);
+ edfree(td);
+}
+
+static void
+tdlinktd(Td *td, Td *next)
+{
+ td->next = next;
+ td->alink = Lterm;
+ if(next == nil)
+ td->nlink = Lterm;
+ else
+ td->nlink = PADDR(next);
+ coherence();
+}
+
+static Qh*
+qhlinkqh(Qh *qh, Qh *next)
+{
+ qh->next = next;
+ qh->link = PADDR(next)|Lqh;
+ coherence();
+ return qh;
+}
+
+static void
+qhsetaddr(Qh *qh, ulong addr)
+{
+ ulong eps0;
+
+ eps0 = qh->eps0 & ~((Epmax<<8)|Devmax);
+ qh->eps0 = eps0 | addr & Devmax | ((addr >> 7) & Epmax) << 8;
+ coherence();
+}
+
+/*
+ * 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 pollival, ulong bw, ulong limit)
+{
+ int i, j, d, upperb, q;
+ ulong best, worst, total;
+
+ d = flog2lower(pollival);
+ 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
+schedq(Ctlr *ctlr, Qh *qh, int pollival)
+{
+ int q;
+ Qh *tqh;
+ ulong bw;
+
+ bw = qh->io->bw;
+ q = pickschedq(ctlr->tree, pollival, 0, ~0);
+ ddqprint("ehci: sched %#p q %d, ival %d, bw %uld\n",
+ qh->io, q, pollival, bw);
+ if(q < 0){
+ print("ehci: no room for ed\n");
+ return -1;
+ }
+ ctlr->tree->bw[q] += bw;
+ tqh = ctlr->tree->root[q];
+ qh->sched = q;
+ qhlinkqh(qh, tqh->next);
+ qhlinkqh(tqh, qh);
+ coherence();
+ qh->inext = ctlr->intrqhs;
+ ctlr->intrqhs = qh;
+ coherence();
+ return 0;
+}
+
+static void
+unschedq(Ctlr *ctlr, Qh *qh)
+{
+ int q;
+ Qh *prev, *this, *next;
+ Qh **l;
+ ulong bw;
+
+ bw = qh->io->bw;
+ q = qh->sched;
+ if(q < 0)
+ return;
+ ctlr->tree->bw[q] -= bw;
+
+ prev = ctlr->tree->root[q];
+ this = prev->next;
+ while(this != nil && this != qh){
+ prev = this;
+ this = this->next;
+ }
+ if(this == nil)
+ print("ehci: unschedq %d: not found\n", q);
+ else{
+ next = this->next;
+ qhlinkqh(prev, next);
+ }
+ for(l = &ctlr->intrqhs; *l != nil; l = &(*l)->inext)
+ if(*l == qh){
+ *l = (*l)->inext;
+ return;
+ }
+ print("ehci: unschedq: qh %#p not found\n", qh);
+}
+
+static ulong
+qhmaxpkt(Qh *qh)
+{
+ return (qh->eps0 >> Qhmplshift) & Qhmplmask;
+}
+
+static void
+qhsetmaxpkt(Qh *qh, int maxpkt)
+{
+ ulong eps0;
+
+ eps0 = qh->eps0 & ~(Qhmplmask << Qhmplshift);
+ qh->eps0 = eps0 | (maxpkt & Qhmplmask) << Qhmplshift;
+ coherence();
+}
+
+/*
+ * Initialize the round-robin circular list of ctl/bulk Qhs
+ * if ep is nil. Otherwise, allocate and link a new Qh in the ctlr.
+ */
+static Qh*
+qhalloc(Ctlr *ctlr, Ep *ep, Qio *io, char* tag)
+{
+ Qh *qh;
+ int ttype;
+
+ qh = edalloc();
+ qh->nlink = Lterm;
+ qh->alink = Lterm;
+ qh->csw = Tdhalt;
+ qh->state = Qidle;
+ qh->sched = -1;
+ qh->io = io;
+ if(ep != nil){
+ qh->eps0 = 0;
+ qhsetmaxpkt(qh, ep->maxpkt);
+ if(ep->dev->speed == Lowspeed)
+ qh->eps0 |= Qhlow;
+ if(ep->dev->speed == Highspeed)
+ qh->eps0 |= Qhhigh;
+ else if(ep->ttype == Tctl)
+ qh->eps0 |= Qhnhctl;
+ qh->eps0 |= Qhdtc | 8 << Qhrlcshift; /* 8 naks max */
+ coherence();
+ qhsetaddr(qh, io->usbid);
+ qh->eps1 = (ep->ntds & Qhmultmask) << Qhmultshift;
+ qh->eps1 |= ep->dev->port << Qhportshift;
+ qh->eps1 |= ep->dev->hub << Qhhubshift;
+ qh->eps1 |= 034 << Qhscmshift;
+ if(ep->ttype == Tintr)
+ qh->eps1 |= 1 << Qhismshift; /* intr. start µf. */
+ coherence();
+ if(io != nil)
+ io->tag = tag;
+ }
+ ilock(ctlr);
+ ttype = Tctl;
+ if(ep != nil)
+ ttype = ep->ttype;
+ switch(ttype){
+ case Tctl:
+ case Tbulk:
+ if(ctlr->qhs == nil){
+ ctlr->qhs = qhlinkqh(qh, qh);
+ qh->eps0 |= Qhhigh | Qhhrl;
+ coherence();
+ ctlr->opio->link = PADDR(qh)|Lqh;
+ coherence();
+ }else{
+ qhlinkqh(qh, ctlr->qhs->next);
+ qhlinkqh(ctlr->qhs, qh);
+ }
+ break;
+ case Tintr:
+ schedq(ctlr, qh, ep->pollival);
+ break;
+ default:
+ print("ehci: qhalloc called for ttype != ctl/bulk\n");
+ }
+ iunlock(ctlr);
+ return qh;
+}
+
+static int
+qhadvanced(void *a)
+{
+ Ctlr *ctlr;
+
+ ctlr = a;
+ return (ctlr->opio->cmd & Ciasync) == 0;
+}
+
+/*
+ * called when a qh is removed, to be sure the hw is not
+ * keeping pointers into it.
+ */
+static void
+qhcoherency(Ctlr *ctlr)
+{
+ int i;
+
+ qlock(&ctlr->portlck);
+ ctlr->opio->cmd |= Ciasync; /* ask for intr. on async advance */
+ coherence();
+ for(i = 0; i < 3 && qhadvanced(ctlr) == 0; i++)
+ if(!waserror()){
+ tsleep(ctlr, qhadvanced, ctlr, Abortdelay);
+ poperror();
+ }
+ dprint("ehci: qhcoherency: doorbell %d\n", qhadvanced(ctlr));
+ if(i == 3)
+ print("ehci: async advance doorbell did not ring\n");
+ ctlr->opio->cmd &= ~Ciasync; /* try to clean */
+ qunlock(&ctlr->portlck);
+}
+
+static void
+qhfree(Ctlr *ctlr, Qh *qh)
+{
+ Td *td, *ltd;
+ Qh *q;
+
+ if(qh == nil)
+ return;
+ ilock(ctlr);
+ if(qh->sched < 0){
+ 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;
+ coherence();
+ }else
+ unschedq(ctlr, qh);
+ iunlock(ctlr);
+
+ qhcoherency(ctlr);
+
+ for(td = qh->tds; td != nil; td = ltd){
+ ltd = td->next;
+ tdfree(td);
+ }
+
+ edfree(qh);
+}
+
+static void
+qhlinktd(Qh *qh, Td *td)
+{
+ ulong csw;
+ int i;
+
+ csw = qh->csw;
+ qh->tds = td;
+ if(td == nil)
+ qh->csw = (csw & ~Tdactive) | Tdhalt;
+ else{
+ csw &= Tddata1 | Tdping; /* save */
+ qh->csw = Tdhalt;
+ coherence();
+ qh->tclink = 0;
+ qh->alink = Lterm;
+ qh->nlink = PADDR(td);
+ for(i = 0; i < nelem(qh->buffer); i++)
+ qh->buffer[i] = 0;
+ coherence();
+ qh->csw = csw & ~(Tdhalt|Tdactive); /* activate next */
+ }
+ coherence();
+}
+
+static char*
+seprintlink(char *s, char *se, char *name, ulong l, int typed)
+{
+ s = seprint(s, se, "%s %ulx", name, l);
+ if((l & Lterm) != 0)
+ return seprint(s, se, "T");
+ if(typed == 0)
+ return s;
+ switch(l & (3<<1)){
+ case Litd:
+ return seprint(s, se, "I");
+ case Lqh:
+ return seprint(s, se, "Q");
+ case Lsitd:
+ return seprint(s, se, "S");
+ default:
+ return seprint(s, se, "F");
+ }
+}
+
+static char*
+seprintitd(char *s, char *se, Itd *td)
+{
+ int i;
+ ulong b0, b1;
+ char flags[6];
+ char *rw;
+
+ if(td == nil)
+ return seprint(s, se, "<nil itd>\n");
+ b0 = td->buffer[0];
+ b1 = td->buffer[1];
+
+ s = seprint(s, se, "itd %#p", td);
+ rw = (b1 & Itdin) ? "in" : "out";
+ s = seprint(s, se, " %s ep %uld dev %uld max %uld mult %uld",
+ rw, (b0>>8)&Epmax, (b0&Devmax),
+ td->buffer[1] & 0x7ff, b1 & 3);
+ s = seprintlink(s, se, " link", td->link, 1);
+ s = seprint(s, se, "\n");
+ for(i = 0; i < nelem(td->csw); i++){
+ memset(flags, '-', 5);
+ if((td->csw[i] & Itdactive) != 0)
+ flags[0] = 'a';
+ if((td->csw[i] & Itdioc) != 0)
+ flags[1] = 'i';
+ if((td->csw[i] & Itddberr) != 0)
+ flags[2] = 'd';
+ if((td->csw[i] & Itdbabble) != 0)
+ flags[3] = 'b';
+ if((td->csw[i] & Itdtrerr) != 0)
+ flags[4] = 't';
+ flags[5] = 0;
+ s = seprint(s, se, "\ttd%d %s", i, flags);
+ s = seprint(s, se, " len %uld", (td->csw[i] >> 16) & 0x7ff);
+ s = seprint(s, se, " pg %uld", (td->csw[i] >> 12) & 0x7);
+ s = seprint(s, se, " off %uld\n", td->csw[i] & 0xfff);
+ }
+ s = seprint(s, se, "\tbuffs:");
+ for(i = 0; i < nelem(td->buffer); i++)
+ s = seprint(s, se, " %#lux", td->buffer[i] >> 12);
+ return seprint(s, se, "\n");
+}
+
+static char*
+seprintsitd(char *s, char *se, Sitd *td)
+{
+ char rw, pg, ss;
+ char flags[8];
+ static char pc[4] = { 'a', 'b', 'm', 'e' };
+
+ if(td == nil)
+ return seprint(s, se, "<nil sitd>\n");
+ s = seprint(s, se, "sitd %#p", td);
+ rw = (td->epc & Stdin) ? 'r' : 'w';
+ s = seprint(s, se, " %c ep %uld dev %uld",
+ rw, (td->epc>>8)&0xf, td->epc&0x7f);
+ s = seprint(s, se, " max %uld", (td->csw >> 16) & 0x3ff);
+ s = seprint(s, se, " hub %uld", (td->epc >> 16) & 0x7f);
+ s = seprint(s, se, " port %uld\n", (td->epc >> 24) & 0x7f);
+ memset(flags, '-', 7);
+ if((td->csw & Stdactive) != 0)
+ flags[0] = 'a';
+ if((td->csw & Stdioc) != 0)
+ flags[1] = 'i';
+ if((td->csw & Stderr) != 0)
+ flags[2] = 'e';
+ if((td->csw & Stddberr) != 0)
+ flags[3] = 'd';
+ if((td->csw & Stdbabble) != 0)
+ flags[4] = 'b';
+ if((td->csw & Stdtrerr) != 0)
+ flags[5] = 't';
+ if((td->csw & Stdmmf) != 0)
+ flags[6] = 'n';
+ flags[7] = 0;
+ ss = (td->csw & Stddcs) ? 'c' : 's';
+ pg = (td->csw & Stdpg) ? '1' : '0';
+ s = seprint(s, se, "\t%s %cs pg%c", flags, ss, pg);
+ s = seprint(s, se, " b0 %#lux b1 %#lux off %uld\n",
+ td->buffer[0] >> 12, td->buffer[1] >> 12, td->buffer[0] & 0xfff);
+ s = seprint(s, se, "\ttpos %c tcnt %uld",
+ pc[(td->buffer[0]>>3)&3], td->buffer[1] & 7);
+ s = seprint(s, se, " ssm %#lux csm %#lux cspm %#lux",
+ td->mfs & 0xff, (td->mfs>>8) & 0xff, (td->csw>>8) & 0xff);
+ s = seprintlink(s, se, " link", td->link, 1);
+ s = seprintlink(s, se, " blink", td->blink, 0);
+ return seprint(s, se, "\n");
+}
+
+static long
+maxtdlen(Td *td)
+{
+ return (td->csw >> Tdlenshift) & Tdlenmask;
+}
+
+static long
+tdlen(Td *td)
+{
+ if(td->data == nil)
+ return 0;
+ return td->ndata - maxtdlen(td);
+}
+
+static char*
+seprinttd(char *s, char *se, Td *td, char *tag)
+{
+ int i;
+ char t, ss;
+ char flags[9];
+ static char *tok[4] = { "out", "in", "setup", "BUG" };
+
+ if(td == nil)
+ return seprint(s, se, "%s <nil td>\n", tag);
+ s = seprint(s, se, "%s %#p", tag, td);
+ s = seprintlink(s, se, " nlink", td->nlink, 0);
+ s = seprintlink(s, se, " alink", td->alink, 0);
+ s = seprint(s, se, " %s", tok[(td->csw & Tdtok) >> 8]);
+ if((td->csw & Tdping) != 0)
+ s = seprint(s, se, " png");
+ memset(flags, '-', 8);
+ if((td->csw & Tdactive) != 0)
+ flags[0] = 'a';
+ if((td->csw & Tdioc) != 0)
+ flags[1] = 'i';
+ if((td->csw & Tdhalt) != 0)
+ flags[2] = 'h';
+ if((td->csw & Tddberr) != 0)
+ flags[3] = 'd';
+ if((td->csw & Tdbabble) != 0)
+ flags[4] = 'b';
+ if((td->csw & Tdtrerr) != 0)
+ flags[5] = 't';
+ if((td->csw & Tdmmf) != 0)
+ flags[6] = 'n';
+ if((td->csw & (Tderr2|Tderr1)) == 0)
+ flags[7] = 'z';
+ flags[8] = 0;
+ t = (td->csw & Tddata1) ? '1' : '0';
+ ss = (td->csw & Tddcs) ? 'c' : 's';
+ s = seprint(s, se, "\n\td%c %s %cs", t, flags, ss);
+ s = seprint(s, se, " max %uld", maxtdlen(td));
+ s = seprint(s, se, " pg %uld off %#lux\n",
+ (td->csw >> Tdpgshift) & Tdpgmask, td->buffer[0] & 0xFFF);
+ s = seprint(s, se, "\tbuffs:");
+ for(i = 0; i < nelem(td->buffer); i++)
+ s = seprint(s, se, " %#lux", td->buffer[i]>>12);
+ if(td->data != nil)
+ s = seprintdata(s, se, td->data, td->ndata);
+ return seprint(s, se, "\n");
+}
+
+static void
+dumptd(Td *td, char *pref)
+{
+ char buf[256];
+ char *se;
+ int i;
+
+ i = 0;
+ se = buf+sizeof(buf);
+ for(; td != nil; td = td->next){
+ seprinttd(buf, se, td, pref);
+ print("%s", buf);
+ if(i++ > 20){
+ print("...more tds...\n");
+ break;
+ }
+ }
+}
+
+static void
+qhdump(Qh *qh)
+{
+ char buf[256];
+ char *s, *se, *tag;
+ Td td;
+ static char *speed[] = {"full", "low", "high", "BUG"};
+
+ if(qh == nil){
+ print("<nil qh>\n");
+ return;
+ }
+ if(qh->io == nil)
+ tag = "qh";
+ else
+ tag = qh->io->tag;
+ se = buf+sizeof(buf);
+ s = seprint(buf, se, "%s %#p", tag, qh);
+ s = seprint(s, se, " ep %uld dev %uld",
+ (qh->eps0>>8)&0xf, qh->eps0&0x7f);
+ s = seprint(s, se, " hub %uld", (qh->eps1 >> 16) & 0x7f);
+ s = seprint(s, se, " port %uld", (qh->eps1 >> 23) & 0x7f);
+ s = seprintlink(s, se, " link", qh->link, 1);
+ seprint(s, se, " clink %#lux", qh->tclink);
+ print("%s\n", buf);
+ s = seprint(buf, se, "\tnrld %uld", (qh->eps0 >> Qhrlcshift) & Qhrlcmask);
+ s = seprint(s, se, " nak %uld", (qh->alink >> 1) & 0xf);
+ s = seprint(s, se, " max %uld ", qhmaxpkt(qh));
+ if((qh->eps0 & Qhnhctl) != 0)
+ s = seprint(s, se, "c");
+ if((qh->eps0 & Qhhrl) != 0)
+ s = seprint(s, se, "h");
+ if((qh->eps0 & Qhdtc) != 0)
+ s = seprint(s, se, "d");
+ if((qh->eps0 & Qhint) != 0)
+ s = seprint(s, se, "i");
+ s = seprint(s, se, " %s", speed[(qh->eps0 >> 12) & 3]);
+ s = seprint(s, se, " mult %uld", (qh->eps1 >> Qhmultshift) & Qhmultmask);
+ seprint(s, se, " scm %#lux ism %#lux\n",
+ (qh->eps1 >> 8 & 0xff), qh->eps1 & 0xff);
+ print("%s\n", buf);
+ memset(&td, 0, sizeof(td));
+ memmove(&td, &qh->nlink, 32); /* overlay area */
+ seprinttd(buf, se, &td, "\tovl");
+ print("%s", buf);
+}
+
+static void
+isodump(Isoio* iso, int all)
+{
+ Itd *td, *tdi, *tdu;
+ Sitd *std, *stdi, *stdu;
+ char buf[256];
+ int i;
+
+ if(iso == nil){
+ print("<nil iso>\n");
+ return;
+ }
+ print("iso %#p %s %s speed state %d nframes %d maxsz %uld",
+ iso, iso->tok == Tdtokin ? "in" : "out",
+ iso->hs ? "high" : "full",
+ iso->state, iso->nframes, iso->maxsize);
+ print(" td0 %uld tdi %#p tdu %#p data %#p\n",
+ iso->td0frno, iso->tdi, iso->tdu, iso->data);
+ if(iso->err != nil)
+ print("\terr %s\n", iso->err);
+ if(iso->err != nil)
+ print("\terr='%s'\n", iso->err);
+ if(all == 0)
+ if(iso->hs != 0){
+ tdi = iso->tdi;
+ seprintitd(buf, buf+sizeof(buf), tdi);
+ print("\ttdi %s\n", buf);
+ tdu = iso->tdu;
+ seprintitd(buf, buf+sizeof(buf), tdu);
+ print("\ttdu %s\n", buf);
+ }else{
+ stdi = iso->stdi;
+ seprintsitd(buf, buf+sizeof(buf), stdi);
+ print("\tstdi %s\n", buf);
+ stdu = iso->stdu;
+ seprintsitd(buf, buf+sizeof(buf), stdu);
+ print("\tstdu %s\n", buf);
+ }
+ else
+ for(i = 0; i < Nisoframes; i++)
+ if(iso->tdps[i] != nil)
+ if(iso->hs != 0){
+ td = iso->itdps[i];
+ seprintitd(buf, buf+sizeof(buf), td);
+ if(td == iso->tdi)
+ print("i->");
+ if(td == iso->tdu)
+ print("i->");
+ print("[%d]\t%s", i, buf);
+ }else{
+ std = iso->sitdps[i];
+ seprintsitd(buf, buf+sizeof(buf), std);
+ if(std == iso->stdi)
+ print("i->");
+ if(std == iso->stdu)
+ print("u->");
+ print("[%d]\t%s", i, buf);
+ }
+}
+
+static void
+dump(Hci *hp)
+{
+ int i;
+ char *s, *se;
+ char buf[128];
+ Ctlr *ctlr;
+ Eopio *opio;
+ Isoio *iso;
+ Qh *qh;
+
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+ ilock(ctlr);
+ print("ehci port %#p frames %#p (%d fr.) nintr %d ntdintr %d",
+ ctlr->capio, ctlr->frames, ctlr->nframes,
+ ctlr->nintr, ctlr->ntdintr);
+ print(" nqhintr %d nisointr %d\n", ctlr->nqhintr, ctlr->nisointr);
+ print("\tcmd %#lux sts %#lux intr %#lux frno %uld",
+ opio->cmd, opio->sts, opio->intr, opio->frno);
+ print(" base %#lux link %#lux fr0 %#lux\n",
+ opio->frbase, opio->link, ctlr->frames[0]);
+ se = buf+sizeof(buf);
+ s = seprint(buf, se, "\t");
+ for(i = 0; i < hp->nports; i++){
+ s = seprint(s, se, "p%d %#lux ", i, opio->portsc[i]);
+ if(hp->nports > 4 && i == hp->nports/2 - 1)
+ s = seprint(s, se, "\n\t");
+ }
+ print("%s\n", buf);
+ qh = ctlr->qhs;
+ i = 0;
+ do{
+ qhdump(qh);
+ qh = qh->next;
+ }while(qh != ctlr->qhs && i++ < 100);
+ if(i > 100)
+ print("...too many Qhs...\n");
+ if(ctlr->intrqhs != nil)
+ print("intr qhs:\n");
+ for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+ qhdump(qh);
+ if(ctlr->iso != nil)
+ print("iso:\n");
+ for(iso = ctlr->iso; iso != nil; iso = iso->next)
+ isodump(ctlr->iso, 0);
+ print("%d eds in tree\n", ctlr->ntree);
+ iunlock(ctlr);
+ lock(&edpool);
+ print("%d eds allocated = %d in use + %d free\n",
+ edpool.nalloc, edpool.ninuse, edpool.nfree);
+ unlock(&edpool);
+}
+
+static char*
+errmsg(int err)
+{
+ if(err == 0)
+ return "ok";
+ if(err & Tddberr)
+ return "data buffer error";
+ if(err & Tdbabble)
+ return "babble detected";
+ if(err & Tdtrerr)
+ return "transaction error";
+ if(err & Tdmmf)
+ return "missed µframe";
+ if(err & Tdhalt)
+ return Estalled; /* [uo]hci report this error */
+ return Eio;
+}
+
+static char*
+ierrmsg(int err)
+{
+ if(err == 0)
+ return "ok";
+ if(err & Itddberr)
+ return "data buffer error";
+ if(err & Itdbabble)
+ return "babble detected";
+ if(err & Itdtrerr)
+ return "transaction error";
+ return Eio;
+}
+
+static char*
+serrmsg(int err)
+{
+ if(err & Stderr)
+ return "translation translator error";
+ /* other errors have same numbers than Td errors */
+ return errmsg(err);
+}
+
+static int
+isocanread(void *a)
+{
+ Isoio *iso;
+
+ iso = a;
+ if(iso->state == Qclose)
+ return 1;
+ if(iso->state == Qrun && iso->tok == Tdtokin){
+ if(iso->hs != 0 && iso->tdi != iso->tdu)
+ return 1;
+ if(iso->hs == 0 && iso->stdi != iso->stdu)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+isocanwrite(void *a)
+{
+ Isoio *iso;
+
+ iso = a;
+ if(iso->state == Qclose)
+ return 1;
+ if(iso->state == Qrun && iso->tok == Tdtokout){
+ if(iso->hs != 0 && iso->tdu->next != iso->tdi)
+ return 1;
+ if(iso->hs == 0 && iso->stdu->next != iso->stdi)
+ return 1;
+ }
+ return 0;
+}
+
+static void
+itdinit(Isoio *iso, Itd *td)
+{
+ int p, t;
+ ulong pa, tsize, size;
+
+ /*
+ * BUG: This does not put an integral number of samples
+ * on each µframe unless samples per packet % 8 == 0
+ * Also, all samples are packed early on each frame.
+ */
+ p = 0;
+ size = td->ndata = td->mdata;
+ pa = PADDR(td->data);
+ for(t = 0; size > 0 && t < 8; t++){
+ tsize = size;
+ if(tsize > iso->maxsize)
+ tsize = iso->maxsize;
+ size -= tsize;
+ assert(p < nelem(td->buffer));
+ td->csw[t] = tsize << Itdlenshift | p << Itdpgshift |
+ (pa & 0xFFF) << Itdoffshift | Itdactive | Itdioc;
+ coherence();
+ if(((pa+tsize) & ~0xFFF) != (pa & ~0xFFF))
+ p++;
+ pa += tsize;
+ }
+}
+
+static void
+sitdinit(Isoio *iso, Sitd *td)
+{
+ td->ndata = td->mdata & Stdlenmask;
+ td->buffer[0] = PADDR(td->data);
+ td->buffer[1] = (td->buffer[0] & ~0xFFF) + 0x1000;
+ if(iso->tok == Tdtokin || td->ndata <= 188)
+ td->buffer[1] |= Stdtpall;
+ else
+ td->buffer[1] |= Stdtpbegin;
+ if(iso->tok == Tdtokin)
+ td->buffer[1] |= 1;
+ else
+ td->buffer[1] |= ((td->ndata + 187) / 188) & Stdtcntmask;
+ coherence();
+ td->csw = td->ndata << Stdlenshift | Stdactive | Stdioc;
+ coherence();
+}
+
+static int
+itdactive(Itd *td)
+{
+ int i;
+
+ for(i = 0; i < nelem(td->csw); i++)
+ if((td->csw[i] & Itdactive) != 0)
+ return 1;
+ return 0;
+}
+
+static int
+isohsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+ int err, i, nframes, t;
+ Itd *tdi;
+
+ tdi = iso->tdi;
+ assert(tdi != nil);
+ if(itdactive(tdi)) /* not all tds are done */
+ return 0;
+ ctlr->nisointr++;
+ ddiprint("isohsintr: iso %#p: tdi %#p tdu %#p\n", iso, tdi, iso->tdu);
+ if(iso->state != Qrun && iso->state != Qdone)
+ panic("isofsintr: iso state");
+ if(ehcidebug > 1 || iso->debug > 1)
+ isodump(iso, 0);
+
+ nframes = iso->nframes / 2; /* limit how many we look */
+ if(nframes > Nisoframes)
+ nframes = Nisoframes;
+
+ if(iso->tok == Tdtokin)
+ tdi->ndata = 0;
+ /* else, it has the number of bytes transferred */
+
+ for(i = 0; i < nframes && itdactive(tdi) == 0; i++){
+ if(iso->tok == Tdtokin)
+ tdi->ndata += (tdi->csw[i] >> Itdlenshift) & Itdlenmask;
+ err = 0;
+ coherence();
+ for(t = 0; t < nelem(tdi->csw); t++){
+ tdi->csw[t] &= ~Itdioc;
+ coherence();
+ err |= tdi->csw[t] & Itderrors;
+ }
+ if(err == 0)
+ iso->nerrs = 0;
+ else if(iso->nerrs++ > iso->nframes/2){
+ if(iso->err == nil){
+ iso->err = ierrmsg(err);
+ diprint("isohsintr: tdi %#p error %#ux %s\n",
+ tdi, err, iso->err);
+ diprint("ctlr load %uld\n", ctlr->load);
+ }
+ tdi->ndata = 0;
+ }else
+ tdi->ndata = 0;
+ if(tdi->next == iso->tdu || tdi->next->next == iso->tdu){
+ memset(iso->tdu->data, 0, iso->tdu->mdata);
+ itdinit(iso, iso->tdu);
+ iso->tdu = iso->tdu->next;
+ iso->nleft = 0;
+ }
+ tdi = tdi->next;
+ coherence();
+ }
+ ddiprint("isohsintr: %d frames processed\n", nframes);
+ if(i == nframes){
+ tdi->csw[0] |= Itdioc;
+ coherence();
+ }
+ iso->tdi = tdi;
+ coherence();
+ if(isocanwrite(iso) || isocanread(iso)){
+ diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+ iso->tdi, iso->tdu);
+ wakeup(iso);
+ }
+ return 1;
+}
+
+static int
+isofsinterrupt(Ctlr *ctlr, Isoio *iso)
+{
+ int err, i, nframes;
+ Sitd *stdi;
+
+ stdi = iso->stdi;
+ assert(stdi != nil);
+ if((stdi->csw & Stdactive) != 0) /* nothing new done */
+ return 0;
+ ctlr->nisointr++;
+ ddiprint("isofsintr: iso %#p: tdi %#p tdu %#p\n", iso, stdi, iso->stdu);
+ if(iso->state != Qrun && iso->state != Qdone)
+ panic("isofsintr: iso state");
+ if(ehcidebug > 1 || iso->debug > 1)
+ isodump(iso, 0);
+
+ nframes = iso->nframes / 2; /* limit how many we look */
+ if(nframes > Nisoframes)
+ nframes = Nisoframes;
+
+ for(i = 0; i < nframes && (stdi->csw & Stdactive) == 0; i++){
+ stdi->csw &= ~Stdioc;
+ /* write back csw and see if it produces errors */
+ coherence();
+ err = stdi->csw & Stderrors;
+ if(err == 0){
+ iso->nerrs = 0;
+ if(iso->tok == Tdtokin)
+ stdi->ndata = (stdi->csw>>Stdlenshift)&Stdlenmask;
+ /* else len is assumed correct */
+ }else if(iso->nerrs++ > iso->nframes/2){
+ if(iso->err == nil){
+ iso->err = serrmsg(err);
+ diprint("isofsintr: tdi %#p error %#ux %s\n",
+ stdi, err, iso->err);
+ diprint("ctlr load %uld\n", ctlr->load);
+ }
+ stdi->ndata = 0;
+ }else
+ stdi->ndata = 0;
+
+ if(stdi->next == iso->stdu || stdi->next->next == iso->stdu){
+ memset(iso->stdu->data, 0, iso->stdu->mdata);
+ coherence();
+ sitdinit(iso, iso->stdu);
+ iso->stdu = iso->stdu->next;
+ iso->nleft = 0;
+ }
+ coherence();
+ stdi = stdi->next;
+ }
+ ddiprint("isofsintr: %d frames processed\n", nframes);
+ if(i == nframes){
+ stdi->csw |= Stdioc;
+ coherence();
+ }
+ iso->stdi = stdi;
+ coherence();
+ if(isocanwrite(iso) || isocanread(iso)){
+ diprint("wakeup iso %#p tdi %#p tdu %#p\n", iso,
+ iso->stdi, iso->stdu);
+ wakeup(iso);
+ }
+ return 1;
+}
+
+static int
+qhinterrupt(Ctlr *ctlr, Qh *qh)
+{
+ Td *td;
+ int err;
+
+ if(qh->state != Qrun)
+ panic("qhinterrupt: qh state");
+ td = qh->tds;
+ if(td == nil)
+ panic("qhinterrupt: no tds");
+ if((td->csw & Tdactive) == 0)
+ ddqprint("qhinterrupt port %#p qh %#p\n", ctlr->capio, qh);
+ for(; td != nil; td = td->next){
+ if(td->csw & Tdactive)
+ return 0;
+ err = td->csw & Tderrors;
+ if(err != 0){
+ if(qh->io->err == nil){
+ qh->io->err = errmsg(err);
+ dqprint("qhintr: td %#p csw %#lux error %#ux %s\n",
+ td, td->csw, err, qh->io->err);
+ }
+ break;
+ }
+ td->ndata = tdlen(td);
+ coherence();
+ if(td->ndata < maxtdlen(td)){ /* EOT */
+ td = td->next;
+ break;
+ }
+ }
+ /*
+ * Done. Make void the Tds not used (errors or EOT) and wakeup epio.
+ */
+ for(; td != nil; td = td->next)
+ td->ndata = 0;
+ coherence();
+ qh->state = Qdone;
+ coherence();
+ wakeup(qh->io);
+ return 1;
+}
+
+static int
+ehciintr(Hci *hp)
+{
+ Ctlr *ctlr;
+ Eopio *opio;
+ Isoio *iso;
+ ulong sts;
+ Qh *qh;
+ int i, some;
+
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+
+ /*
+ * Will we know in USB 3.0 who the interrupt was for?.
+ * Do they still teach indexing in CS?
+ * This is Intel's doing.
+ */
+ ilock(ctlr);
+ ctlr->nintr++;
+ sts = opio->sts & Sintrs;
+ if(sts == 0){ /* not ours; shared intr. */
+ iunlock(ctlr);
+ return 0;
+ }
+ opio->sts = sts;
+ coherence();
+ if((sts & Sherr) != 0)
+ print("ehci: port %#p fatal host system error\n", ctlr->capio);
+ if((sts & Shalted) != 0)
+ print("ehci: port %#p: halted\n", ctlr->capio);
+ if((sts & Sasync) != 0){
+ dprint("ehci: doorbell\n");
+ wakeup(ctlr);
+ }
+ /*
+ * We enter always this if, even if it seems the
+ * interrupt does not report anything done/failed.
+ * Some controllers don't post interrupts right.
+ */
+ some = 0;
+ if((sts & (Serrintr|Sintr)) != 0){
+ ctlr->ntdintr++;
+ if(ehcidebug > 1){
+ print("ehci port %#p frames %#p nintr %d ntdintr %d",
+ ctlr->capio, ctlr->frames,
+ ctlr->nintr, ctlr->ntdintr);
+ print(" nqhintr %d nisointr %d\n",
+ ctlr->nqhintr, ctlr->nisointr);
+ print("\tcmd %#lux sts %#lux intr %#lux frno %uld",
+ opio->cmd, opio->sts, opio->intr, opio->frno);
+ }
+
+ /* process the Iso transfers */
+ for(iso = ctlr->iso; iso != nil; iso = iso->next)
+ if(iso->state == Qrun || iso->state == Qdone)
+ if(iso->hs != 0)
+ some += isohsinterrupt(ctlr, iso);
+ else
+ some += isofsinterrupt(ctlr, iso);
+
+ /* process the qhs in the periodic tree */
+ for(qh = ctlr->intrqhs; qh != nil; qh = qh->inext)
+ if(qh->state == Qrun)
+ some += qhinterrupt(ctlr, qh);
+
+ /* process the async Qh circular list */
+ qh = ctlr->qhs;
+ i = 0;
+ do{
+ if (qh == nil)
+ panic("ehciintr: nil qh");
+ if(qh->state == Qrun)
+ some += qhinterrupt(ctlr, qh);
+ qh = qh->next;
+ }while(qh != ctlr->qhs && i++ < 100);
+ if(i > 100)
+ print("echi: interrupt: qh loop?\n");
+ }
+// if (some == 0)
+// panic("ehciintr: no work");
+ iunlock(ctlr);
+ return some;
+}
+
+static void
+interrupt(Ureg*, void* a)
+{
+ ehciintr(a);
+}
+
+static int
+portenable(Hci *hp, int port, int on)
+{
+ Ctlr *ctlr;
+ Eopio *opio;
+ int s;
+
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+ s = opio->portsc[port-1];
+ qlock(&ctlr->portlck);
+ if(waserror()){
+ qunlock(&ctlr->portlck);
+ nexterror();
+ }
+ dprint("ehci %#p port %d enable=%d; sts %#x\n",
+ ctlr->capio, port, on, s);
+ ilock(ctlr);
+ if(s & (Psstatuschg | Pschange))
+ opio->portsc[port-1] = s;
+ if(on)
+ opio->portsc[port-1] |= Psenable;
+ else
+ opio->portsc[port-1] &= ~Psenable;
+ coherence();
+ microdelay(64);
+ iunlock(ctlr);
+ tsleep(&up->sleep, return0, 0, Enabledelay);
+ dprint("ehci %#p port %d enable=%d: sts %#lux\n",
+ ctlr->capio, port, on, opio->portsc[port-1]);
+ qunlock(&ctlr->portlck);
+ poperror();
+ return 0;
+}
+
+/*
+ * If we detect during status that the port is low-speed or
+ * during reset that it's full-speed, the device is not for
+ * ourselves. The companion controller will take care.
+ * Low-speed devices will not be seen by usbd. Full-speed
+ * ones are seen because it's only after reset that we know what
+ * they are (usbd may notice a device not enabled in this case).
+ */
+static void
+portlend(Ctlr *ctlr, int port, char *ss)
+{
+ Eopio *opio;
+ ulong s;
+
+ opio = ctlr->opio;
+
+ dprint("ehci %#p port %d: %s speed device: no longer owned\n",
+ ctlr->capio, port, ss);
+ s = opio->portsc[port-1] & ~(Pschange|Psstatuschg);
+ opio->portsc[port-1] = s | Psowner;
+ coherence();
+}
+
+static int
+portreset(Hci *hp, int port, int on)
+{
+ ulong s;
+ Eopio *opio;
+ Ctlr *ctlr;
+ int i;
+
+ if(on == 0)
+ return 0;
+
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+ qlock(&ctlr->portlck);
+ if(waserror()){
+ iunlock(ctlr);
+ qunlock(&ctlr->portlck);
+ nexterror();
+ }
+ s = opio->portsc[port-1];
+ dprint("ehci %#p port %d reset; sts %#lux\n", ctlr->capio, port, s);
+ ilock(ctlr);
+ s &= ~(Psenable|Psreset);
+ opio->portsc[port-1] = s | Psreset; /* initiate reset */
+ coherence();
+
+ for(i = 0; i < 50; i++){ /* was 10 */
+ delay(10);
+ if((opio->portsc[port-1] & Psreset) == 0)
+ break;
+ }
+ if (opio->portsc[port-1] & Psreset)
+ iprint("ehci %#p: port %d didn't reset after %d ms; sts %#lux\n",
+ ctlr->capio, port, i * 10, opio->portsc[port-1]);
+ opio->portsc[port-1] &= ~Psreset; /* force appearance of reset done */
+ coherence();
+
+ delay(10);
+ if((opio->portsc[port-1] & Psenable) == 0)
+ portlend(ctlr, port, "full");
+
+ iunlock(ctlr);
+ dprint("ehci %#p after port %d reset; sts %#lux\n",
+ ctlr->capio, port, opio->portsc[port-1]);
+ qunlock(&ctlr->portlck);
+ poperror();
+ return 0;
+}
+
+static int
+portstatus(Hci *hp, int port)
+{
+ int s, r;
+ Eopio *opio;
+ Ctlr *ctlr;
+
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+ qlock(&ctlr->portlck);
+ if(waserror()){
+ iunlock(ctlr);
+ qunlock(&ctlr->portlck);
+ nexterror();
+ }
+ ilock(ctlr);
+ s = opio->portsc[port-1];
+ if(s & (Psstatuschg | Pschange)){
+ opio->portsc[port-1] = s;
+ coherence();
+ ddprint("ehci %#p port %d status %#x\n", ctlr->capio, port, s);
+ }
+ /*
+ * If the port is a low speed port we yield ownership now
+ * to the [uo]hci companion controller and pretend it's not here.
+ */
+ if((s & Pspresent) != 0 && (s & Pslinemask) == Pslow){
+ portlend(ctlr, port, "low");
+ s &= ~Pspresent; /* not for us this time */
+ }
+ 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|HPhigh;
+ if(s & Psenable)
+ r |= HPenable;
+ if(s & Pssuspend)
+ r |= HPsuspend;
+ if(s & Psreset)
+ r |= HPreset;
+ if(s & Psstatuschg)
+ r |= HPstatuschg;
+ if(s & Pschange)
+ r |= HPchange;
+ return r;
+}
+
+static char*
+seprintio(char *s, char *e, Qio *io, char *pref)
+{
+ s = seprint(s,e,"%s io %#p qh %#p id %#x", pref, io, io->qh, io->usbid);
+ s = seprint(s,e," iot %ld", io->iotime);
+ s = seprint(s,e," tog %#x tok %#x err %s", io->toggle, io->tok, io->err);
+ return s;
+}
+
+static char*
+seprintep(char *s, char *e, Ep *ep)
+{
+ Qio *io;
+ Ctlio *cio;
+ 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 = seprintio(s, e, cio, "c");
+ s = seprint(s, e, "\trepl %d ndata %d\n", ep->rhrepl, cio->ndata);
+ break;
+ case Tbulk:
+ case Tintr:
+ io = ep->aux;
+ if(ep->mode != OWRITE)
+ s = seprintio(s, e, &io[OREAD], "r");
+ if(ep->mode != OREAD)
+ s = seprintio(s, e, &io[OWRITE], "w");
+ break;
+ case Tiso:
+ *s = 0;
+ break;
+ }
+ iunlock(ctlr);
+ return s;
+}
+
+/*
+ * halt condition was cleared on the endpoint. update our toggles.
+ */
+static void
+clrhalt(Ep *ep)
+{
+ Qio *io;
+
+ ep->clrhalt = 0;
+ coherence();
+ switch(ep->ttype){
+ case Tintr:
+ case Tbulk:
+ 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 void
+xdump(char* pref, void *qh)
+{
+ int i;
+ ulong *u;
+
+ u = qh;
+ print("%s %#p:", pref, u);
+ for(i = 0; i < 16; i++)
+ if((i%4) == 0)
+ print("\n %#8.8ulx", u[i]);
+ else
+ print(" %#8.8ulx", u[i]);
+ print("\n");
+}
+
+static long
+episohscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+ int nr;
+ long tot;
+ Itd *tdu;
+
+ for(tot = 0; iso->tdi != iso->tdu && tot < count; tot += nr){
+ tdu = iso->tdu;
+ if(itdactive(tdu))
+ break;
+ nr = tdu->ndata;
+ if(tot + nr > count)
+ nr = count - tot;
+ if(nr == 0)
+ print("ehci: 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;
+ coherence();
+ }
+ if(tdu->ndata == 0){
+ itdinit(iso, tdu);
+ iso->tdu = tdu->next;
+ }
+ }
+ return tot;
+}
+
+static long
+episofscpy(Ctlr *ctlr, Ep *ep, Isoio* iso, uchar *b, long count)
+{
+ int nr;
+ long tot;
+ Sitd *stdu;
+
+ for(tot = 0; iso->stdi != iso->stdu && tot < count; tot += nr){
+ stdu = iso->stdu;
+ if(stdu->csw & Stdactive){
+ diprint("ehci: episoread: %#p tdu active\n", iso);
+ break;
+ }
+ nr = stdu->ndata;
+ if(tot + nr > count)
+ nr = count - tot;
+ if(nr == 0)
+ print("ehci: ep%d.%d: too many polls\n",
+ ep->dev->nb, ep->nb);
+ else{
+ iunlock(ctlr); /* We could page fault here */
+ memmove(b+tot, stdu->data, nr);
+ ilock(ctlr);
+ if(nr < stdu->ndata)
+ memmove(stdu->data, stdu->data+nr,
+ stdu->ndata - nr);
+ stdu->ndata -= nr;
+ coherence();
+ }
+ if(stdu->ndata == 0){
+ sitdinit(iso, stdu);
+ iso->stdu = stdu->next;
+ }
+ }
+ return tot;
+}
+
+static long
+episoread(Ep *ep, Isoio *iso, void *a, long count)
+{
+ Ctlr *ctlr;
+ uchar *b;
+ long tot;
+
+ iso->debug = ep->debug;
+ diprint("ehci: 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;
+ coherence();
+ while(isocanread(iso) == 0){
+ iunlock(ctlr);
+ diprint("ehci: 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;
+ coherence();
+ assert(iso->tdu != iso->tdi);
+
+ if(iso->hs != 0)
+ tot = episohscpy(ctlr, ep, iso, b, count);
+ else
+ tot = episofscpy(ctlr, ep, iso, b, count);
+ iunlock(ctlr);
+ qunlock(iso);
+ poperror();
+ diprint("uhci: episoread: %#p %uld bytes err '%s'\n", iso, tot, iso->err);
+ if(iso->err != nil)
+ error(iso->err);
+ return tot;
+}
+
+/*
+ * 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, n;
+
+ for(tot = 0; isocanwrite(iso) && tot < count; tot += n){
+ n = count-tot;
+ if(iso->hs != 0){
+ if(n > iso->tdu->mdata - iso->nleft)
+ n = iso->tdu->mdata - iso->nleft;
+ memmove(iso->tdu->data + iso->nleft, b + tot, n);
+ coherence();
+ iso->nleft += n;
+ if(iso->nleft == iso->tdu->mdata){
+ itdinit(iso, iso->tdu);
+ iso->nleft = 0;
+ iso->tdu = iso->tdu->next;
+ }
+ }else{
+ if(n > iso->stdu->mdata - iso->nleft)
+ n = iso->stdu->mdata - iso->nleft;
+ memmove(iso->stdu->data + iso->nleft, b + tot, n);
+ coherence();
+ iso->nleft += n;
+ if(iso->nleft == iso->stdu->mdata){
+ sitdinit(iso, iso->stdu);
+ iso->nleft = 0;
+ iso->stdu = iso->stdu->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, nw;
+ char *err;
+
+ iso->debug = ep->debug;
+ diprint("ehci: 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;
+ coherence();
+ b = a;
+ for(tot = 0; tot < count; tot += nw){
+ while(isocanwrite(iso) == 0){
+ iunlock(ctlr);
+ diprint("ehci: 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("ehci: episowrite: %#p %d bytes\n", iso, tot);
+ return tot;
+}
+
+static int
+nexttoggle(int toggle, int count, int maxpkt)
+{
+ int np;
+
+ np = count / maxpkt;
+ if(np == 0)
+ np = 1;
+ if((np % 2) == 0)
+ return toggle;
+ if(toggle == Tddata1)
+ return Tddata0;
+ else
+ return Tddata1;
+}
+
+static Td*
+epgettd(Qio *io, int flags, void *a, int count, int maxpkt)
+{
+ Td *td;
+ ulong pa;
+ int i;
+
+ if(count > Tdmaxpkt)
+ panic("ehci: epgettd: too many bytes");
+ td = tdalloc();
+ td->csw = flags | io->toggle | io->tok | count << Tdlenshift |
+ Tderr2 | Tderr1;
+ coherence();
+
+ /*
+ * use the space wasted by alignment as an
+ * embedded buffer if count bytes fit in there.
+ */
+ assert(Align > sizeof(Td));
+ if(count <= Align - sizeof(Td)){
+ td->data = td->sbuff;
+ td->buff = nil;
+ }else
+ td->data = td->buff = smalloc(Tdmaxpkt);
+
+ pa = PADDR(td->data);
+ for(i = 0; i < nelem(td->buffer); i++){
+ td->buffer[i] = pa;
+ if(i > 0)
+ td->buffer[i] &= ~0xFFF;
+ pa += 0x1000;
+ }
+ td->ndata = count;
+ if(a != nil && count > 0)
+ memmove(td->data, a, count);
+ coherence();
+ io->toggle = nexttoggle(io->toggle, count, maxpkt);
+ coherence();
+ return td;
+}
+
+/*
+ * Try to get them idle
+ */
+static void
+aborttds(Qh *qh)
+{
+ Td *td;
+
+ qh->state = Qdone;
+ coherence();
+ if(qh->sched >= 0 && (qh->eps0 & Qhspeedmask) != Qhhigh)
+ qh->eps0 |= Qhint; /* inactivate on next pass */
+ coherence();
+ for(td = qh->tds; td != nil; td = td->next){
+ if(td->csw & Tdactive)
+ td->ndata = 0;
+ td->csw |= Tdhalt;
+ coherence();
+ }
+}
+
+/*
+ * Some controllers do not post the usb/error interrupt after
+ * the work has been done. It seems that we must poll for them.
+ */
+static int
+workpending(void *a)
+{
+ Ctlr *ctlr;
+
+ ctlr = a;
+ return ctlr->nreqs > 0;
+}
+
+static void
+ehcipoll(void* a)
+{
+ Hci *hp;
+ Ctlr *ctlr;
+ Poll *poll;
+ int i;
+
+ hp = a;
+ ctlr = hp->aux;
+ poll = &ctlr->poll;
+ for(;;){
+ if(ctlr->nreqs == 0){
+ if(0)ddprint("ehcipoll %#p sleep\n", ctlr->capio);
+ sleep(poll, workpending, ctlr);
+ if(0)ddprint("ehcipoll %#p awaken\n", ctlr->capio);
+ }
+ for(i = 0; i < 16 && ctlr->nreqs > 0; i++)
+ if(ehciintr(hp) == 0)
+ break;
+ do{
+ tsleep(&up->sleep, return0, 0, 1);
+ ehciintr(hp);
+ }while(ctlr->nreqs > 0);
+ }
+}
+
+static void
+pollcheck(Hci *hp)
+{
+ Ctlr *ctlr;
+ Poll *poll;
+
+ ctlr = hp->aux;
+ poll = &ctlr->poll;
+
+ if(poll->must != 0 && poll->does == 0){
+ lock(poll);
+ if(poll->must != 0 && poll->does == 0){
+ poll->does++;
+ print("ehci %#p: polling\n", ctlr->capio);
+ kproc("ehcipoll", ehcipoll, hp);
+ }
+ unlock(poll);
+ }
+}
+
+static int
+epiodone(void *a)
+{
+ Qh *qh;
+
+ qh = a;
+ return qh->state != Qrun;
+}
+
+static void
+epiowait(Hci *hp, Qio *io, int tmout, ulong load)
+{
+ Qh *qh;
+ int timedout;
+ Ctlr *ctlr;
+
+ ctlr = hp->aux;
+ qh = io->qh;
+ ddqprint("ehci io %#p sleep on qh %#p state %s\n",
+ io, qh, qhsname[qh->state]);
+ timedout = 0;
+ if(waserror()){
+ dqprint("ehci 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);
+ /* Are we missing interrupts? */
+ if(qh->state == Qrun){
+ iunlock(ctlr);
+ ehciintr(hp);
+ ilock(ctlr);
+ if(qh->state == Qdone){
+ dqprint("ehci %#p: polling required\n", ctlr->capio);
+ ctlr->poll.must = 1;
+ pollcheck(hp);
+ }
+ }
+
+ if(qh->state == Qrun){
+ dqprint("ehci io %#p qh %#p timed out (no intr?)\n", io, qh);
+ timedout = 1;
+ }else if(qh->state != Qdone && qh->state != Qclose)
+ panic("ehci: epio: queue state %d", qh->state);
+ 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;
+ coherence();
+ qhlinktd(qh, nil);
+ ctlr->load -= load;
+ ctlr->nreqs--;
+ 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)
+{
+ int saved, ntds, tmout;
+ long n, tot;
+ ulong load;
+ char *err;
+ char buf[128];
+ uchar *c;
+ Ctlr *ctlr;
+ Qh* qh;
+ Td *td, *ltd, *td0, *ntd;
+
+ 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((ehcidebug > 1 || ep->debug > 1) && io->tok != Tdtokin){
+ seprintdata(buf, buf+sizeof(buf), a, count);
+ print("echi 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 = (Tdmaxpkt / ep->maxpkt) * ep->maxpkt;
+ if(count-tot < n)
+ n = count-tot;
+ if(c != nil && io->tok != Tdtokin)
+ td = epgettd(io, Tdactive, c+tot, n, ep->maxpkt);
+ else
+ td = epgettd(io, Tdactive, nil, n, ep->maxpkt);
+ 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 */
+ coherence();
+
+ ddeprint("ehci: load %uld ctlr load %uld\n", load, ctlr->load);
+ if(ehcidebug > 1 || ep->debug > 1)
+ dumptd(td0, "epio: put: ");
+
+ ilock(ctlr);
+ if(qh->state != Qclose){
+ io->iotime = TK2MS(MACHP(0)->ticks);
+ qh->state = Qrun;
+ coherence();
+ qhlinktd(qh, td0);
+ ctlr->nreqs++;
+ ctlr->load += load;
+ }
+ iunlock(ctlr);
+
+ if(ctlr->poll.does)
+ wakeup(&ctlr->poll);
+
+ epiowait(ep->hp, io, tmout, load);
+ if(ehcidebug > 1 || ep->debug > 1){
+ dumptd(td0, "epio: got: ");
+ qhdump(qh);
+ }
+
+ 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 & (Tdhalt|Tdactive)){
+ if(saved++ == 0) {
+ io->toggle = td->csw & Tddata1;
+ coherence();
+ }
+ }else{
+ tot += td->ndata;
+ if(c != nil && (td->csw & Tdtok) == 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 == Estalled)
+ return 0; /* that's our convention */
+ if(err != nil)
+ error(err);
+ if(tot < 0)
+ error(Eio);
+ return tot;
+}
+
+static long
+epread(Ep *ep, void *a, long count)
+{
+ Ctlio *cio;
+ Qio *io;
+ Isoio *iso;
+ char buf[160];
+ ulong delta;
+
+ ddeprint("ehci: epread\n");
+ if(ep->aux == nil)
+ panic("epread: not open");
+
+ pollcheck(ep->hp);
+
+ 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(ehcidebug>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);
+ }
+ 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()){
+ free(cio->data);
+ cio->data = nil;
+ cio->ndata = 0;
+ qunlock(cio);
+ 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;
+ coherence();
+ qhsetaddr(cio->qh, cio->usbid);
+ }
+ /* adjust maxpkt if the user has learned a different one */
+ if(qhmaxpkt(cio->qh) != ep->maxpkt)
+ qhsetmaxpkt(cio->qh, ep->maxpkt);
+ c = a;
+ cio->tok = Tdtoksetup;
+ cio->toggle = Tddata0;
+ coherence();
+ 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 ehci");
+ a = cio->data = smalloc(len+1);
+ }else{
+ cio->tok = Tdtokout;
+ len = count;
+ }
+ coherence();
+ 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;
+ coherence();
+ 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)
+{
+ Qio *io;
+ Ctlio *cio;
+ Isoio *iso;
+ ulong delta;
+
+ pollcheck(ep->hp);
+
+ ddeprint("ehci: epwrite ep%d.%d\n", ep->dev->nb, ep->nb);
+ if(ep->aux == nil)
+ panic("ehci: 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);
+ return epio(ep, &io[OWRITE], a, count, 1);
+ 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);
+ }
+ return -1;
+}
+
+static void
+isofsinit(Ep *ep, Isoio *iso)
+{
+ long left;
+ Sitd *td, *ltd;
+ int i;
+ ulong frno;
+
+ left = 0;
+ ltd = nil;
+ frno = iso->td0frno;
+ for(i = 0; i < iso->nframes; i++){
+ td = sitdalloc();
+ td->data = iso->data + i * ep->maxpkt;
+ td->epc = ep->dev->port << Stdportshift;
+ td->epc |= ep->dev->hub << Stdhubshift;
+ td->epc |= ep->nb << Stdepshift;
+ td->epc |= ep->dev->nb << Stddevshift;
+ td->mfs = 034 << Stdscmshift | 1 << Stdssmshift;
+ if(ep->mode == OREAD){
+ td->epc |= Stdin;
+ td->mdata = ep->maxpkt;
+ }else{
+ td->mdata = (ep->hz+left) * ep->pollival / 1000;
+ td->mdata *= ep->samplesz;
+ left = (ep->hz+left) * ep->pollival % 1000;
+ if(td->mdata > ep->maxpkt){
+ print("ehci: ep%d.%d: size > maxpkt\n",
+ ep->dev->nb, ep->nb);
+ print("size = %ld max = %ld\n",
+ td->mdata,ep->maxpkt);
+ td->mdata = ep->maxpkt;
+ }
+ }
+ coherence();
+
+ iso->sitdps[frno] = td;
+ coherence();
+ sitdinit(iso, td);
+ if(ltd != nil)
+ ltd->next = td;
+ ltd = td;
+ frno = TRUNC(frno+ep->pollival, Nisoframes);
+ }
+ ltd->next = iso->sitdps[iso->td0frno];
+ coherence();
+}
+
+static void
+isohsinit(Ep *ep, Isoio *iso)
+{
+ int ival, p;
+ long left;
+ ulong frno, i, pa;
+ Itd *ltd, *td;
+
+ iso->hs = 1;
+ ival = 1;
+ if(ep->pollival > 8)
+ ival = ep->pollival/8;
+ left = 0;
+ ltd = nil;
+ frno = iso->td0frno;
+ for(i = 0; i < iso->nframes; i++){
+ td = itdalloc();
+ td->data = iso->data + i * 8 * iso->maxsize;
+ pa = PADDR(td->data) & ~0xFFF;
+ for(p = 0; p < 8; p++)
+ td->buffer[i] = pa + p * 0x1000;
+ td->buffer[0] = PADDR(iso->data) & ~0xFFF |
+ ep->nb << Itdepshift | ep->dev->nb << Itddevshift;
+ if(ep->mode == OREAD)
+ td->buffer[1] |= Itdin;
+ else
+ td->buffer[1] |= Itdout;
+ td->buffer[1] |= ep->maxpkt << Itdmaxpktshift;
+ td->buffer[2] |= ep->ntds << Itdntdsshift;
+
+ if(ep->mode == OREAD)
+ td->mdata = 8 * iso->maxsize;
+ else{
+ td->mdata = (ep->hz + left) * ep->pollival / 1000;
+ td->mdata *= ep->samplesz;
+ left = (ep->hz + left) * ep->pollival % 1000;
+ }
+ coherence();
+ iso->itdps[frno] = td;
+ coherence();
+ itdinit(iso, td);
+ if(ltd != nil)
+ ltd->next = td;
+ ltd = td;
+ frno = TRUNC(frno + ival, Nisoframes);
+ }
+}
+
+static void
+isoopen(Ctlr *ctlr, Ep *ep)
+{
+ int ival; /* pollival in ms */
+ int tpf; /* tds per frame */
+ int i, n, w, woff;
+ ulong frno;
+ Isoio *iso;
+
+ iso = ep->aux;
+ switch(ep->mode){
+ case OREAD:
+ iso->tok = Tdtokin;
+ break;
+ case OWRITE:
+ iso->tok = Tdtokout;
+ break;
+ default:
+ error("iso i/o is half-duplex");
+ }
+ iso->usbid = ep->nb << 7 | ep->dev->nb & Devmax;
+ iso->state = Qidle;
+ coherence();
+ iso->debug = ep->debug;
+ ival = ep->pollival;
+ tpf = 1;
+ if(ep->dev->speed == Highspeed){
+ tpf = 8;
+ if(ival <= 8)
+ ival = 1;
+ else
+ ival /= 8;
+ }
+ assert(ival != 0);
+ iso->nframes = Nisoframes / ival;
+ if(iso->nframes < 3)
+ error("uhci isoopen bug"); /* we need at least 3 tds */
+ iso->maxsize = ep->ntds * ep->maxpkt;
+ if(ctlr->load + ep->load > 800)
+ print("usb: ehci: bandwidth may be exceeded\n");
+ ilock(ctlr);
+ ctlr->load += ep->load;
+ ctlr->isoload += ep->load;
+ ctlr->nreqs++;
+ dprint("ehci: load %uld isoload %uld\n", ctlr->load, ctlr->isoload);
+ diprint("iso nframes %d pollival %uld ival %d maxpkt %uld ntds %d\n",
+ iso->nframes, ep->pollival, ival, ep->maxpkt, ep->ntds);
+ iunlock(ctlr);
+ if(ctlr->poll.does)
+ wakeup(&ctlr->poll);
+
+ /*
+ * From here on this cannot raise errors
+ * unless we catch them and release here all memory allocated.
+ */
+ assert(ep->maxpkt > 0 && ep->ntds > 0 && ep->ntds < 4);
+ assert(ep->maxpkt <= 1024);
+ iso->tdps = smalloc(sizeof(uintptr) * Nisoframes);
+ iso->data = smalloc(iso->nframes * tpf * ep->ntds * ep->maxpkt);
+ iso->td0frno = TRUNC(ctlr->opio->frno + 10, Nisoframes);
+ /* read: now; write: 1s ahead */
+
+ if(ep->dev->speed == Highspeed)
+ isohsinit(ep, iso);
+ else
+ isofsinit(ep, iso);
+ iso->tdu = iso->tdi = iso->itdps[iso->td0frno];
+ iso->stdu = iso->stdi = iso->sitdps[iso->td0frno];
+ coherence();
+
+ ilock(ctlr);
+ frno = iso->td0frno;
+ for(i = 0; i < iso->nframes; i++){
+ *iso->tdps[frno] = ctlr->frames[frno];
+ frno = TRUNC(frno+ival, Nisoframes);
+ }
+
+ /*
+ * Iso uses a virtual frame window of Nisoframes, and we must
+ * fill the actual ctlr frame array by placing ctlr->nframes/Nisoframes
+ * copies of the window in the frame array.
+ */
+ assert(ctlr->nframes >= Nisoframes && Nisoframes >= iso->nframes);
+ assert(Nisoframes >= Nintrleafs);
+ n = ctlr->nframes / Nisoframes;
+ for(w = 0; w < n; w++){
+ frno = iso->td0frno;
+ woff = w * Nisoframes;
+ for(i = 0; i < iso->nframes ; i++){
+ assert(woff+frno < ctlr->nframes);
+ assert(iso->tdps[frno] != nil);
+ if(ep->dev->speed == Highspeed)
+ ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])
+ |Litd;
+ else
+ ctlr->frames[woff+frno] = PADDR(iso->tdps[frno])
+ |Lsitd;
+ coherence();
+ frno = TRUNC(frno+ep->pollival, Nisoframes);
+ }
+ }
+ coherence();
+ iso->next = ctlr->iso;
+ ctlr->iso = iso;
+ coherence();
+ iso->state = Qdone;
+ iunlock(ctlr);
+ if(ehcidebug > 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;
+ Ctlio *cio;
+ Qio *io;
+ int usbid;
+
+ ctlr = ep->hp->aux;
+ deprint("ehci: epopen ep%d.%d\n", ep->dev->nb, ep->nb);
+ if(ep->aux != nil)
+ panic("ehci: epopen called with open ep");
+ if(waserror()){
+ free(ep->aux);
+ ep->aux = nil;
+ nexterror();
+ }
+ switch(ep->ttype){
+ case Tnone:
+ error("endpoint not configured");
+ case Tiso:
+ ep->aux = smalloc(sizeof(Isoio));
+ isoopen(ctlr, 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, ep, cio, "epc");
+ break;
+ case Tbulk:
+ ep->pollival = 1; /* assume this; doesn't really matter */
+ /* and fall... */
+ 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;
+ assert(ep->pollival != 0);
+ if(ep->mode != OREAD){
+ if(ep->toggle[OWRITE] != 0)
+ io[OWRITE].toggle = Tddata1;
+ else
+ io[OWRITE].toggle = Tddata0;
+ io[OWRITE].tok = Tdtokout;
+ io[OWRITE].usbid = usbid;
+ io[OWRITE].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+ io[OWRITE].qh = qhalloc(ctlr, ep, io+OWRITE, "epw");
+ }
+ if(ep->mode != OWRITE){
+ if(ep->toggle[OREAD] != 0)
+ io[OREAD].toggle = Tddata1;
+ else
+ io[OREAD].toggle = Tddata0;
+ io[OREAD].tok = Tdtokin;
+ io[OREAD].usbid = usbid;
+ io[OREAD].bw = ep->maxpkt*1000/ep->pollival; /* bytes/s */
+ io[OREAD].qh = qhalloc(ctlr, ep, io+OREAD, "epr");
+ }
+ break;
+ }
+ coherence();
+ if(ehcidebug>1 || ep->debug)
+ dump(ep->hp);
+ deprint("ehci: 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("ehci: 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)
+{
+ int frno, i, n, t, w, woff;
+ ulong *lp, *tp;
+ Isoio **il;
+ Itd *td;
+ Sitd *std;
+
+ ilock(ctlr);
+ if(iso->state == Qclose){
+ iunlock(ctlr);
+ return;
+ }
+ ctlr->nreqs--;
+ if(iso->state != Qrun && iso->state != Qdone)
+ panic("bad iso state");
+ iso->state = Qclose;
+ coherence();
+ if(ctlr->isoload < load)
+ panic("ehci: 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("cancleiso: not found");
+ *il = iso->next;
+
+ frno = iso->td0frno;
+ for(i = 0; i < iso->nframes; i++){
+ tp = iso->tdps[frno];
+ if(iso->hs != 0){
+ td = iso->itdps[frno];
+ for(t = 0; t < nelem(td->csw); t++)
+ td->csw[t] &= ~(Itdioc|Itdactive);
+ }else{
+ std = iso->sitdps[frno];
+ std->csw &= ~(Stdioc|Stdactive);
+ }
+ coherence();
+ for(lp = &ctlr->frames[frno]; !(*lp & Lterm);
+ lp = &LPTR(*lp)[0])
+ if(LPTR(*lp) == tp)
+ break;
+ if(*lp & Lterm)
+ panic("cancelisoio: td not found");
+ *lp = tp[0];
+ /*
+ * Iso uses a virtual frame window of Nisoframes, and we must
+ * restore pointers in copies of the window kept at ctlr->frames.
+ */
+ if(lp == &ctlr->frames[frno]){
+ n = ctlr->nframes / Nisoframes;
+ for(w = 1; w < n; w++){
+ woff = w * Nisoframes;
+ ctlr->frames[woff+frno] = *lp;
+ }
+ }
+ coherence();
+ frno = TRUNC(frno+pollival, Nisoframes);
+ }
+ 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* 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++){
+ if(iso->hs != 0)
+ itdfree(iso->itdps[frno]);
+ else
+ sitdfree(iso->sitdps[frno]);
+ iso->tdps[frno] = nil;
+ frno = TRUNC(frno+pollival, Nisoframes);
+ }
+ free(iso->tdps);
+ iso->tdps = nil;
+ free(iso->data);
+ iso->data = nil;
+ coherence();
+}
+
+static void
+epclose(Ep *ep)
+{
+ Qio *io;
+ Ctlio *cio;
+ Isoio *iso;
+ Ctlr *ctlr;
+
+ ctlr = ep->hp->aux;
+ deprint("ehci: epclose ep%d.%d\n", ep->dev->nb, ep->nb);
+
+ if(ep->aux == nil)
+ panic("ehci: epclose called with closed ep");
+ switch(ep->ttype){
+ case Tctl:
+ cio = ep->aux;
+ cancelio(ctlr, cio);
+ free(cio->data);
+ cio->data = nil;
+ break;
+ case Tintr:
+ case Tbulk:
+ 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;
+ }
+ coherence();
+ break;
+ case Tiso:
+ iso = ep->aux;
+ cancelisoio(ctlr, iso, ep->pollival, ep->load);
+ break;
+ default:
+ panic("epclose: bad ttype");
+ }
+ free(ep->aux);
+ ep->aux = nil;
+}
+
+/*
+ * return smallest power of 2 >= n
+ */
+static int
+flog2(int n)
+{
+ int i;
+
+ for(i = 0; (1 << i) < n; i++)
+ ;
+ return i;
+}
+
+/*
+ * build the periodic scheduling tree:
+ * framesize must be a multiple of the tree size
+ */
+static void
+mkqhtree(Ctlr *ctlr)
+{
+ int i, n, d, o, leaf0, depth;
+ ulong leafs[Nintrleafs];
+ Qh *qh;
+ Qh **tree;
+ Qtree *qt;
+
+ depth = flog2(Nintrleafs);
+ n = (1 << (depth+1)) - 1;
+ qt = mallocz(sizeof(*qt), 1);
+ if(qt == nil)
+ panic("ehci: mkqhtree: no memory");
+ qt->nel = n;
+ qt->depth = depth;
+ qt->bw = mallocz(n * sizeof(qt->bw), 1);
+ qt->root = tree = mallocz(n * sizeof(Qh *), 1);
+ if(qt->bw == nil || tree == nil)
+ panic("ehci: mkqhtree: no memory");
+ for(i = 0; i < n; i++){
+ tree[i] = qh = edalloc();
+ if(qh == nil)
+ panic("ehci: mkqhtree: no memory");
+ qh->nlink = qh->alink = qh->link = Lterm;
+ qh->csw = Tdhalt;
+ qh->state = Qidle;
+ coherence();
+ if(i > 0)
+ qhlinkqh(tree[i], tree[(i-1)/2]);
+ }
+ ctlr->ntree = i;
+ dprint("ehci: tree: %d endpoints allocated\n", i);
+
+ /* distribute leaves evenly round the frame list */
+ leaf0 = n / 2;
+ for(i = 0; i < Nintrleafs; 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;
+ }
+ leafs[i] = PADDR(tree[leaf0 + o]) | Lqh;
+ }
+ assert((ctlr->nframes % Nintrleafs) == 0);
+ for(i = 0; i < ctlr->nframes; i += Nintrleafs){
+ memmove(ctlr->frames + i, leafs, sizeof leafs);
+ coherence();
+ }
+ ctlr->tree = qt;
+ coherence();
+}
+
+void
+ehcimeminit(Ctlr *ctlr)
+{
+ int i, frsize;
+ Eopio *opio;
+
+ opio = ctlr->opio;
+ frsize = ctlr->nframes * sizeof(ulong);
+ assert((frsize & 0xFFF) == 0); /* must be 4k aligned */
+ ctlr->frames = xspanalloc(frsize, frsize, 0);
+ if(ctlr->frames == nil)
+ panic("ehci reset: no memory");
+
+ for (i = 0; i < ctlr->nframes; i++)
+ ctlr->frames[i] = Lterm;
+ opio->frbase = PADDR(ctlr->frames);
+ opio->frno = 0;
+ coherence();
+
+ qhalloc(ctlr, nil, nil, nil); /* init async list */
+ mkqhtree(ctlr); /* init sync list */
+ edfree(edalloc()); /* try to get some ones pre-allocated */
+
+ dprint("ehci %#p flb %#lux frno %#lux\n",
+ ctlr->capio, opio->frbase, opio->frno);
+}
+
+static void
+init(Hci *hp)
+{
+ Ctlr *ctlr;
+ Eopio *opio;
+ int i;
+
+ hp->highspeed = 1;
+ ctlr = hp->aux;
+ opio = ctlr->opio;
+ dprint("ehci %#p init\n", ctlr->capio);
+
+ ilock(ctlr);
+ /*
+ * Unless we activate frroll interrupt
+ * some machines won't post other interrupts.
+ */
+ opio->intr = Iusb|Ierr|Iportchg|Ihcerr|Iasync;
+ coherence();
+ opio->cmd |= Cpse;
+ coherence();
+ opio->cmd |= Case;
+ coherence();
+ ehcirun(ctlr, 1);
+ opio->config = Callmine; /* reclaim all ports */
+ coherence();
+
+ for (i = 0; i < hp->nports; i++)
+ opio->portsc[i] = Pspower;
+ iunlock(ctlr);
+ if(ehcidebug > 1)
+ dump(hp);
+}
+
+void
+ehcilinkage(Hci *hp)
+{
+ 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 = "ehci";
+}