ref: 7134d62ccbb32c783de67d26907e4e856c48f29c
dir: /sys/src/9/pc/sdodin.c/
/* * marvell odin ii 88se64xx sata/sas controller * copyright © 2009 erik quanstrom * coraid, inc. */ #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/sd.h" #include <fis.h> #include "../port/led.h" #define dprint(...) if(debug) print(__VA_ARGS__); else USED(debug) #define idprint(...) if(idebug) print(__VA_ARGS__); else USED(idebug) #define aprint(...) if(adebug) print(__VA_ARGS__); else USED(adebug) #define Pciwaddrh(a) 0 #define Pciw64(x) (uvlong)PCIWADDR(x) #define Ticks MACHP(0)->ticks /* copied from sdiahci */ enum { Dnull = 0, Dmissing = 1<<0, Dnopower = 1<<1, Dnew = 1<<2, Dready = 1<<3, Derror = 1<<4, Dreset = 1<<5, Doffline = 1<<6, Dportreset = 1<<7, Dlast = 9, }; static char *diskstates[Dlast] = { "null", "missing", "nopower", "new", "ready", "error", "reset", "offline", "portreset", }; static char *type[] = { "offline", "sas", "sata", }; enum{ Nctlr = 4, Nctlrdrv = 8, Ndrive = Nctlr*Nctlrdrv, Mbar = 2, Mebar = 4, Nqueue = 32, /* cmd queue size */ Qmask = Nqueue - 1, Nregset = 8, Rmask = 0xffff, Nms = 256, /* drive check rate */ Sas = 1, Sata, /* cmd bits */ Error = 1<<31, Done = 1<<30, Noverdict = 1<<29, Creset = 1<<28, Atareset = 1<<27, Sense = 1<<26, Timeout = 1<<25, Response = 1<<24, Active = 1<<23, /* pci registers */ Phy0 = 0x40, Gpio = 0x44, Phy1 = 0x90, Gpio1 = 0x94, Dctl = 0xe8, /* phy offests */ Phydisable = 1<<12, Phyrst = 1<<16, Phypdwn = 1<<20, Phyen = 1<<24, /* bar4 registers */ Gctl = 0x004/4, Gis = 0x008/4, /* global interrupt status */ Pi = 0x00c/4, /* ports implemented */ Flashctl = 0x030/4, /* spi flash control */ Flashcmd = 0x034/4, /* flash wormhole */ Flashdata = 0x038/4, I²cctl = 0x040/4, /* i²c control */ I²ccmd = 0x044/4, I²cdata = 0x048/4, Ptype = 0x0a0/4, /* 15:8 auto detect enable; 7:0 sas=1. sata=0 */ Portcfg0 = 0x100/4, /* 31:16 register sets 31:16 */ Portcfg1 = 0x104/4, /* 31:16 register sets 15:8 tx enable; 7 rx enable */ Clbase = 0x108/4, /* cmd list base; 64 bits */ Fisbase = 0x110/4, /* 64 bits */ Dqcfg = 0x120/4, /* bits 11:0 specify size */ Dqbase = 0x124/4, Dqwp = 0x12c/4, /* delivery queue write pointer */ Dqrp = 0x130/4, Cqcfg = 0x134/4, Cqbase = 0x138/4, Cqwp = 0x140/4, /* hw */ Coal = 0x148/4, Coalto = 0x14c/4, /* coal timeout µs */ Cis = 0x150/4, /* centeral irq status */ Cie = 0x154/4, /* centeral irq enable */ Csis = 0x158/4, /* cmd set irq status */ Csie = 0x15c/4, Cmda = 0x1b8/4, Cmdd = 0x1bc/4, Gpioa = 0x270/4, Gpiod = 0x274/4, Gpiooff = 0x100, /* second gpio offset */ /* port conf registers; mapped through wormhole */ Pinfo = 0x000, Paddr = 0x004, Painfo = 0x00c, /* attached device info */ Pawwn = 0x010, Psatactl = 0x018, Pphysts = 0x01c, Psig = 0x020, /* 16 bytes */ Perr = 0x030, Pcrcerr = 0x034, Pwidecfg = 0x038, Pwwn = 0x080, /* 12 wwn + ict */ /* port cmd registers; mapped through “cmd” wormhole */ Ci = 0x040, /* cmd active (16) */ Task = 0x080, Rassoc = 0x0c0, Pfifo0 = 0x1a8, Pfifo1 = 0x1c4, Pwdtimer = 0x13c, /* “vendor specific” wormhole */ Phymode = 0x001, /* gpio wormhole */ Sgconf0 = 0x000, Sgconf1 = 0x004, Sgclk = 0x008, Sgconf3 = 0x00c, Sgis = 0x010, /* interrupt set */ Sgie = 0x014, /* interrupt enable */ Drivesrc = 0x020, /* 4 drives/register; 4 bits/drive */ Drivectl = 0x038, /* same deal */ /* Gctl bits */ Reset = 1<<0, Intenable = 1<<1, /* Portcfg0/1 bits */ Regen = 1<<16, /* enable sata regsets 31:16 or 15:0 */ Xmten = 1<<8, /* enable port n transmission */ Dataunke = 1<<3, Rsple = 1<<2, /* response frames in le format */ Oabe = 1<<1, /* oa frame in big endian format */ Framele = 1<<0, /* frame contents in le format */ Allresrx = 1<<7, /* receive all responses */ Stpretry = 1<<6, Cmdirq = 1<<5, /* 1 == self clearing */ Fisen = 1<<4, /* enable fis rx */ Errstop = 1<<3, /* set -> stop on ssp/smp error */ Resetiss = 1<<1, /* reset cmd issue; self clearing */ Issueen = 1<<0, /* Dqcfg bits */ Dqen = 1<<16, /* Cqcfg bits */ Noattn = 1<<17, /* don't post entries with attn bit */ Cqen = 1<<16, /* Cis bits */ I2cirq = 1<<31, Swirq1 = 1<<30, Swirq0 = 1<<29, Prderr = 1<<28, Dmato = 1<<27, Parity = 1<<28, /* parity error; fatal */ Slavei2c = 1<<25, Portstop = 1<<16, /* bitmapped */ Portirq = 1<<8, /* bitmapped */ Srsirq = 1<<3, Issstop = 1<<1, Cdone = 1<<0, Iclr = Swirq1 | Swirq0, /* Pis bits */ Caf = 1<<29, /* clear affiliation fail */ Sync = 1<<25, /* sync during fis rx */ Phyerr = 1<<24, Stperr = 1<<23, Crcerr = 1<<22, Linktx = 1<<21, Linkrx = 1<<20, Martianfis = 1<<19, Anot = 1<<18, /* async notification */ Bist = 1<<17, Sigrx = 1<<16, /* native sata signature rx */ Phyunrdy = 1<<12, /* phy went offline*/ Uilong = 1<<11, Uishort = 1<<10, Martiantag = 1<<9, Bnot = 1<<8, /* broadcast noticication */ Comw = 1<<7, Portsel = 1<<6, Hreset = 1<<5, Phyidto = 1<<4, Phyidfail = 1<<3, Phyidok = 1<<2, Hresetok = 1<<1, Phyrdy = 1<<0, Pisataup = Phyrdy | Comw | Sigrx, Pisasup = Phyrdy | Comw | Phyidok, Piburp = Sync | Phyerr | Stperr | Crcerr | Linktx | Linkrx | Martiantag, Pireset = Phyidfail | Bnot | Phyunrdy | Bist | Anot | Martianfis | Bist | Phyidto | Hreset, Piunsupp = Portsel, /* Psc bits */ Sphyrdy = 1<<20, Linkrate = 1<<18, /* 4 bits */ Maxrate = 1<<12, Minrate = 1<<8, Sreset = 1<<3, Sbnote = 1<<2, Shreset = 1<<1, Sphyrst = 1<<0, /* Painfo bits */ Issp = 1<<19, Ismp = 1<<18, Istp = 1<<17, Itype = 1<<0, /* two bits */ /* Psatactl bits */ Powerctl = 1<<30, /* 00 wake; 10 partial 01 slumb */ Srst = 1<<29, /* soft reset */ Power = 1<<28, Sportsel = 1<<24, Dmahon = 1<<22, Srsten = 1<<20, Dmaxfr = 1<<18, /* phy status bits */ Phylock = 1<<9, Nspeed = 1<<4, Psphyrdy = 1<<2, /* Task bits; modeled after ahci */ Eestatus = 0xff<<24, Asdbs = 1<<18, Apio = 1<<17, Adhrs = 1<<16, Eerror = 0xff<<8, Estatus = 0xff, /* Phymode bits */ Pmnotify = 1<<24, Pmnotifyen = 1<<23, /* Sgconf0 bits */ Autolen = 1<<24, /* 8 bits */ Manlen = 1<<16, /* 8 bits */ Sdincapt = 1<<8, /* capture sdatain on + edge */ Sdoutch = 1<<7, /* change sdataout on - edge Sldch = 1<<6, /* change sload on - edge Sdoutivt = 1<<5, /* invert sdataout polarity */ Ldivt = 1<<4, Sclkivt = 1<<3, Blinkben = 1<<2, /* enable blink b */ Blinkaen = 1<<1, /* enable blink a */ Sgpioen = 1<<0, /* Sgconf1 bits; 4 bits each */ Sactoff = 1<<28, /* stretch activity off; 0/64 - 15/64 */ Sacton = 1<<24, /* 1/64th - 16/64 */ Factoff = 1<<20, /* 0/8 - 15/8; default 1 */ Facton = 1<<16, /* 0/4 - 15/4; default 2 */ Bhi = 1<<12, /* 1/8 - 16/8 */ Blo = 1<<8, /* 1/8 - 16/8 */ Ahi = 1<<4, /* 1/8 - 16/8 */ Alo = 1<<0, /* 1/8 - 16/8 */ /* Sgconf3 bits */ Autopat = 1<<20, /* 4 bits of start pattern */ Manpat = 1<<16, Manrep = 1<<4, /* repeats; 7ff ≡ ∞ */ Sdouthalt = 0<<2, Sdoutman = 1<<2, Sdoutauto = 2<<2, Sdoutma = 3<<2, Sdincapoff = 0<<0, Sdinoneshot = 1<<0, Sdinrep = 2<<0, /* Sgie Sgis bits */ Sgreprem = 1<<8, /* 12 bits; not irq related */ Manrep0 = 1<<1, /* write 1 to clear */ Capdone = 1<<0, /* capture done */ /* drive control bits (repeated 4x per drive) */ Aled = 1<<5, /* 3 bits */ Locled = 1<<3, /* 2 bits */ Errled = 1<<0, /* 3 bits */ Llow = 0, Lhigh = 1, Lblinka = 2, Lblinkaneg = 3, Lsof = 4, Leof = 5, Lblinkb = 6, Lblinkbneg = 7, /* cmd queue bits */ Dssp = 1<<29, Dsmp = 2<<29, Dsata = 3<<29, /* also stp */ Ditor = 1<<28, /* initiator */ Dsatareg = 1<<20, Dphyno = 1<<12, Dcslot = 1, /* completion queue bits */ Cgood = 1<<23, /* ssp only */ Cresetdn = 1<<21, Crx = 1<<20, /* target mode */ Cattn = 1<<19, Crxfr = 1<<18, Cerr = 1<<17, Cqdone = 1<<16, Cslot = 1<<0, /* 12 bits */ /* error bits — first word */ Eissuestp = 1<<31, /* cmd issue stopped */ Epi = 1<<30, /* protection info error */ Eoflow = 1<<29, /* buffer overflow */ Eretry = 1<<28, /* retry limit exceeded */ Eufis = 1<<27, Edmat = 1<<26, /* dma terminate */ Esync = 1<<25, /* sync rx during tx */ Etask = 1<<24, Ererr = 1<<23, /* r error received */ Eroff = 1<<20, /* read data offset error */ Exoff = 1<<19, /* xfer rdy offset error */ Euxr = 1<<18, /* unexpected xfer rdy */ Exflow = 1<<16, /* buffer over/underflow */ Elock = 1<<15, /* interlock error */ Enak = 1<<14, Enakto = 1<<13, Enoak = 1<<12, /* conn closed wo nak */ Eopento = 1<<11, /* open conn timeout */ Epath = 1<<10, /* open reject - path blocked */ Enodst = 1<<9, /* open reject - no dest */ Estpbsy = 1<<8, /* stp resource busy */ Ebreak = 1<<7, /* break while sending */ Ebaddst = 1<<6, /* open reject - bad dest */ Ebadprot = 1<<5, /* open reject - proto not supp */ Erate = 1<<4, /* open reject - rate not supp */ Ewdest = 1<<3, /* open reject - wrong dest */ Ecreditto = 1<<2, /* credit timeout */ Edog = 1<<1, /* watchdog timeout */ Eparity = 1<<0, /* buffer parity error */ /* sas ctl cmd header bits */ Ssptype = 1<<5, /* 3 bits */ Ssppt = 1<<4, /* build your own header *. Firstburst = 1<<3, /* first burst */ Vrfylen = 1<<2, /* verify length */ Tlretry = 1<<1, /* transport layer retry */ Piren = 1<<0, /* pir present */ /* sata ctl cmd header bits */ Lreset = 1<<7, Lfpdma = 1<<6, /* first-party dma. (what's that?) */ Latapi = 1<<5, Lpm = 1<<0, /* 4 bits */ Sspcmd = 0*Ssptype, Ssptask = 1*Ssptype, Sspxfrdy = 4*Ssptype, Ssprsp = 5*Ssptype, Sspread = 6*Ssptype, Sspwrite = 7*Ssptype, }; /* following ahci */ typedef struct { ulong dba; ulong dbahi; ulong pad; ulong count; } Aprdt; typedef struct { union{ struct{ uchar cfis[0x40]; uchar atapi[0x20]; }; struct{ uchar mfis[0x40]; }; struct{ uchar sspfh[0x18]; uchar sasiu[0x40]; }; }; } Ctab; /* protection information record */ typedef struct { uchar ctl; uchar pad; uchar size[2]; uchar rtag[4]; uchar atag[2]; uchar mask[2]; } Pir; /* open address frame */ typedef struct { uchar oaf[0x28]; uchar fb[4]; } Oaf; /* status buffer */ typedef struct { uchar error[8]; uchar rsp[0x400]; } Statb; typedef struct { uchar satactl; uchar sasctl; uchar len[2]; uchar fislen[2]; uchar maxrsp; uchar d0; uchar tag[2]; uchar ttag[2]; uchar dlen[4]; uchar ctab[8]; uchar oaf[8]; uchar statb[8]; uchar prd[8]; uchar d3[16]; } Cmdh; typedef struct Cmd Cmd; struct Cmd { Rendez; uint cflag; Cmdh *cmdh; Ctab; Oaf; Statb; Aprdt; }; typedef struct Drive Drive; typedef struct Ctlr Ctlr; struct Drive { Lock; QLock; Ctlr *ctlr; SDunit *unit; char name[16]; Cmd *cmd; /* sdscsi doesn't differentiate drivechange/mediachange */ uchar drivechange; uchar state; uchar type; ushort info[0x100]; Sfis; /* sata and media info*/ Cfis; /* sas and media info */ Ledport; /* led */ /* hotplug info */ uint lastseen; uint intick; uint wait; char serial[20+1]; char firmware[8+1]; char model[40+1]; uvlong wwn; uvlong sectors; uint secsize; uint driveno; }; struct Ctlr { Lock; uchar enabled; SDev *sdev; Pcidev *pci; uint *reg; uint dq[Nqueue]; uint dqwp; uint cq[Nqueue + 1]; uint cqrp; Cmdh *cl; uchar *fis; Cmd *cmdtab; Drive drive[Nctlrdrv]; uint ndrive; }; static Ctlr msctlr[Nctlr]; static SDev sdevs[Nctlr]; static uint nmsctlr; static Drive *msdrive[Ndrive]; static uint nmsdrive; static int debug=0; static int idebug=1; static int adebug; static uint olds[Nctlr*Nctlrdrv]; SDifc sdodinifc; /* a good register is hard to find */ static int pis[] = { 0x160/4, 0x168/4, 0x170/4, 0x178/4, 0x200/4, 0x208/4, 0x210/4, 0x218/4, }; static int pcfg[] = { 0x1c0/4, 0x1c8/4, 0x1d0/4, 0x1d8/4, 0x230/4, 0x238/4, 0x240/4, 0x248/4, }; static int psc[] = { 0x180/4, 0x184/4, 0x188/4, 0x18c/4, 0x220/4, 0x224/4, 0x228/4, 0x22c/4, }; static int vscfg[] = { 0x1e0/4, 0x1e8/4, 0x1f0/4, 0x1f8/4, 0x250/4, 0x258/4, 0x260/4, 0x268/4, }; #define sstatus(d) (d)->ctlr->reg[psc[(d)->driveno]] static char* dstate(uint s) { int i; for(i = 0; s; i++) s >>= 1; return diskstates[i]; } static char* dnam(Drive *d) { if(d->unit) return d->unit->name; return d->name; } static uvlong border = 0x0001020304050607ull; static uvlong lorder = 0x0706050403020100ull; static uvlong getle(uchar *t, int w) { uint i; uvlong r; r = 0; for(i = w; i != 0; ) r = r<<8 | t[--i]; return r; } static void putle(uchar *t, uvlong r, int w) { uchar *o, *f; uint i; f = (uchar*)&r; o = (uchar*)&lorder; for(i = 0; i < w; i++) t[o[i]] = f[i]; } static uvlong getbe(uchar *t, int w) { uint i; uvlong r; r = 0; for(i = 0; i < w; i++) r = r<<8 | t[i]; return r; } static void putbe(uchar *t, uvlong r, int w) { uchar *o, *f; uint i; f = (uchar*)&r; o = (uchar*)&border + (sizeof border-w); for(i = 0; i < w; i++) t[i] = f[o[i]]; } static int phyrtab[] = {Phy0, Phy1}; static void phyenable(Ctlr *c, Drive *d) { uint i, u, reg, m; i = d->driveno; reg = phyrtab[i > 3]; i &= 3; i = 1<<i; u = pcicfgr32(c->pci, reg); m = i*(Phypdwn | Phydisable | Phyen); if((u & m) == Phyen) return; m = i*(Phypdwn | Phydisable); u &= ~m; u |= i*Phyen; pcicfgw32(c->pci, reg, u); } static void regtxreset(Drive *d) { uint i, u, m; Ctlr *c = d->ctlr; i = d->driveno; u = c->reg[Portcfg1]; m = (Regen|Xmten)<<i; u &= ~m; c->reg[Portcfg1] = u; delay(1); c->reg[Portcfg1] = u | m; } /* aka comreset? */ static void phyreset(Drive *d) { uint i, u, reg; Ctlr *c; c = d->ctlr; phyenable(c, d); i = d->driveno; reg = phyrtab[i > 3]; i &= 3; i = 1<<i; u = pcicfgr32(c->pci, reg); pcicfgw32(c->pci, reg, u | i*Phyrst); delay(5); pcicfgw32(c->pci, reg, u); sstatus(d) |= Shreset; while(sstatus(d) & Shreset); ; } static void reset(Drive *d) { regtxreset(d); phyreset(d); } /* * sata/sas register reads through wormhole */ static uint ssread(Ctlr *c, int port, uint r) { c->reg[Cmda] = r + 4*port; return c->reg[Cmdd]; } static void sswrite(Ctlr *c, int port, int r, uint u) { c->reg[Cmda] = r + 4*port; c->reg[Cmdd] = u; } /* * port configuration r/w through wormhole */ static uint pcread(Ctlr *c, uint port, uint r) { c->reg[pcfg[port]] = r; return c->reg[pcfg[port] + 1]; } static void pcwrite(Ctlr *c, uint port, uint r, uint u) { c->reg[pcfg[port] + 0] = r; c->reg[pcfg[port] + 1] = u; } /* * vendor specific r/w through wormhole */ static uint vsread(Ctlr *c, uint port, uint r) { c->reg[vscfg[port]] = r; return c->reg[vscfg[port] + 1]; } static void vswrite(Ctlr *c, uint port, uint r, uint u) { c->reg[vscfg[port] + 0] = r; c->reg[vscfg[port] + 1] = u; } /* * gpio wormhole */ static uint gpread(Ctlr *c, uint r) { c->reg[Gpioa] = r; return c->reg[Gpiod]; } static void gpwrite(Ctlr *c, uint r, uint u) { c->reg[Gpioa] = r; c->reg[Gpiod] = u; } static uint* getsigfis(Drive *d, uint *fis) { uint i; for(i = 0; i < 4; i++) fis[i] = pcread(d->ctlr, d->driveno, Psig + 4*i); return fis; } static uint getsig(Drive *d) { uint fis[4]; return fistosig((uchar*)getsigfis(d, fis)); } static uint ci(Drive *d) { return ssread(d->ctlr, d->driveno, Ci); } static void unsetci(Drive *d) { uint i; i = 1<<d->driveno; sswrite(d->ctlr, d->driveno, Ci, i); while(ci(d) & i) microdelay(1); } static uint gettask(Drive *d) { return ssread(d->ctlr, d->driveno, Task); } static void tprint(Drive *d, uint t) { uint s; s = sstatus(d); dprint("%s: err task %ux sstat %ux\n", dnam(d), t, s); } static int cmdactive(void *v) { Cmd *x; x = v; return (x->cflag & Done) != 0; } static int mswait(Cmd *x, int ms) { uint u, tk0; if(up){ tk0 = Ticks; while(waserror()) ; tsleep(x, cmdactive, x, ms); poperror(); ms -= TK2MS(Ticks - tk0); }else while(ms-- && cmdactive(x)) delay(1); // ilock(cmd->d); u = x->cflag; x->cflag = 0; // iunlock(cmd->d) if(u == (Done | Active)) return 0; if((u & Done) == 0){ u |= Noverdict | Creset | Timeout; print("cmd timeout ms:%d %ux\n", ms, u); } return u; } static void setstate(Drive *d, int state) { ilock(d); d->state = state; iunlock(d); } static void esleep(int ms) { if(waserror()) return; tsleep(&up->sleep, return0, 0, ms); poperror(); } static int waitready(Drive *d) { ulong s, i, δ; for(i = 0; i < 15000; i += 250){ if(d->state & (Dreset | Dportreset | Dnew)) return 1; δ = Ticks - d->lastseen; if(d->state == Dnull || δ > 10*1000) return -1; ilock(d); s = sstatus(d); iunlock(d); if((s & Sphyrdy) == 0 && δ > 1500) return -1; if(d->state == Dready && (s & Sphyrdy)) return 0; esleep(250); } print("%s: not responding; offline: %.8ux\n", dnam(d), sstatus(d)); setstate(d, Doffline); return -1; } static int lockready(Drive *d) { int i, r; for(i = 0; ; i++){ qlock(d); if((r = waitready(d)) != 1) return r; qunlock(d); if(i == Nms*10) break; esleep(1); } return -1; } static int command(Drive *d, uint cmd, int ms) { uint s, n, m, i; Ctlr *c; c = d->ctlr; i = d->driveno; m = 1<<i; n = cmd | Ditor | i*Dsatareg | m*Dphyno | i*Dcslot; // print("cqwp\t%.8ux : n %ux : d%d; \n", c->cq[0], n, i); d->cmd->cflag = Active; ilock(c); s = c->dqwp++; c->dq[s&Qmask] = n; c->reg[Dqwp] = s&Qmask; iunlock(c); // print(" dq slot %d\n", s); d->intick = Ticks; /* move to mswait? */ return mswait(d->cmd, ms); } static int buildfis(Drive *d, SDreq *r, void *data, int n) { Aprdt *p; Cmd *x; Cmdh *h; x = d->cmd; memmove(x->cfis, r->cmd, r->clen); h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; h->len[0] = 0; if(data != nil){ h->len[0] = 1; p = x; p->dba = PCIWADDR(data); p->dbahi = Pciwaddrh(data); p->count = n; } return command(d, Dsata, 10*1000); } static int build(Drive *d, int rw, void *data, int n, vlong lba) { Aprdt *p; Cmd *x; Cmdh *h; x = d->cmd; rwfis(d, x->cfis, rw, n, lba); h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; h->len[0] = 1; /* one prdt entry */ p = x; p->dba = PCIWADDR(data); p->dbahi = Pciwaddrh(data); p->count = d->secsize*n; return command(d, Dsata, 10*1000); } enum{ Rnone = 1, Rdma = 0x00, /* dma setup; length 0x1b */ Rpio = 0x20, /* pio setup; length 0x13 */ Rd2h = 0x40, /* d2h register;length 0x13 */ Rsdb = 0x58, /* set device bits; length 0x08 */ }; static uint fisotab[8] = { [0] Rnone, [1] Rd2h, [2] Rpio, [3] Rnone, [4] Rsdb, [5] Rnone, [6] Rnone, [7] Rnone, }; static uint fisoffset(Drive *d, int mustbe) { uint t, r; t = gettask(d) & 0x70000; r = fisotab[t >> 16]; if(r == Rnone || (mustbe != 0 && r != mustbe)) return 0; return 0x800 + 0x100*d->driveno + r; } /* need to find a non-atapi-specific way of doing this */ static uint atapixfer(Drive *d, uint n) { uchar *u; uint i, x; if((i = fisoffset(d, Rd2h)) == 0) return 0; u = d->ctlr->fis + i; x = u[Flba16]<<8 | u[Flba8]; if(x > n){ x = n; print("%s: atapixfer %ux %ux\n", dnam(d), x, n); } return x; } static int buildpkt(Drive *d, SDreq *r, void *data, int n) { int rv; Aprdt *p; Cmd *x; Cmdh *h; x = d->cmd; atapirwfis(d, x->cfis, r->cmd, r->clen, n); h = x->cmdh; memset(h, 0, 16); h->satactl = Latapi; h->fislen[0] = 5; h->len[0] = 1; /* one prdt entry */ if(data != nil){ p = x; p->dba = PCIWADDR(data); p->dbahi = Pciwaddrh(data); p->count = n; } rv = command(d, Dsata, 10*1000); if(rv == 0) r->rlen = atapixfer(d, n); return rv; } /* * ata 7, required for sata, requires that all devices "support" * udma mode 5, however sata:pata bridges allow older devices * which may not. the innodisk satadom, for example allows * only udma mode 2. on the assumption that actual udma is * taking place on these bridges, we set the highest udma mode * available, or pio if there is no udma mode available. */ static int settxmode(Drive *d, uchar f) { Cmd *x; Cmdh *h; x = d->cmd; if(txmodefis(d, x->cfis, f) == -1) return 0; h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; return command(d, Dsata, 3*1000); } static int setfeatures(Drive *d, uchar f, uint w) { Cmd *x; Cmdh *h; x = d->cmd; featfis(d, x->cfis, f); h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; return command(d, Dsata, w); } static int mvflushcache(Drive *d) { Cmd *x; Cmdh *h; x = d->cmd; flushcachefis(d, x->cfis); h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; return command(d, Dsata, 60*1000); } static int identify0(Drive *d, void *id) { Aprdt *p; Cmd *x; Cmdh *h; x = d->cmd; identifyfis(d, x->cfis); h = x->cmdh; memset(h, 0, 16); h->fislen[0] = 5; h->len[0] = 1; /* one prdt entry */ memset(id, 0, 0x200); p = x; p->dba = PCIWADDR(id); p->dbahi = Pciwaddrh(id); p->count = 0x200; return command(d, Dsata, 3*1000); } static int identify(Drive *d) { int i, n; vlong osectors, s; uchar oserial[21]; ushort *id; SDunit *u; id = d->info; for(i = 0;; i++){ if(i > 5 || identify0(d, id) != 0) return -1; n = idpuis(id); if(n & Pspinup && setfeatures(d, 7, 20*1000) == -1) dprint("%s: puis spinup fail\n", dnam(d)); if(n & Pidready) break; } s = idfeat(d, id); if(s == -1) return -1; if((d->feat&Dlba) == 0){ dprint("%s: no lba support\n", dnam(d)); return -1; } osectors = d->sectors; memmove(oserial, d->serial, sizeof d->serial); d->sectors = s; d->secsize = idss(d, id); idmove(d->serial, id+10, 20); idmove(d->firmware, id+23, 8); idmove(d->model, id+27, 40); d->wwn = idwwn(d, id); u = d->unit; memset(u->inquiry, 0, sizeof u->inquiry); u->inquiry[2] = 2; u->inquiry[3] = 2; u->inquiry[4] = sizeof u->inquiry - 4; memmove(u->inquiry+8, d->model, 40); if(osectors != s || memcmp(oserial, d->serial, sizeof oserial)){ d->drivechange = 1; u->sectors = 0; } return 0; } /* open address fises */ enum{ Initiator = 0x80, Openaddr = 1, Awms = 0x8000, Smp = 0, Ssp = 1, Stp = 2, Spd15 = 8, Spd30 = 9, }; static void oafis(Cfis *f, uchar *c, int type) { c[0] = Initiator | type<<4 | Openaddr; c[1] = Spd30; /* botch; just try 3gbps */ if(type == Smp) memset(c + 2, 0xff, 2); else memmove(c + 2, f->ict, 2); memmove(c + 4, f->tsasaddr, 8); /* dest "port identifier" §4.2.6 */ memmove(c + 12, f->ssasaddr, 8); } /* sas fises */ static int sasfis(Cfis*, uchar *c, SDreq *r) { memmove(c, r->cmd, r->clen); if(r->clen < 16) memset(c + r->clen, 0, 16 - r->clen); return 0; } /* sam3 §4.9.4 single-level lun structure */ static void scsilun8(uchar *c, int l) { memset(c, 0, 8); if(l < 255) c[1] = l; else if(l < 16384){ c[0] = 1<<6 | l>>8; c[1] = l; }else print("bad lun %d\n", l); } static void iuhdr(SDreq *r, uchar *c, int fburst) { scsilun8(c, r->lun); c[8] = 0; c[9] = 0; if(fburst) c[9] = 0x80; } static void ssphdr(Cfis *x, uchar *c, int ftype) { memset(c, 0, 0x18); c[0] = ftype; sasbhash(c + 1, x->tsasaddr); sasbhash(c + 5, x->ssasaddr); } /* debugging */ static void dump(uchar *u, uint n) { uint i; if(n > 100) n = 100; for(i = 0; i < n; i += 4){ print("%.2d %.2ux%.2ux%.2ux%.2ux", i, u[i], u[i + 1], u[i + 2], u[i + 3]); print("\n"); } } static void prsense(uchar *u, uint n) { print("sense data %d: \n", n); dump(u, n); } static void priu(uchar *u, uint n) { print("iu %d: \n", n); dump(u, n); } /* * other suspects: * key asc/q * 02 0401 becoming ready * 040b target port in standby state * 0b01 overtemp * 0b0[345] background * * 0c01 write error - recovered with auto reallocation * 0c02 write error - auto reallocation failed * 0c03 write error - recommend reassignment * 17* recovered data * 18* recovered data * 5d* smart-style reporting (disk/smart handles) * 5e* power state change */ static int classifykey(int asckey) { if(asckey == 0x062901 || asckey == 0x062900){ /* power on */ dprint("power on sense\n"); return SDretry; } return SDcheck; } /* spc3 §4.5 */ static int sasrspck(Drive *d, SDreq *r, int min) { char *p; int rv; uchar *u, *s; uint l, fmt, n, keyasc; u = d->cmd->rsp; s = u + 24; dprint("status %d datapres %d\n", u[11], u[10]); switch(u[10]){ case 1: l = getbe(u + 20, 4); /* * this is always a bug because we don't do * task mgmt */ print("%s: bug: task data %d min %d\n", dnam(d), l, min); return SDcheck; case 2: l = getbe(u + 16, 4); n = sizeof r->sense; if(l < n) n = l; memmove(r->sense, s, n); fmt = s[0] & 0x7f; keyasc = (s[2] & 0xf)<<16 | s[12]<<8 | s[13]; rv = SDcheck; /* spc3 §4.5.3; 0x71 is deferred. */ if(n >= 18 && (fmt == 0x70 || fmt == 0x71)){ rv = classifykey(keyasc); p = ""; if(rv == SDcheck){ r->flags |= SDvalidsense; p = "valid"; } dprint("sense %.6ux %s\n", keyasc, p); }else prsense(s, l); return rv; default: print("%s: sasrspck: spurious\n", dnam(d)); priu(u, 24); prsense(s, 0x30); return SDcheck; } } static int buildsas(Drive *d, SDreq *r, uchar *data, int n) { int w, try, fburst; Aprdt *p; Cmd *x; Cmdh *h; try = 0; top: fburst = 0; /* Firstburst? */ x = d->cmd; /* ssphdr(d, x->sspfh, 6); */ iuhdr(r, x->sasiu, fburst); w = 0; if(r->clen > 16) w = r->clen - 16 + 3>> 2; x->sasiu[11] = w; sasfis(d, x->sasiu + 12, r); h = x->cmdh; memset(h, 0, 16); h->sasctl = Tlretry | /*Vrfylen |*/ Sspcmd | fburst; h->fislen[0] = sizeof x->sspfh + 12 + 16 + 4*w >> 2; h->maxrsp = 0xff; if(n) h->len[0] = 1; h->ttag[0] = 1; *(uint*)h->dlen = n; if(n){ p = x; p->dba = PCIWADDR(data); p->dbahi = Pciwaddrh(data); p->count = n; } switch(w = command(d, Dssp, 10*1000)){ case 0: r->status = sdsetsense(r, SDok, 0, 0, 0); return 0; case Response | Done | Active: r->status = sasrspck(d, r, 0); if(r->status == SDok) return 0; if(r->status == SDretry){ if(try++ < 2) goto top; r->status |= SDvalidsense; } return w | Sense; default: r->status = sdsetsense(r, SDcheck, 4, 24, 0); return w; } } static uint analyze(Drive *d, Statb *b) { uint u, r, t; r = 0; u = *(uint*)b->error; if(u & Eissuestp){ r |= Error; unsetci(d); } if(u & Etask && (d->feat & Datapi) == 0){ t = gettask(d); if(t & 1) tprint(d, t); if(t & Efatal<<8 || t & (ASbsy|ASdrq)) r |= Noverdict|Atareset; if(t&Adhrs && t&33) r |= Noverdict|Atareset; else r |= Error; } if(u & (Ererr | Ebadprot)){ /* sas thing */ print("%s: sas error %.8ux\n", dnam(d), u); r |= Error; } if(u & ~(Ebadprot | Ererr | Etask | Eissuestp)) print("%s: analyze %.8ux\n", dnam(d), u); return r; } static void updatedone(Ctlr *c) { uint a, e, i, u, slot; Cmd *x; Drive *d; e = c->cq[0]; if(e == 0xfff) return; if(e > Qmask) print("sdodin: bug: bad cqrp %ux\n", e); e = e+1 & Qmask; for(i = c->cqrp; i != e; i = i+1 & Qmask){ u = c->cq[1 + i]; c->cq[1 + i] = 0; slot = u & 0xfff; u &= ~slot; d = c->drive + slot; x = d->cmd; if(u & Cqdone){ x->cflag |= Done; u &= ~Cqdone; } if(u & (Crxfr | Cgood)){ if((u & Cgood) == 0) x->cflag |= Response; u &= ~(Crxfr | Cgood); } if(u & Cerr){ dprint("%s: Cerr ..\n", dnam(d)); a = analyze(d, x); x->cflag |= Done | a; u &= ~Cerr; } if(x->cflag & Done) wakeup(x); if(u) print("%s: odd bits %.8ux\n", dnam(d), u); } if(i == c->cqrp)print("odin: spur done\n"); c->cqrp = i; } static void updatedrive(Drive *d) { uint cause, s0, ewake; char *name; Cmd *x; static uint last, tk; ewake = 0; cause = d->ctlr->reg[pis[d->driveno]]; d->ctlr->reg[pis[d->driveno]] = cause; x = d->cmd; name = dnam(d); if(last != cause || Ticks - tk > 5*1000){ dprint("%s: ca %ux ta %ux\n", name, cause, gettask(d)); tk = Ticks; } if(cause & (Phyunrdy | Phyidto | Pisataup | Pisasup)){ s0 = d->state; if(cause == (Phyrdy | Comw)){ d->type = 0; d->state = Dnopower; } switch(cause & (Phyunrdy | Phyidto | Phyidok | Sigrx)){ case Phyunrdy: d->state = Dmissing; if(sstatus(d) & Sphyrdy){ if(d->type != 0) d->state = Dnew; else d->state = Dreset; } break; case Phyidto: d->type = 0; d->state = Dmissing; break; case Phyidok: d->type = Sas; d->state = Dnew; break; case Sigrx: d->type = Sata; d->state = Dnew; break; } dprint("%s: %s → %s [Apcrs] %s %ux\n", name, dstate(s0), dstate(d->state), type[d->type], sstatus(d)); if(s0 == Dready && d->state != Dready) idprint("%s: pulled\n", name); if(d->state != Dready || ci(d)) ewake |= Done | Noverdict; }else if(cause & Piburp) ewake |= Done | Noverdict; else if(cause & Pireset) ewake |= Done | Noverdict | Creset; else if(cause & Piunsupp){ print("%s: unsupported h/w: %.8ux\n", name, cause); ewake |= Done | Error; d->type = 0; d->state = Doffline; } if(ewake){ dprint("%s: ewake %.8ux\n", name, ewake); unsetci(d); x->cflag |= ewake; wakeup(x); } last = cause; } static int satareset(Drive *d) { ilock(d->ctlr); unsetci(d); iunlock(d->ctlr); if(gettask(d) & (ASdrq|ASbsy)) return -1; if(settxmode(d, d->udma) != 0) return -1; return 0; } static int msriopkt(SDreq *r, Drive *d) { int n, count, try, max, flag, task; uchar *cmd; cmd = r->cmd; aprint("%02ux %02ux %c %d %p\n", cmd[0], cmd[2], "rw"[r->write], r->dlen, r->data); r->rlen = 0; count = r->dlen; max = 65536; for(try = 0; try < 10; try++){ n = count; if(n > max) n = max; if(lockready(d) == -1) return SDeio; flag = buildpkt(d, r, r->data, n); task = gettask(d); if(flag & Atareset && satareset(d) == -1) setstate(d, Dreset); qunlock(d); if(flag & Noverdict){ if(flag & Creset) setstate(d, Dreset); print("%s: retry\n", dnam(d)); continue; } if(flag & Error){ if((task & Eidnf) == 0) print("%s: i/o error %ux\n", dnam(d), task); return r->status = SDcheck; } return r->status = SDok; } print("%s: bad disk\n", dnam(d)); return r->status = SDcheck; } static int msriosas(SDreq *r, Drive *d) { int try, flag; for(try = 0; try < 10; try++){ if(lockready(d) == -1) return SDeio; flag = buildsas(d, r, r->data, r->dlen); qunlock(d); if(flag & Noverdict){ if(flag & Creset) setstate(d, Dreset); print("%s: retry\n", dnam(d)); continue; } if(flag & Error){ print("%s: i/o error\n", dnam(d)); return r->status = SDcheck; } r->rlen = r->dlen; /* fishy */ return r->status; /* set in sasrspck */ } print("%s: bad disk\n", dnam(d)); sdsetsense(r, SDcheck, 3, r->write? 0xc00: 0x11, 0); return r->status = SDcheck; } static int flushcache(Drive *d) { int i; i = -1; if(lockready(d) == 0) i = mvflushcache(d); qunlock(d); return i; } static int msriosata(SDreq *r, Drive *d) { char *name; int i, n, count, try, max, flag, task; uvlong lba; uchar *cmd, *data; SDunit *unit; unit = r->unit; cmd = r->cmd; name = dnam(d); if(cmd[0] == 0x35 || cmd[0] == 0x91){ if(flushcache(d) == 0) return sdsetsense(r, SDok, 0, 0, 0); return sdsetsense(r, SDcheck, 3, 0xc, 2); } if((i = sdfakescsi(r)) != SDnostatus){ r->status = i; return i; } if((i = sdfakescsirw(r, &lba, &count, nil)) != SDnostatus) return i; max = 128; if(d->feat & Dllba) max = 65536; try = 0; data = r->data; while(count > 0){ n = count; if(n > max) n = max; if(lockready(d) == -1) return SDeio; flag = build(d, r->write, data, n, lba); task = gettask(d); if(flag & Atareset && satareset(d) == -1) setstate(d, Dreset); qunlock(d); if(flag & Noverdict){ if(flag & Creset) setstate(d, Dreset); if(++try == 2){ print("%s: bad disk\n", name); return r->status = SDeio; } iprint("%s: retry %lld [%.8ux]\n", name, lba, task); continue; } if(flag & Error){ iprint("%s: i/o error %ux @%,lld\n", name, task, lba); return r->status = SDeio; } count -= n; lba += n; data += n*unit->secsize; } r->rlen = data - (uchar*)r->data; r->status = SDok; return SDok; } static int msrio(SDreq *r) { Ctlr *c; Drive *d; SDunit *u; u = r->unit; c = u->dev->ctlr; d = c->drive + u->subno; if(d->feat & Datapi) return msriopkt(r, d); if(d->type == Sas) return msriosas(r, d); if(d->type == Sata) return msriosata(r, d); return sdsetsense(r, SDcheck, 3, 0x04, 0x24); } /* * §6.1.9.5 * not clear that this is necessary * we should know that it's a d2h from the status. * pio returns pio setup fises. hw bug? */ static int sdr(SDreq *r, Drive *d, int st) { uint i; if(i = fisoffset(d, 0/*Rd2h*/)) memmove(r->cmd, d->ctlr->fis + i, 16); else memset(r->cmd, 0xff, 16); r->status = st; return st; } /* * handle oob requests; * restrict & sanitize commands */ static int fisreqchk(Sfis *f, SDreq *r) { uchar *c; if((r->ataproto & Pprotom) == Ppkt) return SDnostatus; if(r->clen != 16) error("bad command length"); //error(Eio); c = r->cmd; if(c[0] == 0xf0){ sigtofis(f, r->cmd); return r->status = SDok; } c[0] = H2dev; c[1] = Fiscmd; c[7] |= Ataobs; return SDnostatus; } static int msataio(SDreq *r) { char *name; int try, flag, task; Ctlr *c; Drive *d; SDunit *u; int (*build)(Drive*, SDreq*, void*, int); u = r->unit; c = u->dev->ctlr; d = c->drive + u->subno; name = dnam(d); if(d->type != Sata) error("not sata"); if(r->cmd[0] == 0xf1){ d->state = Dreset; return r->status = SDok; } if((r->status = fisreqchk(d, r)) != SDnostatus) return r->status; build = buildfis; if((r->ataproto & Pprotom) == Ppkt) build = buildpkt; for(try = 0; try < 10; try++){ if(lockready(d) == -1) return SDeio; flag = build(d, r, r->data, r->dlen); task = gettask(d); if(flag & Atareset && satareset(d) == -1) setstate(d, Dreset); qunlock(d); if(flag & Noverdict){ if(flag & (Timeout | Creset)) setstate(d, Dreset); else if(task & Eabrt<<8){ /* assume bad cmd */ r->status = SDeio; return SDeio; } print("%s: retry\n", name); continue; } if(flag & Error){ print("%s: i/o error %.8ux\n", name, task); r->status = SDeio; return SDeio; } if(build != buildpkt) r->rlen = r->dlen; return sdr(r, d, SDok); } print("%s: bad disk\n", name); return sdr(r, d, SDeio); } static void msinterrupt(Ureg *, void *a) { Ctlr *c; uint u, i; static uint cnt; c = a; ilock(c); u = c->reg[Cis]; if(u == 0){ iunlock(c); return; } c->reg[Cis] = u & ~Iclr; if(u != Cdone && cnt++ < 15) print("sdodin: irq %s %.8ux\n", c->sdev->ifc->name, u); for(i = 0; i < 8; i++) if(u & (1<<i)*(Portirq|Portstop)) updatedrive(c->drive + i); if(u & Srsirq){ u = c->reg[Csis]; c->reg[Csis] = u; for(i = 0; i < 8; i++) if(u & 1<<i) updatedrive(c->drive + i); } if(u & Cdone){ updatedone(c); c->reg[Cis] = Cdone; } iunlock(c); } static char* mc(Drive *d) { char *s; s = ""; if(d->drivechange) s = "[newdrive]"; return s; } static int newsatadrive(Drive *d) { uint task; task = gettask(d); if((task & 0xffff) == 0x80) return SDretry; setfissig(d, getsig(d)); if(identify(d) != 0){ dprint("%s: identify failure\n", dnam(d)); return SDeio; } if(d->feat & Dpower && setfeatures(d, 0x85, 3*1000) != 0){ d->feat &= ~Dpower; if(satareset(d) == -1) return SDeio; } if(settxmode(d, d->udma) != 0){ dprint("%s: can't set tx mode\n", dnam(d)); return SDeio; } return SDok; } static void newoaf(Drive *d, int type) { uint ict, i; uvlong sa; Ctlr *c; i = d->driveno; c = d->ctlr; sa = pcread(c, i, Pawwn + 0); sa |= (uvlong)pcread(c, i, Pawwn + 4)<<32; putbe(d->tsasaddr, sa, 8); memmove(d->ssasaddr, d->ssasaddr, 8); ict = pcread(c, i, Pwwn + 8); putbe(d->ict, ict, 2); oafis(d, d->cmd->oaf, type); } static int sasinquiry(Drive *d) { SDreq r; SDunit *u; u = d->unit; memset(&r, 0, sizeof r); r.cmd[0] = 0x12; r.cmd[4] = 0xff; r.clen = 6; r.unit = u; return buildsas(d, &r, u->inquiry, sizeof u->inquiry); } static int sastur(Drive *d) { SDreq r; SDunit *u; u = d->unit; memset(&r, 0, sizeof r); r.clen = 6; r.unit = u; return buildsas(d, &r, 0, 0); } static int sasvpd(Drive *d, uchar *buf, int l) { SDreq r; SDunit *u; u = d->unit; memset(&r, 0, sizeof r); r.cmd[0] = 0x12; r.cmd[1] = 1; r.cmd[2] = 0x80; r.cmd[4] = l; r.clen = 6; r.unit = u; return buildsas(d, &r, buf, l); } static int sascapacity10(Drive *d, uchar *buf, int l) { SDreq r; SDunit *u; u = d->unit; memset(&r, 0, sizeof r); r.cmd[0] = 0x25; r.clen = 10; r.unit = u; return buildsas(d, &r, buf, l); } static int sascapacity16(Drive *d, uchar *buf, int l) { SDreq r; SDunit *u; u = d->unit; memset(&r, 0, sizeof r); r.cmd[0] = 0x9e; r.cmd[1] = 0x10; r.cmd[13] = l; r.clen = 16; r.unit = u; return buildsas(d, &r, buf, l); } static void frmove(char *p, uchar *c, int n) { char *op, *e; memmove(p, c, n); op = p; p[n] = 0; for(p = p + n - 1; p > op && *p == ' '; p--) *p = 0; e = p; p = op; while(*p == ' ') p++; memmove(op, p, n - (e - p)); } static void chkinquiry(Drive *d, uchar *c) { char buf[32], buf2[32], omod[sizeof d->model]; memmove(omod, d->model, sizeof d->model); frmove(buf, c + 8, 8); frmove(buf2, c + 16, 16); snprint(d->model, sizeof d->model, "%s %s", buf, buf2); frmove(d->firmware, c + 23, 4); if(memcmp(omod, d->model, sizeof omod) != 0) d->drivechange = 1; } static void chkvpd(Drive *d, uchar *c, int n) { char buf[sizeof d->serial]; int l; l = c[3]; if(l > n) l = n; frmove(buf, c + 4, l); if(strcmp(buf, d->serial) != 0) d->drivechange = 1; memmove(d->serial, buf, sizeof buf); } static int adjcapacity(Drive *d, uvlong ns, uint nss) { if(ns != 0) ns++; if(nss == 2352) nss = 2048; if(d->sectors != ns || d->secsize != nss){ d->drivechange = 1; d->sectors = ns; d->secsize = nss; } return 0; } static int chkcapacity10(uchar *p, uvlong *ns, uint *nss) { *ns = getbe(p, 4); *nss = getbe(p + 4, 4); return 0; } static int chkcapacity16(uchar *p, uvlong *ns, uint *nss) { *ns = getbe(p, 8); *nss = getbe(p + 8, 4); return 0; } static int sasprobe(Drive *d) { uchar buf[0x40]; int r; uint nss; uvlong ns; if((r = sastur(d)) != 0) return r; if((r = sasinquiry(d)) != 0) return r; chkinquiry(d, d->unit->inquiry); /* vpd 0x80 (unit serial) is not mandatory */ if((r = sasvpd(d, buf, sizeof buf)) == 0) chkvpd(d, buf, sizeof buf); else if(r & (Error | Timeout)) return r; else{ if(d->serial[0]) d->drivechange = 1; d->serial[0] = 0; } if((r = sascapacity10(d, buf, sizeof buf)) != 0) return r; chkcapacity10(buf, &ns, &nss); if(ns == 0xffffffff){ if((r = sascapacity16(d, buf, sizeof buf)) != 0) return r; chkcapacity16(buf, &ns, &nss); } adjcapacity(d, ns, nss); return 0; } static int newsasdrive(Drive *d) { memset(d->cmd->rsp, 0, sizeof d->cmd->rsp); newoaf(d, Ssp); switch(sasprobe(d) & (Error | Noverdict | Timeout | Sense)){ case Error: case Timeout: return SDeio; case Sense: case Noverdict: return SDretry; } return SDok; } static int newdrive(Drive *d) { char *t; int r; memset(&d->Sfis, 0, sizeof d->Sfis); memset(&d->Cfis, 0, sizeof d->Cfis); qlock(d); switch(d->type){ case Sata: r = newsatadrive(d); break; case Sas: r = newsasdrive(d); break; default: print("%s: bug: martian drive %d\n", dnam(d), d->type); qunlock(d); return -1; } t = type[d->type]; switch(r){ case SDok: idprint("%s: %s %,lld sectors\n", dnam(d), t, d->sectors); idprint(" %s %s %s %s\n", d->model, d->firmware, d->serial, mc(d)); setstate(d, Dready); break; case SDeio: idprint("%s: %s can't be initialized\n", dnam(d), t); setstate(d, Derror); case SDretry: break; } qunlock(d); return r; } static void statechange(Drive *d) { switch(d->state){ case Dmissing: case Dnull: case Doffline: d->drivechange = 1; d->unit->sectors = 0; break; case Dready: d->wait = 0; break; } } /* * we don't respect running commands. botch? */ static void checkdrive(Drive *d, int i) { uint s; if(d->unit == nil) return; ilock(d); s = sstatus(d); d->wait++; if(s & Sphyrdy) d->lastseen = Ticks; if(s != olds[i]){ dprint("%s: status: %.6ux -> %.6ux: %s\n", dnam(d), olds[i], s, dstate(d->state)); olds[i] = s; statechange(d); } switch(d->state){ case Dnull: case Dmissing: if(d->type != 0 && s & Sphyrdy) d->state = Dnew; break; case Dnopower: phyreset(d); /* spinup */ break; case Dnew: if(d->wait % 6 != 0) break; iunlock(d); newdrive(d); ilock(d); break; case Dready: d->wait = 0; break; case Derror: d->wait = 0; d->state = Dreset; case Dreset: if(d->wait % 40 != 0) break; reset(d); break; case Doffline: case Dportreset: break; } iunlock(d); } static void mskproc(void*) { int i; while(waserror()) ; for(;;){ tsleep(&up->sleep, return0, 0, Nms); for(i = 0; i < nmsdrive; i++) checkdrive(msdrive[i], i); } } static void ledcfg(Ctlr *c, int port, uint cfg) { uint u, r, s; r = Drivectl + (port>>2)*Gpiooff; s = 15 - port & 3; s *= 8; u = gpread(c, r); u &= ~(0xff << s); u |= cfg<<s; gpwrite(c, r, u); } static uchar ses2ledstd[Ibpilast] = { [Ibpinone] Lhigh*Aled, [Ibpinormal] Lsof*Aled | Llow*Locled | Llow*Errled, [Ibpirebuild] Lsof*Aled | Llow*Locled | Llow*Errled, [Ibpilocate] Lsof*Aled | Lblinka*Locled | Llow*Errled, [Ibpispare] Lsof*Aled | Llow*Locled| Lblinka*Errled, [Ibpipfa] Lsof*Aled | Lblinkb*Locled | Llow*Errled, [Ibpifail] Lsof*Aled | Llow*Locled | Lhigh*Errled, [Ibpicritarray] Lsof*Aled, [Ibpifailarray] Lsof*Aled, }; static uchar ses2led[Ibpilast] = { [Ibpinone] Lhigh*Aled, [Ibpinormal] Lsof*Aled | Llow*Locled | Llow*Errled, [Ibpirebuild] Lsof*Aled | Lblinkaneg*Locled | Llow*Errled, [Ibpilocate] Lsof*Aled | Lhigh*Locled | Llow*Errled, [Ibpispare] Lsof*Aled | Lblinka*Locled| Llow*Errled, [Ibpipfa] Lsof*Aled | Lblinkb*Locled | Llow*Errled, [Ibpifail] Lsof*Aled | Llow*Locled | Lhigh*Errled, [Ibpicritarray] Lsof*Aled, [Ibpifailarray] Lsof*Aled, }; static void setupled(Ctlr *c) { int i, l, blen; pcicfgw32(c->pci, Gpio, pcicfgr32(c->pci, Gpio) | 1<<7); /* * configure a for 4hz (1/8s on and 1/8s off) * configure b for 1hz (2/8s on and 6/8s off) */ l = 3 + c->ndrive >> 2; blen = 3*24 - 1; for(i = 0; i < l*Gpiooff; i += Gpiooff){ gpwrite(c, Sgconf0 + i, blen*Autolen | Blinkben | Blinkaen | Sgpioen); gpwrite(c, Sgconf1 + i, 1*Bhi | 1*Blo | 1*Ahi | 7*Alo); gpwrite(c, Sgconf3 + i, 7<<20 | Sdoutauto); } } static void trebuild(Ctlr *c, Drive *d, int dno, uint i) { uchar bits; if(0 && d->led == Ibpirebuild){ switch(i%19){ case 0: bits = 0; break; case 1: bits = ses2led[Ibpirebuild] | Lblinka*Locled; break; case 3: bits = ses2led[Ibpirebuild] | Lblinkb*Locled; break; } }else bits = ses2led[d->led]; if(d->ledbits != bits) ledcfg(c, dno, bits); } static long odinledr(SDunit *u, Chan *ch, void *a, long n, vlong off) { Ctlr *c; Drive *d; c = u->dev->ctlr; d = c->drive + u->subno; return ledr(d, ch, a, n, off); } static long odinledw(SDunit *u, Chan *ch, void *a, long n, vlong off) { Ctlr *c; Drive *d; c = u->dev->ctlr; d = c->drive + u->subno; return ledw(d, ch, a, n, off); } /* * this kproc can probablly go when i figure out * how to program the manual blinker */ static void ledkproc(void*) { uint i, j; Drive *d; for(i = 0; i < nmsdrive; i++){ d = msdrive[i]; d->nled = 2; /* how to know? */ } for(i = 0; i < nmsctlr; i++) pcicfgw32(msctlr[i].pci, Gpio, pcicfgr32(msctlr[i].pci, Gpio) | 1<<7); for(i = 0; i < nmsctlr; i++) setupled(msctlr + i); for(i = 0; ; i++){ esleep(Nms); for(j = 0; j < nmsdrive; j++){ d = msdrive[j]; trebuild(d->ctlr, d, j, i); } } } static int msenable(SDev *s) { char buf[32]; Ctlr *c; static int once; c = s->ctlr; ilock(c); if(!c->enabled){ if(once++ == 0) kproc("odin", mskproc, 0); pcisetbme(c->pci); snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name); intrenable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf); // c->reg[Cis] |= Swirq1; /* force initial interrupt. */ c->enabled = 1; } iunlock(c); return 1; } static int msdisable(SDev *s) { char buf[32]; Ctlr *c; c = s->ctlr; ilock(c); // disable(c->hba); snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name); intrdisable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf); c->enabled = 0; iunlock(c); return 1; } static int scsiish(Drive *d) { return d->type == Sas || d->feat & Datapi; } static int msonline(SDunit *u) { int r; Ctlr *c; Drive *d; c = u->dev->ctlr; d = c->drive + u->subno; r = 0; if(scsiish(d)){ if(!d->drivechange) return r; r = scsionline(u); if(r > 0) d->drivechange = 0; return r; } ilock(d); if(d->drivechange){ r = 2; d->drivechange = 0; u->sectors = d->sectors; u->secsize = d->secsize; } else if(d->state == Dready) r = 1; iunlock(d); return r; } static void verifychk(Drive *d) { int w; if(!up) checkdrive(d, d->driveno); for(w = 0; w < 12000; w += 210){ if(d->state == Dready) break; if(w > 2000 && d->state != Dnew) break; if((sstatus(d) & Sphyrdy) == 0) break; if(!up) checkdrive(d, d->driveno); esleep(210); } } static int msverify(SDunit *u) { int chk; Ctlr *c; Drive *d; static int once; c = u->dev->ctlr; d = c->drive + u->subno; ilock(c); ilock(d); chk = 0; if(d->unit == nil){ d->unit = u; sdaddfile(u, "led", 0644, eve, odinledr, odinledw); once++; if(once == nmsctlr) kproc("mvled", ledkproc, 0); chk = 1; } iunlock(d); iunlock(c); /* * since devsd doesn't know much about hot-plug drives, * we need to give detected drives a chance. */ if(chk){ reset(d); verifychk(d); } return 1; } static uint* map(Pcidev *p, int bar) { uintptr io; io = p->mem[bar].bar & ~0xf; return (uint*)vmap(io, p->mem[bar].size); } /* §5.1.3 */ static void initmem(Ctlr *c) { c->fis = malloc(0x800 + 0x100*16); /* §6.1.9.3 */ c->cl = malloc(nelem(c->cq)*sizeof *c->cl); c->cmdtab = malloc(Nctlrdrv*sizeof *c->cmdtab); if(c->fis == nil || c->cl == nil || c->cmdtab == nil) panic("sdodin: no memory"); c->reg[Fisbase + 0] = PCIWADDR(c->fis); c->reg[Fisbase + 1] = Pciwaddrh(c->fis); c->reg[Cqbase + 0] = PCIWADDR(c->cq); c->reg[Cqbase + 1] = Pciwaddrh(c->cq); c->reg[Cqcfg] = Cqen | Noattn | nelem(c->cq) - 1; c->reg[Dqbase + 0] = PCIWADDR(c->dq); c->reg[Dqbase + 1] = Pciwaddrh(c->dq); c->reg[Dqcfg] = Dqen | nelem(c->dq); c->reg[Clbase + 0] = PCIWADDR(c->cl); c->reg[Clbase + 1] = Pciwaddrh(c->cl); } /* §5.1.2 */ static void startup(Ctlr *c) { c->reg[Gctl] |= Reset; while(c->reg[Gctl] & Reset) ; initmem(c); c->reg[Cie] = Swirq1 | 0xff*Portstop | 0xff*Portirq | Srsirq | Issstop | Cdone; c->reg[Gctl] |= Intenable; c->reg[Portcfg0] = Rmask*Regen | Dataunke | Rsple | Framele; c->reg[Portcfg1] = Rmask*Regen | 0xff*Xmten | /*Cmdirq |*/ Fisen | Resetiss | Issueen; c->reg[Csie] = ~0; sswrite(c, 0, Pwdtimer, 0x7fffff); } static void forcetype(Ctlr*) { /* * if we want to force sas/sata, here's where to do it. */ } static void setupcmd(Drive *d) { int i; Ctlr *c; Cmd *cmd; Cmdh *h; i = d->driveno; c = d->ctlr; d->cmd = c->cmdtab + i; d->cmd->cmdh = c->cl + i; cmd = d->cmd; h = cmd->cmdh; /* prep the precomputable bits in the cmd hdr §6.1.4 */ putle(h->ctab, Pciw64(&cmd->Ctab), sizeof h->ctab); putle(h->oaf, Pciw64(&cmd->Oaf), sizeof h->oaf); putle(h->statb, Pciw64(&cmd->Statb), sizeof h->statb); putle(h->prd, Pciw64(&cmd->Aprdt), sizeof h->prd); /* finally, set up the wide-port participating bit */ pcwrite(c, i, Pwidecfg, 1<<i); } static void phychk(Ctlr *c, Drive *d) { int i; uvlong u; static uchar src[8] = {0x50, 0x03, 0x04, 0x80}; i = d->driveno; memmove(d->ssasaddr, src, 8); u = getbe(d->ssasaddr, 8); pcwrite(c, i, Paddr + 0, u); pcwrite(c, i, Paddr + 4, u>>32); } static SDev* mspnp(void) { int i, nunit; Ctlr *c; Drive *d; Pcidev *p; SDev **ll, *s, *s0; static int done; if(done++) return nil; s0 = nil; ll = &s0; for(p = nil; (p = pcimatch(p, 0x11ab, 0x6485)) != nil; ){ if(nmsctlr == Nctlr){ print("sdodin: too many controllers\n"); break; } c = msctlr + nmsctlr; s = sdevs + nmsctlr; memset(c, 0, sizeof *c); memset(s, 0, sizeof *s); if((c->reg = map(p, Mebar)) == 0){ print("sdodin: bar %#p in use\n", c->reg); continue; } nunit = p->did>>4 & 0xf; s->ifc = &sdodinifc; s->idno = 'a' + nmsctlr; s->ctlr = c; c->sdev = s; c->pci = p; c->ndrive = s->nunit = nunit; i = pcicfgr32(p, Dctl) & ~(7<<12); pcicfgw32(p, Dctl, i | 4<<12); print("#S/sd%c: odin ii sata/sas with %d ports\n", s->idno, nunit); startup(c); forcetype(c); for(i = 0; i < nunit; i++){ d = c->drive + i; d->driveno = i; d->sectors = 0; d->ctlr = c; setupcmd(d); snprint(d->name, sizeof d->name, "odin%d.%d", nmsctlr, i); msdrive[nmsdrive + i] = d; // phychk(c, d); c->reg[pis[i] + 1] = Sync | Phyerr | Stperr | Crcerr | Linkrx | Martianfis | Anot | Bist | Sigrx | Phyunrdy | Martiantag | Bnot | Comw | Portsel | Hreset | Phyidto | Phyidok | Hresetok | Phyrdy; } nmsdrive += nunit; nmsctlr++; *ll = s; ll = &s->next; } return s0; } static char* msrctlsata(Drive *d, char *p, char *e) { p = seprint(p, e, "flag\t"); p = pflag(p, e, d); p = seprint(p, e, "udma\t%d\n", d->udma); return p; } static char* rctldebug(char *p, char *e, Ctlr *c, Drive *d) { int i; uvlong sasid; i = d->driveno; p = seprint(p, e, "sstatus\t%.8ux\n", sstatus(d)); // p = seprint(p, e, "cis\t%.8ux %.8ux\n", c->reg[Cis], c->reg[Cie]); // p = seprint(p, e, "gis\t%.8ux\n", c->reg[Gis]); p = seprint(p, e, "pis\t%.8ux %.8ux\n", c->reg[pis[i]], c->reg[pis[i] + 1]); p = seprint(p, e, "sis\t%.8ux\n", c->reg[Csis]); p = seprint(p, e, "cqwp\t%.8ux\n", c->cq[0]); p = seprint(p, e, "cerror\t%.8ux %.8ux\n", *(uint*)d->cmd->error, *(uint*)(d->cmd->error+4)); p = seprint(p, e, "task\t%.8ux\n", gettask(d)); p = seprint(p, e, "ptype\t%.8ux\n", c->reg[Ptype]); p = seprint(p, e, "satactl\t%.8ux\n", pcread(c, i, Psatactl)); /* appears worthless */ p = seprint(p, e, "info %.8ux %.8ux\n", pcread(c, i, Pinfo), pcread(c, i, Painfo)); p = seprint(p, e, "physts %.8ux\n", pcread(c, i, Pphysts)); p = seprint(p, e, "widecfg %.8ux\n", pcread(c, i, Pwidecfg)); sasid = pcread(c, i, Pwwn + 0); sasid |= (uvlong)pcread(c, i, Pwwn + 4)<<32; p = seprint(p, e, "wwn %.16llux %.8ux\n", sasid, pcread(c, i, Pwwn + 8)); sasid = pcread(c, i, Pawwn + 0); sasid |= (uvlong)pcread(c, i, Pawwn + 4)<<32; p = seprint(p, e, "awwn %.16llux\n", sasid); sasid = pcread(c, i, Paddr + 0); sasid |= (uvlong)pcread(c, i, Paddr + 4)<<32; p = seprint(p, e, "sasid %.16llux\n", sasid); return p; } static int msrctl(SDunit *u, char *p, int l) { char *e, *op; Ctlr *c; Drive *d; if((c = u->dev->ctlr) == nil) return 0; d = c->drive + u->subno; e = p + l; op = p; p = seprint(p, e, "state\t%s\n", dstate(d->state)); p = seprint(p, e, "type\t%s", type[d->type]); if(d->type == Sata) p = seprint(p, e, " sig %.8ux", getsig(d)); p = seprint(p, e, "\n"); if(d->state == Dready){ p = seprint(p, e, "model\t%s\n", d->model); p = seprint(p, e, "serial\t%s\n", d->serial); p = seprint(p, e, "firm\t%s\n", d->firmware); p = seprint(p, e, "wwn\t%llux\n", d->wwn); p = msrctlsata(d, p, e); } p = rctldebug(p, e, c, d); p = seprint(p, e, "geometry %llud %lud\n", d->sectors, u->secsize); return p - op; } static void forcestate(Drive *d, char *state) { int i; for(i = 1; i < nelem(diskstates); i++) if(strcmp(state, diskstates[i]) == 0) break; if(i == nelem(diskstates)) error(Ebadctl); ilock(d); d->state = 1 << i - 1; statechange(d); iunlock(d); } static int mswctl(SDunit *u, Cmdbuf *cmd) { char **f; Ctlr *c; Drive *d; c = u->dev->ctlr; d = c->drive + u->subno; f = cmd->f; if(strcmp(f[0], "state") == 0) forcestate(d, f[1]? f[1]: "null"); else cmderror(cmd, Ebadctl); return 0; } static int mswtopctl(SDev*, Cmdbuf *cmd) { char **f; int *v; f = cmd->f; v = 0; if(strcmp(f[0], "debug") == 0) v = &debug; else if(strcmp(f[0], "idprint") == 0) v = &idebug; else if(strcmp(f[0], "aprint") == 0) v = &adebug; else cmderror(cmd, Ebadctl); if(cmd->nf == 1) *v ^= 1; else if(cmd->nf == 2) *v = strcmp(f[1], "on") == 0; else cmderror(cmd, Ebadarg); return 0; } SDifc sdodinifc = { "odin", mspnp, nil, msenable, msdisable, msverify, msonline, msrio, msrctl, mswctl, scsibio, nil, /* probe */ nil, /* clear */ nil, mswtopctl, msataio, };