#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" #include "usbehci.h" /* * USB debug port support for Net20DC Ajays debug cable. * Still WIP. * * ehci must avoid reset of the ctlr whose capio is at * ehcidebugcapio, if not nil. * * Note that using this requires being able to use vmap() to map ehci * registers for I/O. This means that MMU code must be initialized. * * CAUTION: The debug device has two ports but they are NOT * the same. If you put the "USB 2.0 debug cable" text in front of you * the port to the right is the one to attach to the debug port. The port * on the left is the one to attach to the host used for debugging. * Thigs are worse. Power for the "debug cable" is taken only from the * port attached to the target machine. * This means the device will malfunction unless you do the plug/unplug * start serial dance in the wrong order. * * For the machine I tried, I plug the debugged port and cold start the * machine. * Then I do this each time I want to start again: * - unplug the debugging port * - warm reboot the debugged machine * - wait until bootfile prompt (device has its power) * - plug the debugging port * - start usb/serail * - con /dev/eiaUX/data * * If the debug device seems to be stuck I unplug the debugged port * to remove power from the device and make it restart. * * This is so clumsy that the bug might be ours, but I wouldn't bet. * * The usb/serial driver talking to us must be sure that maxpkt is * set to 8 bytes. Otherwise, the debug device hangs. It's picky this cable. * * There's also some problem in the usb/serial kludge for con'ing this: * we miss some messages. However, this could be due to the debugging * machine being too slow. */ typedef struct Ctlr Ctlr; enum { Pdebugport = 0x0A, /* ehci debug port PCI cap. */ Ddebug = 0x0A, /* debug descriptor type */ Ddebuglen = 4, /* debug descriptor length */ Debugmode = 6, /* debug mode feature */ Daddr = 127, /* debug device address */ Tokin = 0x69, Tokout = 0xE1, Toksetup = 0x2D, Ack = 0xD2, Nak = 0x5A, Nyet = 0x96, Stall = 0x1E, Data0 = 0xC3 << Pspidshift, Data1 = 0x4B << Pspidshift, Read = 0, /* mode for ucio() */ Write = 1, }; struct Ctlr { Lock; Pcidev* pcidev; Ecapio* capio; /* Capability i/o regs */ Eopio* opio; /* Operational i/o regs */ Edbgio* dbgio; /* Debug port i/o regs */ /* software */ int port; /* port number */ int epin; /* input endpoint address */ int epout; /* output endpoint address */ int sc; /* start char in buf */ int ec; /* end char in buf */ char buf[8]; /* read buffer */ Queue* iq; /* input queue */ Queue* oq; /* output queue */ int consid; /* console id */ }; #undef dprint #define dprint if(debug)iprint static int debug = 1; static Ctlr ctlr; /* one console at most */ static void ehcirun(Ctlr *ctlr, int on) { int i; Eopio *opio; dprint("usbcons: %s", on ? "starting" : "halting"); opio = ctlr->opio; if(on) opio->cmd |= Crun; else opio->cmd = Cstop; 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) dprint("usbcons: %s cmd timed out\n", on ? "run" : "halt"); dprint(" sts %#ulx\n", opio->sts); } static int rpid(ulong csw) { return (csw >> Prpidshift) & Prpidmask; } static int spid(ulong csw) { return (csw >> Pspidshift) & Pspidmask; } /* * Perform I/O. * This returns -1 upon errors but -2 upon naks after (re)trying. * The caller could probably retry even more upon naks. */ static int ucio(Edbgio *dbgio, int pid, void *data, int len, int iswrite, int tmout) { static char *wstr[] = {"rd", "wr" }; int i; uchar *b; int ntries; int once; int ppid; b = data; if(len > 8) panic("usbcons ucio bug. len > 0"); if(len > 0 && data == nil) panic("usbcons ucio bug. len but not data"); ntries = 1000; once = 1; Again: dbgio->csw = (dbgio->csw & ~Clen) | len; dbgio->pid = pid; if(once && debug > 1){ once = 0; dprint("usbcons: %s: csw %#ulx pid %#ux [%d] ", wstr[iswrite], dbgio->csw, pid, len); if(iswrite){ for(i = 0; i < len; i++) dprint("%02ux ", b[i]); } } memset(dbgio->data, 0, sizeof(dbgio->data)); if(iswrite){ if(len > 0) memmove(dbgio->data, data, len); dbgio->csw |= Cwrite; }else dbgio->csw &= ~Cwrite; dbgio->csw |= Cgo; for(i = 0; (dbgio->csw&Cdone) == 0; i++) if(tmout != 0 && i > 100000) return -1; dbgio->csw |= Cdone; /* acknowledge */ if((dbgio->csw & Cfailed) != 0){ dprint(" err csw %#ulx\n", dbgio->csw); return -1; } ppid = rpid(dbgio->pid); if((ppid == Nak || ppid == Nyet) && --ntries > 0){ microdelay(10); goto Again; } if(ntries == 0) dprint(" naks"); if(ppid != Ack && ppid != spid(dbgio->pid)){ dprint(" bad pid %#x\n", ppid); len = -1; if(ppid == Nak) len = -2; } if(iswrite == 0 && len > 0){ if((dbgio->csw&Clen) < len) len = dbgio->csw&Clen; if(len > 0) memmove(data, dbgio->data, len); } if(debug > 1){ dprint("-> [%d] ", len); if(iswrite == 0){ for(i = 0; i < len; i++) dprint("%02ux ", b[i]); } dprint(" csw %#ulx\n", dbgio->csw); } return len; } /* * BUG: This is not a generic usb cmd tool. * If you call this be sure it works for the type of call you are making. * This is just for what this driver uses. Compare this with the * general purpose ehci control transfer dance. */ static int uccmd(Edbgio *dbgio, int type, int req, int val, int idx, void *data, int cnt) { uchar buf[Rsetuplen]; int r; assert(cnt >= 0 && cnt <= nelem(dbgio->data)); dprint("usbcons: cmd t %#x r %#x v %#x i %#x c %d\n", type, req, val, idx, cnt); buf[Rtype] = type; buf[Rreq] = req; PUT2(buf+Rvalue, val); PUT2(buf+Rindex, idx); PUT2(buf+Rcount, cnt); if(ucio(dbgio, Data0|Toksetup, buf, Rsetuplen, Write, 100) < 0) return -1; if((type&Rd2h) == 0 && cnt > 0) panic("out debug command with data"); r = ucio(dbgio, Data1|Tokin, data, cnt, Read, 100); if((type&Rd2h) != 0) ucio(dbgio, Data1|Tokout, nil, 0, Write, 100); return r; } static ulong uctoggle(Edbgio *dbgio) { return (dbgio->pid ^ Ptoggle) & Ptogglemask; } static int ucread(Ctlr *ctlr, void *data, int cnt) { Edbgio *dbgio; int r; ilock(ctlr); dbgio = ctlr->dbgio; dbgio->addr = (Daddr << Adevshift) | (ctlr->epin << Aepshift); r = ucio(dbgio, uctoggle(dbgio)|Tokin, data, cnt, Read, 10); if(r < 0) uctoggle(dbgio); /* leave toggle as it was */ iunlock(ctlr); return r; } static int ucwrite(Ctlr *ctlr, void *data, int cnt) { Edbgio *dbgio; int r; ilock(ctlr); dbgio = ctlr->dbgio; dbgio->addr = (Daddr << Adevshift) | (ctlr->epout << Aepshift); r = ucio(dbgio, uctoggle(dbgio)|Tokout, data, cnt, Write, 10); if(r < 0) uctoggle(dbgio); /* leave toggle as it was */ iunlock(ctlr); return r; } /* * Set the device address to 127 and enable it. * The device might have the address hardwired to 127. * We try to set it up in any case but ignore most errors. */ static int ucconfigdev(Ctlr *ctlr) { uchar desc[8]; Edbgio *dbgio; int r; dbgio = ctlr->dbgio; dprint("usbcons: setting up device address to %d\n", Daddr); dbgio->addr = (0 << Adevshift) | (0 << Aepshift); if(uccmd(dbgio, Rh2d|Rstd|Rdev, Rsetaddr, Daddr, 0, nil, 0) < 0) print("usbcons: debug device: can't set address to %d\n", Daddr); else dprint("usbcons: device address set to %d\n", Daddr); dbgio->addr = (Daddr << Adevshift) | (0 << Aepshift); dprint("usbcons: reading debug descriptor\n"); r = uccmd(dbgio, Rd2h|Rstd|Rdev, Rgetdesc, Ddebug << 8, 0, desc, 4); if(r < Ddebuglen || desc[1] != Ddebug){ print("usbcons: debug device: can't get debug descriptor\n"); dbgio->csw &= ~(Cowner|Cbusy); return -1; } dprint("usbcons: setting up debug mode\n"); if(uccmd(dbgio, Rh2d|Rstd|Rdev, Rsetfeature, Debugmode, 0, nil, 0) < 0){ print("usbcons: debug device: can't set debug mode\n"); dbgio->csw &= ~(Cowner|Cbusy); return -1; } ctlr->epin = desc[2] & ~0x80; /* clear direction bit from ep. addr */; ctlr->epout = desc[3]; print("#u/usb/ep%d.0: ehci debug port: in ep%d.%d out: ep%d.%d\n", Daddr, Daddr, ctlr->epin, Daddr, ctlr->epout); return 0; } /* * Ctlr already ilocked. */ static void portreset(Ctlr *ctlr, int port) { Eopio *opio; ulong s; int i; opio = ctlr->opio; s = opio->portsc[port-1]; dprint("usbcons %#p port %d reset: sts %#ulx\n", ctlr->capio, port, s); s &= ~(Psenable|Psreset); opio->portsc[port-1] = s|Psreset; for(i = 0; i < 10; i++){ delay(10); if((opio->portsc[port-1] & Psreset) == 0) break; } opio->portsc[port-1] &= ~Psreset; delay(50); dprint("usbcons %#p port %d after reset: sts %#ulx\n", ctlr->capio, port, s); } /* * Ctlr already ilocked. */ static void portenable(Ctlr *ctlr, int port) { Eopio *opio; ulong s; opio = ctlr->opio; s = opio->portsc[port-1]; dprint("usbcons: port %d enable: sts %#ulx\n", port, s); if(s & (Psstatuschg | Pschange)) opio->portsc[port-1] = s; opio->portsc[port-1] |= Psenable; delay(100); dprint("usbcons: port %d after enable: sts %#ulx\n", port, s); } static int ucattachdev(Ctlr *ctlr) { Eopio *opio; Edbgio *dbgio; int i; ulong s; int port; ilock(ctlr); if(ehcidebugcapio != nil){ iunlock(ctlr); return -1; } /* reclaim port */ dbgio = ctlr->dbgio; dbgio->csw |= Cowner; dbgio->csw &= ~(Cenable|Cbusy); opio = ctlr->opio; opio->cmd &= ~(Chcreset|Ciasync|Cpse|Case); opio->config = Callmine; /* reclaim all ports */ ehcirun(ctlr, 1); delay(100); ctlr->port = (ctlr->capio->parms >> Cdbgportshift) & Cdbgportmask; port = ctlr->port; if(port < 1 || port > (ctlr->capio->parms & Cnports)){ print("usbcons: debug port out of range\n"); dbgio->csw &= ~(Cowner|Cbusy); iunlock(ctlr); return -1; } dprint("usbcons: debug port: %d\n", port); opio->portsc[port-1] = Pspower; for(i = 0; i < 200; i++){ s = opio->portsc[port-1]; if(s & (Psstatuschg | Pschange)){ opio->portsc[port-1] = s; dprint("usbcons: port sts %#ulx\n", s); } if(s & Pspresent){ portreset(ctlr, port); break; } delay(1); } if((opio->portsc[port-1] & Pspresent) == 0 || i == 200){ print("usbcons: no debug device (attached to another port?)\n"); dbgio->csw &= ~(Cowner|Cbusy); iunlock(ctlr); return -1; } ehcidebugcapio = ctlr->capio; /* this ehci must avoid reset */ ehcidebugport = port; /* and this port must not be available */ dbgio->csw |= Cowner|Cenable|Cbusy; opio->portsc[port-1] &= ~Psenable; delay(100); ehcirun(ctlr, 0); // portenable(ctlr, port); // dbgio->csw |= Cowner|Cenable|Cbusy; delay(100); iunlock(ctlr); return 0; } static int ucreset(Ctlr *ctlr) { Eopio *opio; int i; dprint("ucreset\n"); /* * Turn off legacy mode. */ ehcirun(ctlr, 0); pcicfgw16(ctlr->pcidev, 0xc0, 0x2000); /* clear high 32 bits of address signals if it's 64 bits. * This is probably not needed but it does not hurt. */ opio = ctlr->opio; if((ctlr->capio->capparms & C64) != 0){ dprint("ehci: 64 bits\n"); opio->seg = 0; } opio->cmd |= Chcreset; /* controller reset */ for(i = 0; i < 100; i++){ if((opio->cmd & Chcreset) == 0) break; delay(1); } if(i == 100){ print("usbcons: controller reset timed out\n"); return -1; } dprint("ucreset done\n"); return 0; } static int ucinit(Pcidev *p, uint ptr) { uintptr io; uint off; Ecapio *capio; io = p->mem[0].bar & ~0xF; if(io == 0){ print("usbcons: failed to map registers\n"); return -1; } off = pcicfgr16(p, ptr+2) & 0xFFF; capio = ctlr.capio = vmap(io, p->mem[0].size); ctlr.opio = (Eopio*)((uintptr)capio + (capio->cap & 0xFF)); ctlr.dbgio = (Edbgio*)((uintptr)capio + off); ctlr.pcidev = p; pcisetbme(p); pcisetpms(p, 0); if((ctlr.dbgio->csw & Cbusy) != 0){ print("usbcons: debug port already in use\n"); return -1; } print("usbcons: port %#p: ehci debug port\n", ctlr.dbgio); if(ucreset(&ctlr) < 0 || ucattachdev(&ctlr) < 0 || ucconfigdev(&ctlr) < 0) return -1; return 0; } /* * Polling interface. */ int usbgetc(void) { int nr; if(ehcidebugcapio == nil) return -1; if(ctlr.sc == ctlr.ec){ ctlr.sc = ctlr.ec = 0; nr = ucread(&ctlr, ctlr.buf, sizeof(ctlr.buf)); if(nr > 0) ctlr.ec += nr; } if(ctlr.sc < ctlr.ec) return ctlr.buf[ctlr.sc++]; return -1; } void usbputc(int c) { char buf[1]; if(ehcidebugcapio == nil) return; buf[0] = c; ucwrite(&ctlr, buf, 1); } /* * Put 8 chars at a time. * Ignore errors (this device seems to be flaky). * Some times (for some packets) the device keeps on * sending naks. This does not seem to depend on toggles or * pids or packet content. It's supposed we don't need to send * unstalls here. But it really looks like we do need then. * Time to use a sniffer to see what windows does * with the device?? */ void usbputs(char *s, int n) { int nw; if(ehcidebugcapio == nil) return; for(; n > 0; n -= nw){ nw = n; if(nw > 8) nw = 8; ucwrite(&ctlr, s, nw); s += nw; } } static Lock lck; static void usbclock(void) { char buf[80]; int n; lock(&lck); usbputs(".", 1); if((n = qconsume(ctlr.oq, buf, sizeof(buf))) > 0) usbputs(buf, n); unlock(&lck); // while((n = ucread(&ctlr, buf, sizeof(buf))) > 0) // qproduce(ctlr.iq, buf, n); } /* * Queue interface * The debug port does not seem to * issue interrupts for us. We must poll for completion and * also to check for input. * By now this might suffice but it's probably wrong. */ static void usbconsreset(void) { if(ehcidebugcapio == nil) return; return; ctlr.oq = qopen(8*1024, 0, nil, nil); if(ctlr.oq == nil) return; debug = 0; addconsdev(ctlr.oq, usbputs, ctlr.consid, 0); addclock0link(usbclock, 10); ctlr.iq = qopen(8*1024, 0, nil, nil); if(ctlr.iq == nil) return; addkbdq(ctlr.iq, -1); } /* * Scan the bus and enable the first debug port found. * Perhaps we should search all for a ctlr specified by port * e.g., console=usb port=xxxx * or by some other means. */ void usbconsole(void) { static int already = 0; uint ptr; Pcidev *p; char *s; if(already++ != 0) return; if((s = getconf("console")) == nil || strncmp(s, "usb", 3) != 0) if(debug == 0) return; dprint("usb console..."); p = nil; while ((p = pcimatch(p, 0, 0)) != nil) { /* * Find EHCI controllers (Programming Interface = 0x20). */ if(p->ccrb != Pcibcserial || p->ccru != Pciscusb || p->ccrp != 0x20) continue; if((pcicfgr16(p, PciPSR) & 0x0010) == 0) continue; /* * We have extended caps. search list for ehci debug port. */ ptr = pcicfgr32(p, 0x34); while(ptr >= 0x40 && (ptr & ~0xFC) == 0){ if(pcicfgr8(p, ptr) == Pdebugport){ dprint("found\n"); if(ucinit(p, ptr) >= 0){ ctlr.consid = addconsdev(nil, usbputs, -1, 0); print("1"); print("2"); print("3"); print("4"); print("5"); print("6"); print("7"); print("Plan 9 usb console\n"); } return; } ptr = pcicfgr8(p, ptr+1); } } dprint("not found\n"); } /* * This driver does not really serve any file. However, we want to * setup the console at reset time. */ static Chan* usbconsattach(char*) { error(Eperm); return nil; } Dev usbconsdevtab = { L'↔', "usbcons", usbconsreset, devinit, devshutdown, usbconsattach, /* all others set to nil. Attachments are not allowed */ };