--- /sys/src/9/pc/sdodin.c Sat Aug 10 00:00:00 2013 +++ /sys/src/9/pc/sdodin.c Sat Aug 10 00:00:00 2013 @@ -0,0 +1,2790 @@ +/* + * 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 +#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 Pciw64(x) (uvlong)PCIWADDR(x) +#define Pciwaddrh(va) (sizeof(uintptr)>4? (uvlong)PCIWADDR(va)>>32: 0) +#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 = 6, + 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 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<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)<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<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<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<cq[0], n, i); + /* + * xinc doesn't return the previous value and i can't + * figure out how to do this without a lock + * s = _xinc(&c->dqwp); + */ + 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<drive + i); + if(u & Srsirq){ + u = c->reg[Csis]; + c->reg[Csis] = u; + for(i = 0; i < 8; i++) + if(u & 1<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; + + 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<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 = smalloc(0x800 + 0x100*16); /* §6.1.9.3 */ + 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->cl = smalloc(nelem(c->cq)*sizeof *c->cl); + c->reg[Clbase + 0] = PCIWADDR(c->cl); + c->reg[Clbase + 1] = Pciwaddrh(c->cl); + c->cmdtab = smalloc(Nctlrdrv*sizeof *c->cmdtab); +} + +/* §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<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, +}; --- /sys/man/3/sdodin Sat Jan 5 18:10:42 2013 +++ /sys/man/3/sdodin Mon Dec 6 00:00:00 2010 @@ -0,0 +1,147 @@ +.TH SDODIN 3 +.SH NAME +sdodin \- Marvell Odin II SAS/SATA drivers +.SH SYNOPSIS +.nf +.B bind -a #S /dev +.sp 0.3v +.BI /dev/sdctl +.sp 0.3v +.BI /dev/sd a 0/ctl +.BI /dev/sd a 0/raw +.BI /dev/sd a 0/data +.BI /dev/sd a 0/led +\&... +.fi +.SH DESCRIPTION +The +.I sdodin +driver provides access to SAS, SATA and ATAPI devices via the +.IR sd (3) +interface. The Odin programming interface supports up to 16 +hot-swappable devices per controller. +.PP +Odin controllers are detected automatically. +Currently only the Marvell 6485 is supported, although +devices using the Marvell 6420, 6440 and 6480 chipsets can +likely be supported with minimal changes. +.PP +The top level control file, +.BR /dev/sdctl , +supports the following control messages for +.IR sdodin : +.TF "\fLodin idprint" +.TP +.B odin debug +Toggle debug messages. Default is off. +.TP +.B odin idprint +Toggle printing of drive identification messages. Default is on. +Prints short messages when a drive is identified or removed. +.TP +.B odin aprint +Print SCSI commands executed. Default is off. +.PD +.PP +The device-level +.B ctl +file supports: +.TF \fLnopower +.TP +.BI "state " state +Force a transition to the named +.IR state . +The states are: +.RS +.TF portreset +.TP +.B null +ignored (may only be reached manually); +.TP +.B missing +not detected; +.TP +.B nopower +detected in power-saving state; +.B new +newly discovered; +.TP +.B ready +ready for commands; +.TP +.B error +not ready for commands due to error; +.TP +.B reset +being reset gently; +.TP +.B portreset +being fully reset; +.TP +.B offline +device failed +.B portreset +(a port reset will be attempted periodically). +.RE +.PD +.PP +For devices present at boot, the transition is from state +.B new +to state +.BR ready . +.PP +The +.B led +file allows the external control of enclosure LED +states through SES-2 messages over GPIO/SFF-8485. I²C +transport is not supported. The contents of the +.B led +file is the current state of the port's LEDs. Writing +a state to the file will set the given state. +The Odin does not set LED states and slots without +drives may not be controlled. The standard SES states accepted are +.TF \fLfailarray +.TP +.B none +no state set; +.TP +.B normal +nominal condition; +.TP +.B rebuild +parent lun is rebuilding; +.TP +.B locate +show drive location; +.TP +.B spare +hot spare drive; +.TP +.B pfa +predicted failure; +.TP +.B critarray +array has lost redundency; +.TP +.B failarray +failed array; +.TP +.B fail +drive has failed and should be replaced. +backplane buzzer will sound. +.PD +.PP +The LED state has no effect on drive function. +.SH SOURCE +.B /sys/src/9/pc/sdodin.c +.SH SEE ALSO +.IR atazz (8), +.IR scuzz (8), +.IR sd (3), +SES-2 (ANSI INCITS 305-1998) +.SH BUGS +Enclosure management, port multipliers and SAS expanders +are not supported. Commands that take too long to +complete will be timed out; +.L "security erase unit" +is almost certain to time out.