/* * USB Open Host Controller Interface (OHCI) driver * from devohci.c provided by Charles Forsyth, 5 Aug 2006. */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "usb.h" #define XPRINT if(usbhdebug) print #define XIPRINT if(usbhdebug) iprint #define XEPRINT if(usbhdebug || ep->debug) print #define XEIPRINT if(usbhdebug || ep->debug) iprint #define IPRINT(x) iprint x static int usbhdebug = 0; static int dcls; enum { Ned = 63 + 32, Ntd = 256, }; /* * USB packet definitions */ enum { Otoksetup = 0, Otokout = 1, Otokin = 2, /* port status - UHCI style */ Suspend = 1<<12, PortReset = 1<<9, SlowDevice = 1<<8, ResumeDetect = 1<<6, PortEnableChange = 1<<3, /* write 1 to clear */ PortEnable = 1<<2, ConnectStatusChange = 1<<1, /* write 1 to clear */ DevicePresent = 1<<0, }; typedef struct Ctlr Ctlr; typedef struct QTree QTree; enum { ED_MPS_MASK = 0x7ff, ED_MPS_SHIFT = 16, ED_C_MASK = 1, ED_C_SHIFT = 1, ED_F_BIT = 1 << 15, ED_S_MASK = 1, ED_S_SHIFT = 13, ED_D_MASK = 3, ED_D_SHIFT = 11, ED_H_MASK = 1, ED_H_SHIFT = 0, }; typedef struct Endptx Endptx; typedef struct TD TD; struct Endptx { Lock; /* for manipulating ed */ ED *ed; /* Single endpoint descriptor */ int ntd; /* Number of TDs in use */ int overruns; }; struct TD { ulong ctrl; ulong cbp; ulong nexttd; ulong be; ushort offsets[8]; /* Iso TDs only */ /* driver specific; pad to multiple of 32 */ TD* next; Endpt *ep; Block *bp; ulong flags; ulong offset; /* offset associated with end of data */ ulong bytes; /* bytes in this TD */ ulong pad[2]; }; enum { TD_R_SHIFT = 18, TD_DP_MASK = 3, TD_DP_SHIFT = 19, TD_CC_MASK = 0xf, TD_CC_SHIFT = 28, TD_EC_MASK = 3, TD_EC_SHIFT = 26, TD_FLAGS_LAST = 1 << 0, }; typedef struct HCCA HCCA; struct HCCA { ulong intrtable[32]; ushort framenumber; ushort pad1; ulong donehead; uchar reserved[116]; }; /* OHCI registers */ typedef struct OHCI OHCI; struct OHCI { /* control and status group */ /*00*/ ulong revision; ulong control; ulong cmdsts; ulong intrsts; /*10*/ ulong intrenable; ulong intrdisable; /* memory pointer group */ ulong hcca; ulong periodcurred; /*20*/ ulong ctlheaded; ulong ctlcurred; ulong bulkheaded; ulong bulkcurred; /*30*/ ulong donehead; /* frame counter group */ ulong fminterval; ulong fmremaining; ulong fmnumber; /*40*/ ulong periodicstart; ulong lsthreshold; /* root hub group */ ulong rhdesca; ulong rhdescb; /*50*/ ulong rhsts; ulong rhportsts[15]; /*90*/ ulong pad25[20]; /* unknown */ /*e0*/ ulong hostueaddr; ulong hostuests; ulong hosttimeoutctrl; ulong pad59; /*f0*/ ulong pad60; ulong hostrevision; ulong pad62[2]; /*100*/ }; /* * software structures */ static struct { int bit; char *name; } portstatus[] = { { Suspend, "suspend", }, { PortReset, "reset", }, { SlowDevice, "lowspeed", }, { ResumeDetect, "resume", }, { PortEnableChange, "portchange", }, { PortEnable, "enable", }, { ConnectStatusChange, "statuschange", }, { DevicePresent, "present", }, }; struct QTree { QLock; int nel; int depth; ulong* bw; ED **root; }; /* device parameters */ static char *devstates[] = { [Disabled] "Disabled", [Attached] "Attached", [Enabled] "Enabled", }; struct Ctlr { Lock; /* protects state shared with interrupt (eg, free list) */ int active; Pcidev* pcidev; int irq; ulong tbdf; Ctlr* next; int nports; OHCI *base; /* equiv to io in uhci */ HCCA *uchcca; int idgen; /* version # to distinguish new connections */ QLock resetl; /* lock controller during USB reset */ struct { Lock; TD* pool; TD* free; int alloced; } td; struct { QLock; ED* pool; ED* free; int alloced; } ed; /* TODO: what happened to ctlq, etc. from uhci? */ QTree* tree; /* tree for t Endpt i/o */ struct { QLock; Endpt* f; } activends; }; enum { HcRevision = 0x00, HcControl = 0x01, HcfsMask = 3 << 6, HcfsReset = 0 << 6, HcfsResume = 1 << 6, HcfsOperational=2 << 6, HcfsSuspend = 3 << 6, Ble = 1 << 5, Cle = 1 << 4, Ie = 1 << 3, Ple = 1 << 2, Cbsr_MASK = 3, HcCommandStatus = 0x02, Ocr = 1 << 3, Blf = 1 << 2, Clf = 1 << 1, Hcr = 1 << 0, HcIntrStatus = 0x03, HcIntrEnable = 0x04, Mie = 1 << 31, Oc = 1 << 30, Rhsc = 1 << 6, Fno = 1 << 5, Ue = 1 << 4, Rd = 1 << 3, Sf = 1 << 2, Wdh = 1 << 1, So = 1 << 0, HcIntrDisable = 0x05, HcFmIntvl = 0x0d, HcFmIntvl_FSMaxpack_MASK = 0x7fff, HcFmIntvl_FSMaxpack_SHIFT = 16, HcFmRemaining = 0x0e, HcFmNumber = 0x0f, HcLSThreshold = 0x11, HcRhDescA = 0x12, HcRhDescA_POTPGT_MASK = 0xff << 24, HcRhDescA_POTPGT_SHIFT = 24, HcRhDescB = 0x13, HcRhStatus = 0x14, Lps = 1 << 0, Cgp = 1 << 0, Oci = 1 << 1, Drwe = 1 << 15, Srwe = 1 << 15, LpsC = 1 << 16, Sgp = 1 << 16, Ccic = 1 << 17, Crwe = 1 << 31, HcRhPortStatus1 = 0x15, Ccs = 1 << 0, Cpe = 1 << 0, Pes = 1 << 1, Spe = 1 << 1, Pss = 1 << 2, Poci = 1 << 3, Prs = 1 << 4, Spr = 1 << 4, Pps = 1 << 8, Spp= 1 << 8, Lsda = 1 << 9, Cpp = 1 << 9, Csc = 1 << 16, Pesc = 1 << 17, Pssc = 1 << 18, Ocic = 1 << 19, Prsc = 1 << 20, HcRhPortStatus2 = 0x16, L2NFRAME = 5, NFRAME = 1 << L2NFRAME, /* TODO: from UHCI; correct for OHCI? */ FRAMESIZE = NFRAME*sizeof(ulong), /* fixed by hardware; aligned to same */ }; char *usbmode[] = { [Ctlmode]= "Ctl", [Bulkmode] = "Bulk", [Intrmode] = "Intr", [Isomode] = "Iso", }; static char *ousbmode[] = { [OREAD] = "r", [OWRITE] = "w", [ORDWR] = "rw", }; int ohciinterrupts[Nmodes]; static Ctlr* ctlrhead; static Ctlr* ctlrtail; static char Estalled[] = "usb endpoint stalled"; static char EnotWritten[] = "usb write unfinished"; static char EnotRead[] = "usb read unfinished"; static char Eunderrun[] = "usb endpoint underrun"; static QLock usbhstate; /* protects name space state */ static void eptactivate(Ctlr *ub, Endpt *ep); static void eptdeactivate(Ctlr *ub, Endpt *e); static long read (Usbhost *, Endpt*, void*, long, vlong); static void scanpci(void); static int schedendpt(Ctlr *ub, Endpt *ep, int direction); static void unschedendpt(Ctlr *ub, Endpt *ep, int); static long write(Usbhost *, Endpt*, void*, long, vlong, int); static long qtd(Ctlr*, Endpt*, int, Block*, uchar*, uchar*, int, ulong); void printdata(void *pdata, int itemsize, int nitems) { int i; uchar *p1; ushort *p2; ulong *p4; if(!usbhdebug) return; p1 = pdata; p2 = pdata; p4 = pdata; i = 0; for(;;){ switch(itemsize){ default: assert(0); case 1: print("%2.2ux ", *p1++); break; case 2: print("%4.4ux ", *p2++); break; case 4: print("%8.8lux ", *p4++); break; } if(++i >= nitems || (i & ((0x40 >> itemsize) - 1)) == 0){ print("\n"); if(i >= nitems) break; } } } /* * i left these in so that we could use the same * driver on several other platforms (in principle). * the processor on which it was originally developed * had an IO MMU and thus another address space. * it's nothing to do with USB as such. */ ulong va2hcva(void *va) { if(va == nil) return 0; return PADDR(va); } void * hcva2va(ulong hcva) { if(hcva == 0) return nil; return KADDR(hcva); } void * va2ucva(void *va) { return va; } void * hcva2ucva(ulong hcva) { if(hcva == 0) return nil; if(hcva & 0xf0000000){ iprint("hcva2ucva: bad %#lux, called from %#p\n", hcva, getcallerpc(&hcva)); return nil; } return KADDR(hcva); } #define IOCACHED 0 #define invalidatedcacheva(va) #define dcclean(p, n) static void EDinit(ED *ed, int mps, int f, int k, int s, int d, int en, int fa, TD *tail, TD *head, int c, int h, ED *next) { /* check nothing is running? */ ed->ctrl = (mps & ED_MPS_MASK) << ED_MPS_SHIFT | (f & 1) << 15 | (k & 1) << 14 | (s & ED_S_MASK) << ED_S_SHIFT | (d & 3) << 11 /* 00 is obtained from TD (used here) */ | (en & 0xf) << 7 | (fa & 0x7f); ed->tail = va2hcva(tail) & ~0xF; ed->head = (va2hcva(head) & ~0xF) | (c & ED_C_MASK) << ED_C_SHIFT | (h & ED_H_MASK) << ED_H_SHIFT; ed->next = va2hcva(next) & ~0xF; } static void EDsetS(ED *ed, int s) { XIPRINT("EDsetS: %s speed\n", s == Lowspeed ? "low" : "high"); if(s == Lowspeed) ed->ctrl |= 1 << ED_S_SHIFT; else ed->ctrl &= ~(1 << ED_S_SHIFT); } static void EDsetMPS(ED *ed, int mps) { ed->ctrl = (ed->ctrl & ~(ED_MPS_MASK << ED_MPS_SHIFT)) | (mps & ED_MPS_MASK) << ED_MPS_SHIFT; } static void EDsetC(ED *ed, int c) { ed->head = (ed->head & ~(ED_C_MASK << ED_C_SHIFT)) | (c & ED_C_MASK) << ED_C_SHIFT; } static void EDsetH(ED *ed, int h) { ed->head = (ed->head & ~(ED_H_MASK << ED_H_SHIFT)) | (h & ED_H_MASK) << ED_H_SHIFT; } static int EDgetH(ED *ed) { return (ed->head >> ED_H_SHIFT) & ED_H_MASK; } static int EDgetC(ED *ed) { return (ed->head >> ED_C_SHIFT) & ED_C_MASK; } static void EDsetnext(ED *ed, void *va) { ed->next = va2hcva(va) & ~0xF; } static ED * EDgetnext(ED *ed) { return hcva2ucva(ed->next & ~0xF); } static void EDsettail(ED *ed, void *va) { ed->tail = va2hcva(va) & ~0xF; } static TD * EDgettail(ED *ed) { return hcva2ucva(ed->tail & ~0xF); } static void EDsethead(ED *ed, void *va) { ed->head = (ed->head & 0xf) | (va2hcva(va) & ~0xF); } static TD * EDgethead(ED *ed) { return hcva2ucva(ed->head & ~0xF); } static ED * EDalloc(Ctlr *ub) { ED *t; qlock(&ub->ed); t = ub->ed.free; if(t == nil){ qunlock(&ub->ed); return nil; } ub->ed.free = (ED *)t->next; ub->ed.alloced++; if (0) print("%d endpoints allocated\n", ub->ed.alloced); qunlock(&ub->ed); t->next = 0; return t; } void TDsetnexttd(TD *td, TD *va) { td->nexttd = va2hcva(va) & ~0xF; } TD * TDgetnexttd(TD *td) { return hcva2ucva(td->nexttd & ~0xF); } void OHCIsetControlHeadED(OHCI *ohci, ED *va) { ohci->ctlheaded = va2hcva(va) & ~0xF; } ED * OHCIgetControlHeadED(OHCI *ohci) { return hcva2ucva(ohci->ctlheaded); } void OHCIsetBulkHeadED(OHCI *ohci, ED *va) { ohci->bulkheaded = va2hcva(va) & ~0xF; } ED * OHCIgetBulkHeadED(OHCI *ohci) { return hcva2ucva(ohci->bulkheaded); } static TD * TDalloc(Ctlr *ub, Endpt *ep, int musthave) /* alloctd */ { TD *t; Endptx *epx; for(;;){ ilock(ub); t = ub->td.free; if(t) break; iunlock(ub); if(up == nil){ if(musthave) panic("TDalloc: out of descs"); return nil; } tsleep(&up->sleep, return0, 0, 100); } ub->td.free = t->next; epx = ep->private; epx->ntd++; ub->td.alloced++; iunlock(ub); memset(t, 0, sizeof(TD)); t->ep = ep; return t; } /* call under ilock */ static void TDfree(Ctlr *ub, TD *t) /* freetd */ { Endptx *epx; if(t == 0) return; if(t->ep){ epx = t->ep->private; epx->ntd--; } else t->ep = nil; /* redundant? */ t->bp = nil; t->next = ub->td.free; ub->td.free = t; ub->td.alloced--; } static void EDfree(Ctlr *ub, ED *t) { TD *td, *next; if(t == 0) return; qlock(&ub->ed); t->next = (ulong)ub->ed.free; ub->ed.free = t; ub->ed.alloced--; if (0) print("%d endpoints allocated\n", ub->ed.alloced); ilock(ub); for(td = EDgethead(t); td; td = next){ next = TDgetnexttd(td); TDfree(ub, td); } iunlock(ub); EDsethead(t, 0); EDsettail(t, 0); qunlock(&ub->ed); } static void waitSOF(Ctlr *ub) { /* * wait for SOF - interlock with interrupt handler so * done queue processed first. */ int frame = ub->uchcca->framenumber & 0x3f; do { delay(2); } while(frame == (ub->uchcca->framenumber & 0x3f)); } static void dumptd(TD *td, char *s) { int i; Endpt *ep; ep = td->ep; print("\t%s: %#p ctrl %#.8lux cbp %#.8lux " "nexttd %#.8lux be %#.8lux, flags %#lux\n", s, td, td->ctrl, td->cbp, td->nexttd, td->be, td->flags); if(ep->epmode != Isomode){ print("\t\tbytes: %ld\n", td->be + 1 - td->cbp); return; } print("\t\t%#ux %#ux %#ux %#ux %#ux %#ux %#ux %#ux\n", td->offsets[0], td->offsets[1], td->offsets[2], td->offsets[3], td->offsets[4], td->offsets[5], td->offsets[6], td->offsets[7]); print("\t\tbytes:"); for(i = 0; i < td->ctrl >> 24 & 0x7; i++) print(" %d", (td->offsets[i+1]-td->offsets[i])&0xfff); print(" %ld\n", (td->be + 1 - td->offsets[i]) & 0xfff); } static void dumped(ED *ed) { TD *tailp, *td; tailp = EDgettail(ed); td = EDgethead(ed); print("dumpED %#p: ctrl %#lux tail %#lux head %#lux next %#lux\n", ed, ed->ctrl, ed->tail, ed->head, ed->next); if(tailp == td) return; do { dumptd(td, "td"); } while((td = TDgetnexttd(td)) != tailp); } static void dumpstatus(Ctlr *ub) { ED *ed; print("dumpstatus %#p, frame %#ux:\n", ub, ub->uchcca->framenumber); print("control %#lux, cmdstat %#lux, intrsts %#lux\n", ub->base->control, ub->base->cmdsts, ub->base->intrsts); print("Control:\n"); for(ed = OHCIgetControlHeadED(ub->base); ed; ed = EDgetnext(ed)) dumped(ed); print("Bulk:\n"); for(ed = OHCIgetBulkHeadED(ub->base); ed; ed = EDgetnext(ed)) dumped(ed); print("Iso:\n"); for(ed = ub->tree->root[0]; ed; ed = EDgetnext(ed)) dumped(ed); print("frame %#ux:\n", ub->uchcca->framenumber); } /* * halt the ED and free input or output transfer descs * called when the relevant lock in the enclosing Endpt is held */ static void EDcancel(Ctlr *ub, ED *ed, int dirin) { int tddir, iso, n; TD *tailp, *headp, *td, *prev, *next; Endpt *ep; if(ed == nil) return; /* halt ED if not already halted */ if(EDgetH(ed) != 1){ EDsetH(ed, 1); waitSOF(ub); } SET(tddir); if((iso = ed->ctrl & ED_F_BIT) != 0) switch((ed->ctrl >> 11) & 0x3){ default: panic("ED iso direction unset"); case Otokin: tddir = Dirin; break; case Otokout: tddir = Dirout; break; } /* can now clean up TD list of ED */ tailp = EDgettail(ed); headp = EDgethead(ed); n = 0; prev = nil; td = headp; while(td != tailp){ ep = td->ep; if(iso == 0) switch((td->ctrl >> TD_DP_SHIFT) & TD_DP_MASK){ default: panic("TD direction unset"); case Otoksetup: tddir = Dirout; break; case Otokin: tddir = Dirin; break; case Otokout: tddir = Dirout; break; } else if(usbhdebug || ep->debug) print("EDcancel: buffered: %d, bytes %ld\n", ep->buffered, td->bytes); next = TDgetnexttd(td); if(dirin == 2 || dirin == tddir){ XEPRINT("%d/%d: EDcancel %d\n", ep->dev->x, ep->x, tddir); /* Remove this sucker */ ep->buffered -= td->bytes; if(ep->buffered < 0) ep->buffered = 0; ilock(ub); ep->dir[tddir].queued--; if(tddir == Dirout){ freeb(td->bp); td->bp = nil; } if(prev) TDsetnexttd(prev, next); else EDsethead(ed, next); TDfree(ub, td); n++; iunlock(ub); }else{ XEPRINT("%d/%d: EDcancel skip %d\n", ep->dev->x, ep->x, tddir); prev = td; } td = next; } XPRINT("EDcancel: %d\n", n); } static void eptactivate(Ctlr *ub, Endpt *ep) { Endptx *epx; qlock(&ub->activends); if(ep->active == 0){ epx = ep->private; XEPRINT("%d/%d: activate\n", ep->dev->x, ep->x); ep->active = 1; /* * set the right speed */ EDsetS(epx->ed, ep->dev->speed); switch(ep->epmode){ case Ctlmode: /* * chain the two descs together, and * bind to beginning of control queue */ EDsetnext(epx->ed, OHCIgetControlHeadED(ub->base)); OHCIsetControlHeadED(ub->base, epx->ed); /* * prompt controller to absorb new queue on next pass */ ub->base->cmdsts |= Clf; XEPRINT("%d/%d: activated in control queue\n", ep->dev->x, ep->x); break; case Bulkmode: EDsetnext(epx->ed, OHCIgetBulkHeadED(ub->base)); OHCIsetBulkHeadED(ub->base, epx->ed); ub->base->cmdsts |= Blf; XEPRINT("%d/%d: activated %s in bulk input queue\n", ep->dev->x, ep->x, ousbmode[ep->mode]); break; case Isomode: if(ep->mode != OWRITE) schedendpt(ub, ep, Dirin); if(ep->mode != OREAD) schedendpt(ub, ep, Dirout); ep->buffered = 0; ep->partial = 0; break; case Intrmode: if(ep->mode != OWRITE) schedendpt(ub, ep, Dirin); if(ep->mode != OREAD) schedendpt(ub, ep, Dirout); break; case Nomode: break; default: panic("eptactivate: wierd epmode %d", ep->epmode); } ep->dir[Dirin].xdone = ep->dir[Dirin].xstarted = 0; ep->dir[Dirout].xdone = ep->dir[Dirout].xstarted = 0; ep->activef = ub->activends.f; ub->activends.f = ep; } qunlock(&ub->activends); } static void EDpullfrombulk(Ctlr *ub, ED *ed) { ED *this, *prev, *next; this = OHCIgetBulkHeadED(ub->base); ub->base->bulkcurred = 0; prev = nil; while(this != nil && this != ed){ prev = this; this = EDgetnext(this); } if(this == nil){ print("EDpullfrombulk: not found\n"); return; } next = EDgetnext(this); if(prev == nil) OHCIsetBulkHeadED(ub->base, next); else EDsetnext(prev, next); EDsetnext(ed, nil); /* wipe out next field */ } static void EDpullfromctl(Ctlr *ub, ED *ed) { ED *this, *prev, *next; this = OHCIgetControlHeadED(ub->base); ub->base->ctlcurred = 0; prev = nil; while(this != nil && this != ed){ prev = this; this = EDgetnext(this); } if(this == nil) panic("EDpullfromctl: not found"); next = EDgetnext(this); if(prev == nil) OHCIsetControlHeadED(ub->base, next); else EDsetnext(prev, next); EDsetnext(ed, nil); /* wipe out next field */ } static void eptdeactivate(Ctlr *ub, Endpt *ep) { ulong ctrl; Endpt **l; Endptx *epx; /* could be O(1) but not worth it yet */ qlock(&ub->activends); if(ep->active){ epx = ep->private; XEPRINT("ohci: eptdeactivate %d/%d\n", ep->dev->x, ep->x); ep->active = 0; for(l = &ub->activends.f; *l != ep; l = &(*l)->activef) if(*l == nil){ qunlock(&ub->activends); panic("usb eptdeactivate"); } *l = ep->activef; /* pull it from the appropriate queue */ ctrl = ub->base->control; switch(ep->epmode){ case Ctlmode: if(ctrl & Cle){ ub->base->control &= ~Cle; waitSOF(ub); } EDpullfromctl(ub, epx->ed); if(ctrl & Cle){ ub->base->control |= Cle; /* * don't fill it if there is nothing in it - * shouldn't be necessary according to the * spec., but practice is different */ if(OHCIgetControlHeadED(ub->base)) ub->base->cmdsts |= Clf; } break; case Bulkmode: if(ctrl & Ble){ ub->base->control &= ~Ble; waitSOF(ub); } EDpullfrombulk(ub, epx->ed); if(ctrl & Ble){ ub->base->control |= Ble; /* * don't fill it if there is nothing in it - * shouldn't be necessary according to the * spec., but practice is different */ if(OHCIgetBulkHeadED(ub->base)) ub->base->cmdsts |= Blf; } break; case Intrmode: case Isomode: if(ep->mode != OWRITE) unschedendpt(ub, ep, Dirin); if(ep->mode != OREAD) unschedendpt(ub, ep, Dirout); waitSOF(ub); break; case Nomode: break; default: panic("eptdeactivate: wierd in.epmode %d", ep->epmode); } } qunlock(&ub->activends); } static void kickappropriatequeue(Ctlr *ub, Endpt *ep, int) { switch(ep->epmode){ case Nomode: break; case Ctlmode: ub->base->cmdsts |= Clf; break; case Bulkmode: ub->base->cmdsts |= Blf; break; case Intrmode: case Isomode: /* no kicking required */ break; default: panic("wierd epmode %d", ep->epmode); } } static void eptenable(Ctlr *ub, Endpt *ep, int dirin) { ED *ed; Endptx *epx; epx = ep->private; ed = epx->ed; if(EDgetH(ed) == 1){ EDsetH(ed, 0); kickappropriatequeue(ub, ep, dirin); if(ep->epmode == Isomode || ep->epmode == Intrmode) waitSOF(ub); } } /* * return smallest power of 2 >= n */ static int flog2(int n) { int i; for(i = 0; (1 << i) < n; i++) ; return i; } /* * return smallest power of 2 <= n */ static int flog2lower(int n) { int i; for(i = 0; (1 << (i + 1)) <= n; i++) ; return i; } static int pickschedq(QTree *qt, int pollms, ulong bw, ulong limit) { int i, j, d, upperb, q; ulong best, worst, total; d = flog2lower(pollms); if(d > qt->depth) d = qt->depth; q = -1; worst = 0; best = ~0; upperb = (1 << (d+1)) - 1; for(i = (1 << d) - 1; i < upperb; i++){ total = qt->bw[0]; for(j = i; j > 0; j = (j - 1) / 2) total += qt->bw[j]; if(total < best){ best = total; q = i; } if(total > worst) worst = total; } if(worst + bw >= limit) return -1; return q; } static int schedendpt(Ctlr *ub, Endpt *ep, int dirin) { int q; ED *ed; Endptx *epx; epx = ep->private; qlock(ub->tree); /* TO DO: bus bandwidth limit */ q = pickschedq(ub->tree, ep->pollms, ep->bw, ~0); XEPRINT("schedendpt, dir %d Q index %d, ms %d, bw %ld\n", dirin, q, ep->pollms, ep->bw); if(q < 0){ qunlock(ub->tree); return -1; } ub->tree->bw[q] += ep->bw; ed = ub->tree->root[q]; ep->sched = q; EDsetnext(epx->ed, EDgetnext(ed)); EDsetnext(ed, epx->ed); XEPRINT("%d/%d: sched on q %d pollms %d\n", ep->dev->x, ep->x, q, ep->pollms); qunlock(ub->tree); return 0; } static void unschedendpt(Ctlr *ub, Endpt *ep, int dirin) { int q; ED *prev, *this, *next; Endptx *epx; epx = ep->private; if((q = ep->sched) < 0) return; qlock(ub->tree); ub->tree->bw[q] -= ep->bw; prev = ub->tree->root[q]; this = EDgetnext(prev); while(this != nil && this != epx->ed){ prev = this; this = EDgetnext(this); } if(this == nil) print("unschedendpt %d %d: not found\n", dirin, q); else{ next = EDgetnext(this); EDsetnext(prev, next); } qunlock(ub->tree); } /* at entry, *e is partly populated */ static void epalloc(Usbhost *uh, Endpt *ep) { int id; Endptx *epx; Ctlr *ctlr; Udev *d; TD *dtd; XEPRINT("ohci: epalloc from devusb\n"); ctlr = uh->ctlr; id = ep->id; d = ep->dev; epx = malloc(sizeof(Endptx)); memset(epx, 0, sizeof(Endptx)); ep->private = epx; dtd = nil; if(waserror()){ XEPRINT("ohci: epalloc error\n"); EDfree(ctlr, epx->ed); epx->ed = nil; TDfree(ctlr, dtd); nexterror(); } if(epx->ed) error("usb: already allocated"); if((epx->ed = EDalloc(ctlr)) == nil) error(Enomem); ep->bw = 1; /* all looks the same currently */ if((dtd = TDalloc(ctlr, ep, 0)) == nil) error(Enomem); EDinit(epx->ed, ep->maxpkt, 0, 0, 0, 0, id, d->id, dtd, dtd, 0, 0, 0); XEPRINT("ohci: epalloc done\n"); poperror(); } static void epfree(Usbhost *uh, Endpt *ep) { Endptx *epx; Ctlr *ctlr; epx = ep->private; ctlr = uh->ctlr; XEPRINT("ohci: epfree %d/%d from devusb\n", ep->dev->x, ep->x); if(ep->active) panic("epfree: active"); EDfree(ctlr, epx->ed); epx->ed = nil; free(epx); ep->private = nil; } static void epopen(Usbhost *uh, Endpt *ep) { Ctlr *ctlr; XEPRINT("ohci: epopen %d/%d from devusb\n", ep->dev->x, ep->x); ctlr = uh->ctlr; if((ep->epmode == Isomode || ep->epmode == Intrmode) && ep->active) error("already open"); eptactivate(ctlr, ep); } static int setfrnum(Ctlr *ub, Endpt *ep) { short frnum, d; static int adj; /* adjust frnum as necessary... */ frnum = ub->base->fmnumber + (ep->buffered*1000)/ep->bw; d = frnum - ep->frnum; if(d < -100 || d > 100){ /* We'd play in the past */ if(0 && d > 1000) /* We're more than a second off: */ print("d %d, done %d, started %d, buffered %d\n", d, ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted, ep->buffered); if(ep->dir[Dirout].xdone == ep->dir[Dirout].xstarted) ep->buffered = adj = 0; if(0 && (adj++ & 0xff) == 0) print("adj %d %d\n", d, ep->buffered); ep->frnum = ub->base->fmnumber + 10 + (ep->buffered*1000)/ep->bw; ep->partial = 0; return 1; } return 0; } static int ceptdone(void *arg) { Endpt *ep; ep = arg; return ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted >= 0 || ep->dir[Dirout].err; } static void epclose(Usbhost *uh, Endpt *ep) { Ctlr *ctlr; int xdone, part; Endptx *epx; XEPRINT("ohci: epclose %d/%d from devusb, %d buffered\n", ep->dev->x, ep->x, ep->buffered); ctlr = uh->ctlr; epx = ep->private; if(ep->epmode == Isomode && ep->active){ qlock(&ep->wlock); if(ep->partial && setfrnum(ctlr, ep) == 0){ part = ep->partial; memset(ep->bpartial->wp, 0, ep->maxpkt - ep->partial); ep->bpartial->wp = ep->bpartial->rp + ep->maxpkt; qtd(uh->ctlr, ep, Dirout, nil, ep->bpartial->rp, ep->bpartial->wp, Otokout, TD_FLAGS_LAST); XEPRINT("epclose: wrote partial block %d\n", part); ep->partial = 0; } qunlock(&ep->wlock); XEPRINT("epclose: wait for outstanding TDs, xdone %d" ", xstarted %d, buffered %d, queued %d\n", ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted, ep->buffered, ep->dir[Dirout].queued); while(ep->dir[Dirout].err == nil && (xdone = ep->dir[Dirout].xdone) - ep->dir[Dirout].xstarted < 0){ tsleep(&ep->dir[Dirout].rend, ceptdone, ep, 500); if(xdone == ep->dir[Dirout].xdone){ print("no progress\n"); break; } } if(ep->dir[Dirout].err) XEPRINT("error: %s\n", ep->dir[Dirout].err); if(ep->buffered) XEPRINT("epclose: done waiting, xdone %d, xstarted %d, " "buffered %d, queued %d\n", ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted, ep->buffered, ep->dir[Dirout].queued); } lock(epx); EDcancel(ctlr, epx->ed, 2); unlock(epx); if(ep->epmode == Isomode && ep->buffered) XEPRINT("epclose: after cancelling, xdone %d, xstarted %d" ", buffered %d, queued %d\n", ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted, ep->buffered, ep->dir[Dirout].queued); eptdeactivate(ctlr, ep); } static void epmaxpkt(Usbhost *, Endpt *ep) { Endptx *epx; epx = ep->private; XEPRINT("ohci: epmaxpkt %d/%d: %d\n", ep->dev->x, ep->x, ep->maxpkt); EDsetMPS(epx->ed, ep->maxpkt); } static void epmode(Usbhost *uh, Endpt *ep) { int tok, reactivate; Ctlr *ctlr; Endptx *epx; epx = ep->private; ctlr = uh->ctlr; XEPRINT("ohci: epmode %d/%d %s ā†’ %s\n", ep->dev->x, ep->x, usbmode[ep->epmode], usbmode[ep->epnewmode]); reactivate = 0; if(ep->epnewmode != ep->epmode) if(reactivate = ep->active){ XEPRINT("ohci: epmode %d/%d: already open\n", ep->dev->x, ep->x); eptdeactivate(ctlr, ep); } EDsetS(epx->ed, ep->dev->speed); switch(ep->epnewmode){ default: panic("devusb is sick"); case Intrmode: // ep->debug++; ep->bw = ep->maxpkt*1000/ep->pollms; /* bytes/sec */ XEPRINT("ohci: epmode %d/%d %s, intr: maxpkt %d, pollms %d, bw %ld\n", ep->dev->x, ep->x, ousbmode[ep->mode], ep->maxpkt, ep->pollms, ep->bw); break; case Isomode: // ep->debug++; ep->rem = 999; switch(ep->mode){ default: panic("ep mode"); case ORDWR: error("iso unidirectional only"); case OREAD: tok = Otokin; error("iso read not implemented"); break; case OWRITE: tok = Otokout; break; } XEPRINT("ohci: epmode %d/%d %s, iso: maxpkt %d, pollms %d, hz %d, samp %d\n", ep->dev->x, ep->x, ousbmode[ep->mode], ep->maxpkt, ep->pollms, ep->hz, ep->samplesz); ep->bw = ep->hz * ep->samplesz; /* bytes/sec */ /* Use Iso TDs: */ epx->ed->ctrl |= ED_F_BIT; /* Set direction in ED, no room in an Iso TD for this */ epx->ed->ctrl &= ~(ED_D_MASK << ED_D_SHIFT); epx->ed->ctrl |= tok << ED_D_SHIFT; break; case Bulkmode: // ep->debug++; /* * Each Bulk device gets a queue head hanging off the * bulk queue head */ break; case Ctlmode: break; } ep->epmode = ep->epnewmode; epmaxpkt(uh, ep); if(reactivate){ XEPRINT("ohci: epmode %d/%d: reactivate\n", ep->dev->x, ep->x); eptactivate(ctlr, ep); } } static long qtd(Ctlr *ub, Endpt *ep, int dirin , Block *bp, uchar *base, uchar *limit, int pid, ulong flags) { int fc, mps; ulong x; uchar *p; ED *ed; Endptx *epx; TD *dummytd, *td; epx = ep->private; ed = epx->ed; td = hcva2ucva(ed->tail); td->flags = flags; if(ep->epmode == Isomode){ x = va2hcva(base); td->cbp = x & ~0xfff; x &= 0xfff; p = base; setfrnum(ub, ep); td->ctrl = ep->frnum & 0xffff; fc = 0; for(;;){ /* Calculate number of samples in next packet */ mps = (ep->hz + ep->rem)/1000; /* rem is the partial sample left over */ ep->rem += ep->hz - 1000*mps; mps *= ep->samplesz; if(mps > ep->maxpkt) panic("Packet size"); if(ep->partial == 0 && mps > limit - p){ /* Save this data for later ... */ ep->partial = limit - p; if(fc-- == 0) return p - base; /* No TD */ /* We do have a TD, send this one off normally */ td->flags |= TD_FLAGS_LAST; break; }else if(mps >= limit - p){ td->flags |= TD_FLAGS_LAST; mps = limit - p; ep->partial = 0; } td->offsets[fc] = 0xe000 | x; x += mps; p += mps; ep->frnum++; if(fc == 7 || limit - p == 0) break; fc++; } td->ctrl |= fc << 24; }else{ td->cbp = va2hcva(base); td->ctrl = (pid & TD_DP_MASK) << TD_DP_SHIFT; p = base; mps = 0x2000 - ((ulong)p & 0xfff); if(mps > ep->maxpkt) mps = ep->maxpkt; if(mps >= limit - p){ mps = limit - base; td->flags |= TD_FLAGS_LAST; } p += mps; } td->be = va2hcva(p == nil? nil: p - 1); td->ep = ep; td->bytes = p - base; ep->buffered += td->bytes; td->bp = bp; if(td->flags & TD_FLAGS_LAST) ep->dir[dirin].xstarted++; if(dirin == Dirout && bp) _xinc(&bp->ref); dummytd = TDalloc(ub, ep, 1); TDsetnexttd(td, dummytd); ep->dir[dirin].queued++; EDsettail(ed, dummytd); if(usbhdebug || ep->debug) dumptd(td, "qtd: before"); kickappropriatequeue(ub, ep, dirin); return p - base; } Block* allocrcvb(long size) { Block *b; int asize; asize = ROUND(size, dcls) + dcls - 1; /* * allocate enough to align rp to dcls, and have an integral number * of cache lines in the buffer */ while(waserror()) tsleep(&up->sleep, return0, 0, 100); b = allocb(asize); poperror(); /* * align the rp and wp */ b->rp = b->wp = (uchar *)ROUND((ulong)b->rp, dcls); /* * invalidate the cache lines which enclose the buffer */ if(IOCACHED){ uchar *p; p = b->rp; while(size > 0){ invalidatedcacheva((ulong)p); p += dcls; size -= dcls; } } return b; } /* * build the periodic scheduling tree: * framesize must be a multiple of the tree size */ static QTree * mkqhtree(Ctlr *ub) { int i, n, d, o, leaf0, depth; ED **tree; QTree *qt; depth = flog2(32); n = (1 << (depth+1)) - 1; qt = mallocz(sizeof(*qt), 1); if(qt == nil) return nil; qt->nel = n; qt->depth = depth; qt->bw = mallocz(n * sizeof(qt->bw), 1); if(qt->bw == nil){ free(qt); return nil; } tree = mallocz(n * sizeof(ED *), 1); if(tree == nil){ free(qt->bw); free(qt); return nil; } for(i = 0; i < n; i++) if((tree[i] = EDalloc(ub)) == nil) break; if(i < n){ int j; for(j = 0; j < i; j++) EDfree(ub, tree[j]); free(tree); free(qt->bw); free(qt); return nil; } qt->root = tree; EDinit(qt->root[0], 8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0); for(i = 1; i < n; i++) EDinit(tree[i], 8, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, tree[(i-1)/2]); /* distribute leaves evenly round the frame list */ leaf0 = n / 2; for(i = 0; i < 32; i++){ o = 0; for(d = 0; d < depth; d++){ o <<= 1; if(i & (1 << d)) o |= 1; } if(leaf0 + o >= n){ print("leaf0=%d o=%d i=%d n=%d\n", leaf0, o, i, n); break; } ub->uchcca->intrtable[i] = va2hcva(tree[leaf0 + o]); } return qt; } static void portreset(Usbhost *uh, int port) { Ctlr *ctlr; XIPRINT("ohci: portreset(port %d) from devusb\n", port); ctlr = uh->ctlr; /* should check that device not being configured on other port? */ qlock(&ctlr->resetl); if(waserror()){ qunlock(&ctlr->resetl); nexterror(); } ilock(ctlr); ctlr->base->rhportsts[port - 1] = Spp | Spr; while((ctlr->base->rhportsts[port - 1] & Prsc) == 0){ iunlock(ctlr); XIPRINT("ohci: portreset, wait for reset complete\n"); ilock(ctlr); } ctlr->base->rhportsts[port - 1] = Prsc; iunlock(ctlr); poperror(); qunlock(&ctlr->resetl); } static void portenable(Usbhost *uh, int port, int on) { Ctlr *ctlr; XIPRINT("ohci: portenable(port %d, on %d) from devusb\n", port, on); ctlr = uh->ctlr; /* should check that device not being configured on other port? */ qlock(&ctlr->resetl); if(waserror()){ qunlock(&ctlr->resetl); nexterror(); } ilock(ctlr); if(on) ctlr->base->rhportsts[port - 1] = Spe | Spp; else ctlr->base->rhportsts[port - 1] = Cpe; iunlock(ctlr); poperror(); qunlock(&ctlr->resetl); } static int getportstatus(Ctlr *ub, int port) { int v; ulong ohcistatus; ohcistatus = ub->base->rhportsts[port - 1]; v = 0; if(ohcistatus & Ccs) v |= DevicePresent; if(ohcistatus & Pes) v |= PortEnable; if(ohcistatus & Pss) v |= Suspend; if(ohcistatus & Prs) v |= PortReset; else { /* port is not in reset; these potential writes are ok */ if(ohcistatus & Csc){ /* TODO: could notify usbd equivalent here */ v |= ConnectStatusChange; ub->base->rhportsts[port - 1] = Csc; } if(ohcistatus & Pesc){ /* TODO: could notify usbd equivalent here */ v |= PortEnableChange; ub->base->rhportsts[port - 1] = Pesc; } } if(ohcistatus & Lsda) v |= SlowDevice; if(ohcistatus & Ccs) XIPRINT("portstatus(%d) = OHCI %#.8lux UHCI %#.8ux\n", port, ohcistatus, v); return v; } /* print any interesting stuff here for debugging purposes */ static void usbdebug(Usbhost *uh, char *s, char *se) { Udev *dev; Endpt *ep; int n, i, j; n = 0; for(i = 0; i < MaxUsbDev; i++) if(uh->dev[i]) n++; s = seprint(s, se, "OHCI, %d devices\n", n); for(i = 0; i < MaxUsbDev; i++){ if((dev = uh->dev[i]) == nil) continue; s = seprint(s, se, "dev %#6.6lux, %d epts\n", dev->csp, dev->npt); for(j = 0; j < nelem(dev->ep); j++){ if((ep = dev->ep[j]) == nil) continue; if(ep->epmode >= 0 && ep->epmode < Nmodes) s = seprint(s, se, "ept %d/%d: %s %#6.6lux " "maxpkt %d %s\n", dev->x, ep->x, usbmode[ep->epmode], ep->csp, ep->maxpkt, ep->active ? "active" : "idle"); else{ s = seprint(s, se, "ept %d/%d: bad mode %#6.6lux\n", dev->x, ep->x, ep->csp); continue; } switch(ep->epmode){ case Nomode: break; case Ctlmode: break; case Bulkmode: break; case Intrmode: s = seprint(s, se, "\t%d ms\n", ep->pollms); break; case Isomode: s = seprint(s, se, "\t%d ms, remain %d, " "partial %d, buffered %d, " "xdone %d, xstarted %d, err %s\n", ep->pollms, ep->remain, ep->partial, ep->buffered, ep->dir[ep->mode == OREAD].xdone, ep->dir[ep->mode == OREAD].xstarted, ep->dir[ep->mode == OREAD].err ? ep->dir[ep->mode == OREAD].err : "no"); break; } } } } /* this is called several times every few seconds, possibly due to usbd */ static void portinfo(Usbhost *uh, char *s, char *se) { int x, i, j; Ctlr *ctlr; XIPRINT("ohci: portinfo from devusb\n"); ctlr = uh->ctlr; for(i = 1; i <= ctlr->nports; i++){ ilock(ctlr); x = getportstatus(ctlr, i); iunlock(ctlr); s = seprint(s, se, "%d %ux", i, x); for(j = 0; j < nelem(portstatus); j++) if((x & portstatus[j].bit) != 0) s = seprint(s, se, " %s", portstatus[j].name); s = seprint(s, se, "\n"); } } void interrupt(Ureg *, void *arg) { int dirin, cc; ulong ctrl, status; uchar *p; Block *bp; Ctlr *ub; Endpt *ep; Endptx *epx; TD *donetd, *nexttd; Usbhost *eh; XIPRINT("ohci: interrupt\n"); eh = arg; ub = eh->ctlr; status = ub->base->intrsts; status &= ub->base->intrenable; status &= Oc | Rhsc | Fno | Ue | Rd | Sf | Wdh | So; if(status & Wdh){ /* LSb of donehead has bit that says there are other interrupts */ donetd = hcva2ucva(ub->uchcca->donehead & ~0xf); XIPRINT("donetd %#p\n", donetd); }else donetd = 0; ub->base->intrsts = status; status &= ~Wdh; while(donetd){ ctrl = donetd->ctrl; ep = donetd->ep; bp = donetd->bp; donetd->bp = nil; epx = ep->private; ohciinterrupts[ep->epmode]++; dirin = ((ctrl >> TD_DP_SHIFT) & TD_DP_MASK) == Otokin; ep->buffered -= donetd->bytes; if(ep->epmode == Isomode){ dirin = Dirout; if(ep->buffered < 0){ print("intr: buffered %d bytes %ld\n", ep->buffered, donetd->bytes); ep->buffered = 0; } } cc = (ctrl >> TD_CC_SHIFT) & TD_CC_MASK; if((usbhdebug || ep->debug) && (cc != 0 && cc != 9)){ print("%d/%d: cc %d frnum %#lux\n", ep->dev->x, ep->x, cc, ub->base->fmnumber); dumptd(donetd, "after"); } switch(cc){ case 8: /* Overrun, Not an error */ epx->overruns++; /* fall through to no error code */ case 0: /* noerror */ if((donetd->flags & TD_FLAGS_LAST) == 0) break; if(dirin){ if(bp){ p = hcva2va(donetd->be + 1); if(p < bp->wp) print("interrupt: bp: rp %#p" ", wp %#pā†’%#p\n", bp->rp, bp->wp, p); bp->wp = p; } } ep->dir[dirin].xdone++; wakeup(&ep->dir[dirin].rend); break; case 9: /* underrun */ if(bp){ p = hcva2va(donetd->cbp); XEIPRINT("interrupt: bp: rp %#p, wp " "%#pā†’%#p\n", bp->rp, bp->wp, p); bp->wp = p; } if((donetd->flags & TD_FLAGS_LAST) == 0){ XEIPRINT("Underrun\n"); ep->dir[dirin].err = Eunderrun; } ep->dir[dirin].xdone++; wakeup(&ep->dir[dirin].rend); break; case 1: /* CRC */ ep->dir[dirin].err = "CRC error"; goto error; case 2: /* Bitstuff */ ep->dir[dirin].err = "Bitstuff error"; goto error; case 3: ep->dir[dirin].err = "data toggle mismatch"; goto error; case 4: /* Stall */ ep->dir[dirin].err = Estalled; goto error; case 5: /* No response */ ep->dir[dirin].err = "No response"; goto error; case 6: /* PIDcheck */ ep->dir[dirin].err = "PIDcheck"; goto error; case 7: /* UnexpectedPID */ ep->dir[dirin].err = "badPID"; goto error; error: XEPRINT("fail %d (%lud)\n", cc, (ctrl >> TD_EC_SHIFT) & TD_EC_MASK); ep->dir[dirin].xdone++; wakeup(&ep->dir[dirin].rend); break; default: panic("cc %ud unimplemented", cc); } ep->dir[dirin].queued--; /* Clean up blocks used for transfers */ if(dirin == Dirout) freeb(bp); nexttd = TDgetnexttd(donetd); TDfree(ub, donetd); donetd = nexttd; } if(status & Sf){ if (0) XIPRINT(("sof!!\n")); // wakeup(&ub->sofr); /* sofr doesn't exist anywhere! */ status &= ~Sf; } if(status & Ue){ ulong curred; // usbhdbg(); /* TODO */ curred = ub->base->periodcurred; print("usbh: unrecoverable error frame %#.8lux ed %#.8lux, " "ints %d %d %d %d\n", ub->base->fmnumber, curred, ohciinterrupts[1], ohciinterrupts[2], ohciinterrupts[3], ohciinterrupts[4]); if(curred) dumped(hcva2ucva(curred)); } if(status) IPRINT(("interrupt: unhandled interrupt %#.8lux\n", status)); } static void usbhattach(Ctlr *ub) /* TODO: is unused now, but it fiddles ctlr */ { ulong ctrl; if(ub == nil || ub->base == 0) error(Enodev); ctrl = ub->base->control; if((ctrl & HcfsMask) != HcfsOperational){ ctrl = (ctrl & ~HcfsMask) | HcfsOperational; ub->base->control = ctrl; ub->base->rhsts = Sgp; } } static int reptdone(void *arg) { Endpt *ep; ep = arg; return ep->dir[Dirin].err /* Expression crafted to deal with wrap around: */ || ep->dir[Dirin].xdone - ep->dir[Dirin].xstarted >= 0; } Block * breadusbh(Ctlr *ub, Endpt *ep, long n) /* guts of read() */ { long in, l; uchar *p; Block *bp; Endptx *epx; epx = ep->private; qlock(&ep->rlock); EDsetC(epx->ed, ep->rdata01); XEPRINT("breadusbh(%d/%d, %ld, dt %d)\n", ep->dev->x, ep->x, n, ep->rdata01); eptenable(ub, ep, Dirin); if(waserror()){ EDcancel(ub, epx->ed, Dirin); ep->dir[Dirin].err = nil; qunlock(&ep->rlock); nexterror(); } if(ep->dir[Dirin].err != nil) error("usb: can't happen"); bp = allocrcvb(n); in = n; if(in > bp->lim - bp->wp){ print("usb: read larger than block\n"); in = bp->lim - bp->wp; } p = bp->rp; do{ l = qtd(ub, ep, Dirin, bp, p, p+in, Otokin, 0); p += l; in -= l; }while(in > 0); sleep(&ep->dir[Dirin].rend, reptdone, ep); if(ep->dir[Dirin].err){ EDcancel(ub, epx->ed, Dirin); if(ep->dir[Dirin].err == Eunderrun) ep->dir[Dirin].err = nil; else error(ep->dir[Dirin].err); } XEPRINT("breadusbh(%d/%d, %ld) returned %ld\n", ep->dev->x, ep->x, n, BLEN(bp)); poperror(); qunlock(&ep->rlock); ep->rdata01 = EDgetC(epx->ed); return bp; } static long read(Usbhost *uh, Endpt *ep, void *a, long n, vlong off) /* TODO off */ { long l; Block *bp; Ctlr *ub; XEPRINT("ohci: read from devusb\n"); USED(off); ub = uh->ctlr; XEPRINT("%d/%d: read %#p %ld\n", ep->dev->x, ep->x, a, n); bp = breadusbh(ub, ep, n); l = BLEN(bp); memmove(a, bp->rp, l); printdata(bp->rp, 1, l); XEPRINT("ohci: read %ld\n\n", l); freeb(bp); return l; } static int weptdone(void *arg) { Endpt *ep; ep = arg; /* * success when all operations are done or when less than * a second is buffered in iso connections */ return ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted >= 0 || (ep->epmode == Isomode && ep->buffered <= ep->bw) || ep->dir[Dirout].err; } /* TODO: honour off */ static long write(Usbhost *uh, Endpt *ep, void *a, long n, vlong off, int tok) { long m; short frnum; uchar *p = a; Block *b; Ctlr *ub; Endptx *epx; XEPRINT("ohci: write(addr %p, bytes %ld, off %lld, tok %d) from devusb\n", a, n, off, tok); epx = ep->private; ub = uh->ctlr; qlock(&ep->wlock); XEPRINT("%d/%d: write %#p %ld %s\n", ep->dev->x, ep->x, a, n, tok == Otoksetup? "setup": "out"); if(ep->dir[Dirout].xdone - ep->dir[Dirout].xstarted > 0){ print("done > started, %d %d\n", ep->dir[Dirout].xdone, ep->dir[Dirout].xstarted); ep->dir[Dirout].xdone = ep->dir[Dirout].xstarted; } if(waserror()){ lock(epx); EDcancel(ub, epx->ed, Dirout); unlock(epx); ep->dir[Dirout].err = nil; qunlock(&ep->wlock); nexterror(); } eptenable(ub, ep, Dirout); EDsetC(epx->ed, ep->wdata01); if(ep->dir[Dirout].err) error(ep->dir[Dirout].err); if((m = n) == 0 || p == nil) qtd(ub, ep, Dirout, 0, 0, 0, tok, TD_FLAGS_LAST); else{ b = allocb(m+ep->partial); if(ep->partial){ memmove(b->wp, ep->bpartial->rp, ep->partial); b->wp += ep->partial; ep->partial = 0; } validaddr((uintptr)p, m, 0); /* DEBUG */ memmove(b->wp, a, m); b->wp += m; printdata(b->rp, 1, m); m = BLEN(b); dcclean(b->rp, m); if(ep->epmode == Isomode && ep->buffered <= ep->bw<<1){ sleep(&ep->dir[Dirout].rend, weptdone, ep); if(ep->dir[Dirout].err) { freeb(b); error(ep->dir[Dirout].err); } } while(m > 0){ int l; l = qtd(ub, ep, Dirout, b, b->rp, b->wp, tok, 0); b->rp += l; m -= l; tok = Otokout; if(ep->partial){ /* We have some data to save */ if(ep->bpartial == nil) ep->bpartial = allocb(ep->maxpkt); if(ep->partial != m) print("curious: %d != %ld\n", ep->partial, m); memmove(ep->bpartial->rp, b->rp, ep->partial); ep->bpartial->wp = ep->bpartial->rp + ep->partial; break; } } freeb(b); /* qtd calls incref; this undoes the one too many */ } if(ep->epmode != Isomode){ sleep(&ep->dir[Dirout].rend, weptdone, ep); if(ep->dir[Dirout].err) error(ep->dir[Dirout].err); }else if(0 && (frnum = ep->frnum - ub->base->fmnumber) < 0) print("too late %d\n", frnum); poperror(); qunlock(&ep->wlock); XEPRINT("ohci: wrote %ld\n\n", n); ep->wdata01 = EDgetC(epx->ed); return n; } static void init(Usbhost*) { XIPRINT("ohci: init from devusb\n"); } static void scanpci(void) { ulong mem; Ctlr *ctlr; Pcidev *p; static int already = 0; if(already) return; already = 1; p = nil; while(p = pcimatch(p, 0, 0)) { /* * Find OHCI controllers (Programming Interface = 0x10). */ if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || p->ccrp != 0x10) continue; mem = p->mem[0].bar & ~0x0F; XPRINT("usbohci: %x/%x port %#lux size %#x irq %d\n", p->vid, p->did, mem, p->mem[0].size, p->intl); if(mem == 0){ print("usbohci: failed to map registers\n"); continue; } if(p->intl == 0xFF || p->intl == 0) { print("usbohci: no irq assigned for port %#lux\n", mem); continue; } ctlr = malloc(sizeof(Ctlr)); ctlr->pcidev = p; ctlr->base = vmap(mem, p->mem[0].size); XPRINT("scanpci: ctlr %#p, base %#p\n", ctlr, ctlr->base); pcisetbme(p); pcisetpms(p, 0); if(ctlrhead != nil) ctlrtail->next = ctlr; else ctlrhead = ctlr; ctlrtail = ctlr; } } static int reset(Usbhost *uh) { int i, linesize; ulong io, fminterval, ctrl; Ctlr *ctlr; HCCA *atmp; OHCI *ohci; Pcidev *p; QTree *qt; /* * data cache line size; probably doesn't matter on pc * except that it must be a power of 2 for xspanalloc. */ dcls = 32; scanpci(); /* * Any adapter matches if no uh->port is supplied, * otherwise the ports must match. */ for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){ if(ctlr->active) continue; if(uh->port == 0 || uh->port == (uintptr)ctlr->base){ ctlr->active = 1; break; } } if(ctlr == nil) return -1; io = (uintptr)ctlr->base; /* TODO: correct? */ ohci = ctlr->base; XPRINT("OHCI ctlr %#p, base %#p\n", ctlr, ohci); p = ctlr->pcidev; uh->ctlr = ctlr; uh->port = io; uh->irq = p->intl; uh->tbdf = p->tbdf; XPRINT("OHCI revision %ld.%ld\n", (ohci->revision >> 4) & 0xf, ohci->revision & 0xf); XPRINT("Host revision %ld.%ld\n", (ohci->hostrevision >> 4) & 0xf, ohci->hostrevision & 0xf); ctlr->nports = ohci->rhdesca & 0xff; XPRINT("HcControl %#.8lux, %d ports\n", ohci->control, ctlr->nports); delay(100); /* anything greater than 50 should ensure reset is done */ if(ohci->control == ~0){ ctlrhead = nil; return -1; } /* * usually enter here in reset, wait till its through, * then do our own so we are on known timing conditions. */ ohci->control = 0; delay(100); fminterval = ohci->fminterval; /* legacy support register: turn off lunacy mode */ pcicfgw16(p, 0xc0, 0x2000); /* * transfer descs need at least 16 byte alignment, but * align to dcache line size since the access will always be uncached. * linesize must be a power of 2 for xspanalloc. */ linesize = dcls; if(linesize < 0x20) linesize = 0x20; ctlr->td.pool = va2ucva(xspanalloc(Ntd * sizeof(TD), linesize, 0)); if(ctlr->td.pool == nil) panic("usbohci: no memory for TD pool"); for(i = Ntd - 1; --i >= 0;){ ctlr->td.pool[i].next = ctlr->td.free; ctlr->td.free = &ctlr->td.pool[i]; } ctlr->td.alloced = 0; ctlr->ed.pool = va2ucva(xspanalloc(Ned*sizeof(ED), linesize, 0)); if(ctlr->ed.pool == nil) panic("usbohci: no memory for ED pool"); for(i = Ned - 1; --i >= 0;){ ctlr->ed.pool[i].next = (ulong)ctlr->ed.free; ctlr->ed.free = &ctlr->ed.pool[i]; } ctlr->ed.alloced = 0; atmp = xspanalloc(sizeof(HCCA), 256, 0); if(atmp == nil) panic("usbhreset: no memory for HCCA"); memset(atmp, 0, sizeof(*atmp)); ctlr->uchcca = atmp; qt = mkqhtree(ctlr); if(qt == nil){ panic("usb: can't allocate scheduling tree"); return -1; } ctlr->tree = qt; ctlr->base = ohci; /* time to move to rest then suspend mode. */ ohci->cmdsts = 1; /* reset the block */ while(ohci->cmdsts == 1) continue; /* wait till reset complete, OHCI says 10us max. */ /* * now that soft reset is done we are in suspend state. * Setup registers which take in suspend state * (will only be here for 2ms). */ ohci->hcca = va2hcva(ctlr->uchcca); OHCIsetControlHeadED(ctlr->base, 0); ctlr->base->ctlcurred = 0; OHCIsetBulkHeadED(ctlr->base, 0); ctlr->base->bulkcurred = 0; ohci->intrenable = Mie | Wdh | Ue; ohci->control |= Cle | Ble | Ple | Ie | HcfsOperational; /* set frame after operational */ ohci->fminterval = (fminterval & ~(HcFmIntvl_FSMaxpack_MASK << HcFmIntvl_FSMaxpack_SHIFT)) | 5120 << HcFmIntvl_FSMaxpack_SHIFT; ohci->rhdesca = 1 << 9; for(i = 0; i < ctlr->nports; i++) ohci->rhportsts[i] = Spp | Spr; delay(100); ctrl = ohci->control; if((ctrl & HcfsMask) != HcfsOperational){ XIPRINT("ohci: reset, take ctlr out of Suspend\n"); ctrl = (ctrl & ~HcfsMask) | HcfsOperational; ohci->control = ctrl; ohci->rhsts = Sgp; } p = ctlr->pcidev; ctlr->irq = p->intl; ctlr->tbdf = p->tbdf; /* * Linkage to the generic USB driver. */ uh->init = init; uh->interrupt = interrupt; uh->debug = usbdebug; uh->portinfo = portinfo; uh->portreset = portreset; uh->portenable = portenable; uh->epalloc = epalloc; uh->epfree = epfree; uh->epopen = epopen; uh->epclose = epclose; uh->epmode = epmode; uh->epmaxpkt = epmaxpkt; uh->read = read; uh->write = write; uh->tokin = Otokin; uh->tokout = Otokout; uh->toksetup = Otoksetup; return 0; } void usbohcilink(void) { addusbtype("ohci", reset); }