55d31f2cab
catch the error() that can be thrown by sleep() and tsleep() in kprocs. add missing pexit() calls. always set the freemem argument to pexit() from kproc otherwise the process gets added to the broken list.
2846 lines
52 KiB
C
2846 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;
|
|
|
|
while(waserror())
|
|
;
|
|
for(;;){
|
|
tsleep(&up->sleep, return0, 0, Nms);
|
|
for(i = 0; i < nmsdrive; i++)
|
|
checkdrive(msdrive[i], i);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ledcfg(Ctlr *c, int port, uint cfg)
|
|
{
|
|
uint u, r, s;
|
|
|
|
r = Drivectl + (port>>2)*Gpiooff;
|
|
s = 15 - port & 3;
|
|
s *= 8;
|
|
u = gpread(c, r);
|
|
u &= ~(0xff << s);
|
|
u |= cfg<<s;
|
|
gpwrite(c, r, u);
|
|
}
|
|
|
|
static uchar ses2ledstd[Ibpilast] = {
|
|
[Ibpinone] Lhigh*Aled,
|
|
[Ibpinormal] Lsof*Aled | Llow*Locled | Llow*Errled,
|
|
[Ibpirebuild] Lsof*Aled | Llow*Locled | Llow*Errled,
|
|
[Ibpilocate] Lsof*Aled | Lblinka*Locled | Llow*Errled,
|
|
[Ibpispare] Lsof*Aled | Llow*Locled| Lblinka*Errled,
|
|
[Ibpipfa] Lsof*Aled | Lblinkb*Locled | Llow*Errled,
|
|
[Ibpifail] Lsof*Aled | Llow*Locled | Lhigh*Errled,
|
|
[Ibpicritarray] Lsof*Aled,
|
|
[Ibpifailarray] Lsof*Aled,
|
|
};
|
|
|
|
static uchar ses2led[Ibpilast] = {
|
|
[Ibpinone] Lhigh*Aled,
|
|
[Ibpinormal] Lsof*Aled | Llow*Locled | Llow*Errled,
|
|
[Ibpirebuild] Lsof*Aled | Lblinkaneg*Locled | Llow*Errled,
|
|
[Ibpilocate] Lsof*Aled | Lhigh*Locled | Llow*Errled,
|
|
[Ibpispare] Lsof*Aled | Lblinka*Locled| Llow*Errled,
|
|
[Ibpipfa] Lsof*Aled | Lblinkb*Locled | Llow*Errled,
|
|
[Ibpifail] Lsof*Aled | Llow*Locled | Lhigh*Errled,
|
|
[Ibpicritarray] Lsof*Aled,
|
|
[Ibpifailarray] Lsof*Aled,
|
|
};
|
|
|
|
static void
|
|
setupled(Ctlr *c)
|
|
{
|
|
int i, l, blen;
|
|
pcicfgw32(c->pci, Gpio, pcicfgr32(c->pci, Gpio) | 1<<7);
|
|
|
|
/*
|
|
* configure a for 4hz (1/8s on and 1/8s off)
|
|
* configure b for 1hz (2/8s on and 6/8s off)
|
|
*/
|
|
l = 3 + c->ndrive >> 2;
|
|
blen = 3*24 - 1;
|
|
for(i = 0; i < l*Gpiooff; i += Gpiooff){
|
|
gpwrite(c, Sgconf0 + i, blen*Autolen | Blinkben | Blinkaen | Sgpioen);
|
|
gpwrite(c, Sgconf1 + i, 1*Bhi | 1*Blo | 1*Ahi | 7*Alo);
|
|
gpwrite(c, Sgconf3 + i, 7<<20 | Sdoutauto);
|
|
}
|
|
}
|
|
|
|
static void
|
|
trebuild(Ctlr *c, Drive *d, int dno, uint i)
|
|
{
|
|
uchar bits;
|
|
|
|
if(0 && d->led == Ibpirebuild){
|
|
switch(i%19){
|
|
case 0:
|
|
bits = 0;
|
|
break;
|
|
case 1:
|
|
bits = ses2led[Ibpirebuild] | Lblinka*Locled;
|
|
break;
|
|
case 3:
|
|
bits = ses2led[Ibpirebuild] | Lblinkb*Locled;
|
|
break;
|
|
}
|
|
}else
|
|
bits = ses2led[d->led];
|
|
if(d->ledbits != bits)
|
|
ledcfg(c, dno, bits);
|
|
}
|
|
|
|
static long
|
|
odinledr(SDunit *u, Chan *ch, void *a, long n, vlong off)
|
|
{
|
|
Ctlr *c;
|
|
Drive *d;
|
|
|
|
c = u->dev->ctlr;
|
|
d = c->drive + u->subno;
|
|
return ledr(d, ch, a, n, off);
|
|
}
|
|
|
|
static long
|
|
odinledw(SDunit *u, Chan *ch, void *a, long n, vlong off)
|
|
{
|
|
Ctlr *c;
|
|
Drive *d;
|
|
|
|
c = u->dev->ctlr;
|
|
d = c->drive + u->subno;
|
|
return ledw(d, ch, a, n, off);
|
|
}
|
|
|
|
/*
|
|
* this kproc can probablly go when i figure out
|
|
* how to program the manual blinker
|
|
*/
|
|
static void
|
|
ledkproc(void*)
|
|
{
|
|
uint i, j;
|
|
Drive *d;
|
|
|
|
for(i = 0; i < nmsdrive; i++){
|
|
d = msdrive[i];
|
|
d->nled = 2; /* how to know? */
|
|
}
|
|
for(i = 0; i < nmsctlr; i++)
|
|
pcicfgw32(msctlr[i].pci, Gpio, pcicfgr32(msctlr[i].pci, Gpio) | 1<<7);
|
|
for(i = 0; i < nmsctlr; i++)
|
|
setupled(msctlr + i);
|
|
for(i = 0; ; i++){
|
|
esleep(Nms);
|
|
for(j = 0; j < nmsdrive; j++){
|
|
d = msdrive[j];
|
|
trebuild(d->ctlr, d, j, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
msenable(SDev *s)
|
|
{
|
|
char buf[32];
|
|
Ctlr *c;
|
|
static int once;
|
|
|
|
c = s->ctlr;
|
|
ilock(c);
|
|
if(!c->enabled){
|
|
if(once++ == 0)
|
|
kproc("odin", mskproc, 0);
|
|
pcisetbme(c->pci);
|
|
snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name);
|
|
intrenable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf);
|
|
// c->reg[Cis] |= Swirq1; /* force initial interrupt. */
|
|
c->enabled = 1;
|
|
}
|
|
iunlock(c);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
msdisable(SDev *s)
|
|
{
|
|
char buf[32];
|
|
Ctlr *c;
|
|
|
|
c = s->ctlr;
|
|
ilock(c);
|
|
// disable(c->hba);
|
|
snprint(buf, sizeof buf, "%s (%s)", s->name, s->ifc->name);
|
|
intrdisable(c->pci->intl, msinterrupt, c, c->pci->tbdf, buf);
|
|
c->enabled = 0;
|
|
iunlock(c);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
scsiish(Drive *d)
|
|
{
|
|
return d->type == Sas || d->feat & Datapi;
|
|
}
|
|
|
|
static int
|
|
msonline(SDunit *u)
|
|
{
|
|
int r;
|
|
Ctlr *c;
|
|
Drive *d;
|
|
|
|
c = u->dev->ctlr;
|
|
d = c->drive + u->subno;
|
|
r = 0;
|
|
|
|
if(scsiish(d)){
|
|
if(!d->drivechange)
|
|
return r;
|
|
r = scsionline(u);
|
|
if(r > 0)
|
|
d->drivechange = 0;
|
|
return r;
|
|
}
|
|
ilock(d);
|
|
if(d->drivechange){
|
|
r = 2;
|
|
d->drivechange = 0;
|
|
u->sectors = d->sectors;
|
|
u->secsize = d->secsize;
|
|
} else if(d->state == Dready)
|
|
r = 1;
|
|
iunlock(d);
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
verifychk(Drive *d)
|
|
{
|
|
int w;
|
|
|
|
if(!up)
|
|
checkdrive(d, d->driveno);
|
|
for(w = 0; w < 12000; w += 210){
|
|
if(d->state == Dready)
|
|
break;
|
|
if(w > 2000 && d->state != Dnew)
|
|
break;
|
|
if((sstatus(d) & Sphyrdy) == 0)
|
|
break;
|
|
if(!up)
|
|
checkdrive(d, d->driveno);
|
|
esleep(210);
|
|
}
|
|
}
|
|
|
|
static int
|
|
msverify(SDunit *u)
|
|
{
|
|
int chk;
|
|
Ctlr *c;
|
|
Drive *d;
|
|
static int once;
|
|
|
|
c = u->dev->ctlr;
|
|
d = c->drive + u->subno;
|
|
ilock(c);
|
|
ilock(d);
|
|
chk = 0;
|
|
if(d->unit == nil){
|
|
d->unit = u;
|
|
sdaddfile(u, "led", 0644, eve, odinledr, odinledw);
|
|
once++;
|
|
if(once == nmsctlr)
|
|
kproc("mvled", ledkproc, 0);
|
|
chk = 1;
|
|
}
|
|
iunlock(d);
|
|
iunlock(c);
|
|
|
|
/*
|
|
* since devsd doesn't know much about hot-plug drives,
|
|
* we need to give detected drives a chance.
|
|
*/
|
|
if(chk){
|
|
reset(d);
|
|
verifychk(d);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint*
|
|
map(Pcidev *p, int bar)
|
|
{
|
|
uintptr io;
|
|
|
|
io = p->mem[bar].bar & ~0xf;
|
|
return (uint*)vmap(io, p->mem[bar].size);
|
|
}
|
|
|
|
/* §5.1.3 */
|
|
static void
|
|
initmem(Ctlr *c)
|
|
{
|
|
c->fis = malloc(0x800 + 0x100*16); /* §6.1.9.3 */
|
|
c->cl = malloc(nelem(c->cq)*sizeof *c->cl);
|
|
c->cmdtab = malloc(Nctlrdrv*sizeof *c->cmdtab);
|
|
if(c->fis == nil || c->cl == nil || c->cmdtab == nil)
|
|
panic("sdodin: no memory");
|
|
c->reg[Fisbase + 0] = PCIWADDR(c->fis);
|
|
c->reg[Fisbase + 1] = Pciwaddrh(c->fis);
|
|
c->reg[Cqbase + 0] = PCIWADDR(c->cq);
|
|
c->reg[Cqbase + 1] = Pciwaddrh(c->cq);
|
|
c->reg[Cqcfg] = Cqen | Noattn | nelem(c->cq) - 1;
|
|
c->reg[Dqbase + 0] = PCIWADDR(c->dq);
|
|
c->reg[Dqbase + 1] = Pciwaddrh(c->dq);
|
|
c->reg[Dqcfg] = Dqen | nelem(c->dq);
|
|
c->reg[Clbase + 0] = PCIWADDR(c->cl);
|
|
c->reg[Clbase + 1] = Pciwaddrh(c->cl);
|
|
}
|
|
|
|
/* §5.1.2 */
|
|
static void
|
|
startup(Ctlr *c)
|
|
{
|
|
c->reg[Gctl] |= Reset;
|
|
while(c->reg[Gctl] & Reset)
|
|
;
|
|
initmem(c);
|
|
c->reg[Cie] = Swirq1 | 0xff*Portstop | 0xff*Portirq | Srsirq | Issstop | Cdone;
|
|
c->reg[Gctl] |= Intenable;
|
|
c->reg[Portcfg0] = Rmask*Regen | Dataunke | Rsple | Framele;
|
|
c->reg[Portcfg1] = Rmask*Regen | 0xff*Xmten | /*Cmdirq |*/ Fisen | Resetiss | Issueen;
|
|
c->reg[Csie] = ~0;
|
|
sswrite(c, 0, Pwdtimer, 0x7fffff);
|
|
}
|
|
|
|
static void
|
|
forcetype(Ctlr*)
|
|
{
|
|
/*
|
|
* if we want to force sas/sata, here's where to do it.
|
|
*/
|
|
}
|
|
|
|
static void
|
|
setupcmd(Drive *d)
|
|
{
|
|
int i;
|
|
Ctlr *c;
|
|
Cmd *cmd;
|
|
Cmdh *h;
|
|
|
|
i = d->driveno;
|
|
c = d->ctlr;
|
|
d->cmd = c->cmdtab + i;
|
|
d->cmd->cmdh = c->cl + i;
|
|
cmd = d->cmd;
|
|
h = cmd->cmdh;
|
|
|
|
/* prep the precomputable bits in the cmd hdr §6.1.4 */
|
|
putle(h->ctab, Pciw64(&cmd->Ctab), sizeof h->ctab);
|
|
putle(h->oaf, Pciw64(&cmd->Oaf), sizeof h->oaf);
|
|
putle(h->statb, Pciw64(&cmd->Statb), sizeof h->statb);
|
|
putle(h->prd, Pciw64(&cmd->Aprdt), sizeof h->prd);
|
|
|
|
/* finally, set up the wide-port participating bit */
|
|
pcwrite(c, i, Pwidecfg, 1<<i);
|
|
}
|
|
|
|
static void
|
|
phychk(Ctlr *c, Drive *d)
|
|
{
|
|
int i;
|
|
uvlong u;
|
|
static uchar src[8] = {0x50, 0x03, 0x04, 0x80};
|
|
|
|
i = d->driveno;
|
|
memmove(d->ssasaddr, src, 8);
|
|
u = getbe(d->ssasaddr, 8);
|
|
pcwrite(c, i, Paddr + 0, u);
|
|
pcwrite(c, i, Paddr + 4, u>>32);
|
|
}
|
|
|
|
static SDev*
|
|
mspnp(void)
|
|
{
|
|
int i, nunit;
|
|
Ctlr *c;
|
|
Drive *d;
|
|
Pcidev *p;
|
|
SDev **ll, *s, *s0;
|
|
static int done;
|
|
|
|
if(done++)
|
|
return nil;
|
|
s0 = nil;
|
|
ll = &s0;
|
|
for(p = nil; (p = pcimatch(p, 0x11ab, 0x6485)) != nil; ){
|
|
if(nmsctlr == Nctlr){
|
|
print("sdodin: too many controllers\n");
|
|
break;
|
|
}
|
|
c = msctlr + nmsctlr;
|
|
s = sdevs + nmsctlr;
|
|
memset(c, 0, sizeof *c);
|
|
memset(s, 0, sizeof *s);
|
|
if((c->reg = map(p, Mebar)) == 0){
|
|
print("sdodin: bar %#p in use\n", c->reg);
|
|
continue;
|
|
}
|
|
nunit = p->did>>4 & 0xf;
|
|
s->ifc = &sdodinifc;
|
|
s->idno = 'a' + nmsctlr;
|
|
s->ctlr = c;
|
|
c->sdev = s;
|
|
c->pci = p;
|
|
c->ndrive = s->nunit = nunit;
|
|
i = pcicfgr32(p, Dctl) & ~(7<<12);
|
|
pcicfgw32(p, Dctl, i | 4<<12);
|
|
|
|
print("#S/sd%c: odin ii sata/sas with %d ports\n", s->idno, nunit);
|
|
startup(c);
|
|
forcetype(c);
|
|
for(i = 0; i < nunit; i++){
|
|
d = c->drive + i;
|
|
d->driveno = i;
|
|
d->sectors = 0;
|
|
d->ctlr = c;
|
|
setupcmd(d);
|
|
snprint(d->name, sizeof d->name, "odin%d.%d", nmsctlr, i);
|
|
msdrive[nmsdrive + i] = d;
|
|
// phychk(c, d);
|
|
c->reg[pis[i] + 1] =
|
|
Sync | Phyerr | Stperr | Crcerr |
|
|
Linkrx | Martianfis | Anot | Bist | Sigrx |
|
|
Phyunrdy | Martiantag | Bnot | Comw |
|
|
Portsel | Hreset | Phyidto | Phyidok |
|
|
Hresetok | Phyrdy;
|
|
}
|
|
nmsdrive += nunit;
|
|
nmsctlr++;
|
|
*ll = s;
|
|
ll = &s->next;
|
|
}
|
|
return s0;
|
|
}
|
|
|
|
static char*
|
|
msrctlsata(Drive *d, char *p, char *e)
|
|
{
|
|
p = seprint(p, e, "flag\t");
|
|
p = pflag(p, e, d);
|
|
p = seprint(p, e, "udma\t%d\n", d->udma);
|
|
return p;
|
|
}
|
|
|
|
static char*
|
|
rctldebug(char *p, char *e, Ctlr *c, Drive *d)
|
|
{
|
|
int i;
|
|
uvlong sasid;
|
|
|
|
i = d->driveno;
|
|
p = seprint(p, e, "sstatus\t%.8ux\n", sstatus(d));
|
|
// p = seprint(p, e, "cis\t%.8ux %.8ux\n", c->reg[Cis], c->reg[Cie]);
|
|
// p = seprint(p, e, "gis\t%.8ux\n", c->reg[Gis]);
|
|
p = seprint(p, e, "pis\t%.8ux %.8ux\n", c->reg[pis[i]], c->reg[pis[i] + 1]);
|
|
p = seprint(p, e, "sis\t%.8ux\n", c->reg[Csis]);
|
|
p = seprint(p, e, "cqwp\t%.8ux\n", c->cq[0]);
|
|
p = seprint(p, e, "cerror\t%.8ux %.8ux\n", *(uint*)d->cmd->error, *(uint*)(d->cmd->error+4));
|
|
p = seprint(p, e, "task\t%.8ux\n", gettask(d));
|
|
p = seprint(p, e, "ptype\t%.8ux\n", c->reg[Ptype]);
|
|
p = seprint(p, e, "satactl\t%.8ux\n", pcread(c, i, Psatactl)); /* appears worthless */
|
|
p = seprint(p, e, "info %.8ux %.8ux\n", pcread(c, i, Pinfo), pcread(c, i, Painfo));
|
|
p = seprint(p, e, "physts %.8ux\n", pcread(c, i, Pphysts));
|
|
p = seprint(p, e, "widecfg %.8ux\n", pcread(c, i, Pwidecfg));
|
|
sasid = pcread(c, i, Pwwn + 0);
|
|
sasid |= (uvlong)pcread(c, i, Pwwn + 4)<<32;
|
|
p = seprint(p, e, "wwn %.16llux %.8ux\n", sasid, pcread(c, i, Pwwn + 8));
|
|
sasid = pcread(c, i, Pawwn + 0);
|
|
sasid |= (uvlong)pcread(c, i, Pawwn + 4)<<32;
|
|
p = seprint(p, e, "awwn %.16llux\n", sasid);
|
|
sasid = pcread(c, i, Paddr + 0);
|
|
sasid |= (uvlong)pcread(c, i, Paddr + 4)<<32;
|
|
p = seprint(p, e, "sasid %.16llux\n", sasid);
|
|
return p;
|
|
}
|
|
|
|
static int
|
|
msrctl(SDunit *u, char *p, int l)
|
|
{
|
|
char *e, *op;
|
|
Ctlr *c;
|
|
Drive *d;
|
|
|
|
if((c = u->dev->ctlr) == nil)
|
|
return 0;
|
|
d = c->drive + u->subno;
|
|
e = p + l;
|
|
op = p;
|
|
p = seprint(p, e, "state\t%s\n", dstate(d->state));
|
|
p = seprint(p, e, "type\t%s", type[d->type]);
|
|
if(d->type == Sata)
|
|
p = seprint(p, e, " sig %.8ux", getsig(d));
|
|
p = seprint(p, e, "\n");
|
|
if(d->state == Dready){
|
|
p = seprint(p, e, "model\t%s\n", d->model);
|
|
p = seprint(p, e, "serial\t%s\n", d->serial);
|
|
p = seprint(p, e, "firm\t%s\n", d->firmware);
|
|
p = seprint(p, e, "wwn\t%llux\n", d->wwn);
|
|
p = msrctlsata(d, p, e);
|
|
}
|
|
p = rctldebug(p, e, c, d);
|
|
p = seprint(p, e, "geometry %llud %lud\n", d->sectors, u->secsize);
|
|
return p - op;
|
|
}
|
|
|
|
static void
|
|
forcestate(Drive *d, char *state)
|
|
{
|
|
int i;
|
|
|
|
for(i = 1; i < nelem(diskstates); i++)
|
|
if(strcmp(state, diskstates[i]) == 0)
|
|
break;
|
|
if(i == nelem(diskstates))
|
|
error(Ebadctl);
|
|
ilock(d);
|
|
d->state = 1 << i - 1;
|
|
statechange(d);
|
|
iunlock(d);
|
|
}
|
|
|
|
static int
|
|
mswctl(SDunit *u, Cmdbuf *cmd)
|
|
{
|
|
char **f;
|
|
Ctlr *c;
|
|
Drive *d;
|
|
|
|
c = u->dev->ctlr;
|
|
d = c->drive + u->subno;
|
|
f = cmd->f;
|
|
if(strcmp(f[0], "state") == 0)
|
|
forcestate(d, f[1]? f[1]: "null");
|
|
else
|
|
cmderror(cmd, Ebadctl);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mswtopctl(SDev*, Cmdbuf *cmd)
|
|
{
|
|
char **f;
|
|
int *v;
|
|
|
|
f = cmd->f;
|
|
v = 0;
|
|
if(strcmp(f[0], "debug") == 0)
|
|
v = &debug;
|
|
else if(strcmp(f[0], "idprint") == 0)
|
|
v = &idebug;
|
|
else if(strcmp(f[0], "aprint") == 0)
|
|
v = &adebug;
|
|
else
|
|
cmderror(cmd, Ebadctl);
|
|
if(cmd->nf == 1)
|
|
*v ^= 1;
|
|
else if(cmd->nf == 2)
|
|
*v = strcmp(f[1], "on") == 0;
|
|
else
|
|
cmderror(cmd, Ebadarg);
|
|
return 0;
|
|
}
|
|
|
|
SDifc sdodinifc = {
|
|
"odin",
|
|
mspnp,
|
|
nil,
|
|
msenable,
|
|
msdisable,
|
|
msverify,
|
|
msonline,
|
|
msrio,
|
|
msrctl,
|
|
mswctl,
|
|
scsibio,
|
|
nil, /* probe */
|
|
nil, /* clear */
|
|
nil,
|
|
mswtopctl,
|
|
msataio,
|
|
};
|