2844 lines
52 KiB
C
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,
|
|
};
|