plan9fox/sys/src/9/pc/sdodin.c
2011-12-12 16:55:26 +01:00

2844 lines
52 KiB
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);
/*
* 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<<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;
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,
};