ref: 3fc3cd11595dccd740be41321e1acba802bf047b
parent: 16def74e165576eb0b6771976b87b58bc6953f95
author: joe9 <joe9mail@gmail.com>
date: Wed Jul 14 18:40:13 EDT 2021
compiling 9front sdiahci
--- /dev/null
+++ b/include/fis.h
@@ -1,0 +1,164 @@
+#pragma lib "libfis.a"
+#pragma src "/usr/inferno/libfis"
+
+/* ata errors */
+enum {
+ Emed = 1<<0, /* media error */
+ Enm = 1<<1, /* no media */
+ Eabrt = 1<<2, /* abort */
+ Emcr = 1<<3, /* media change request */
+ Eidnf = 1<<4, /* no user-accessible address */
+ Emc = 1<<5, /* media change */
+ Eunc = 1<<6, /* data error */
+ Ewp = 1<<6, /* write protect */
+ Eicrc = 1<<7, /* interface crc error */
+
+ Efatal = Eidnf|Eicrc, /* must sw reset */
+};
+
+/* ata status */
+enum {
+ ASerr = 1<<0, /* error */
+ ASdrq = 1<<3, /* request */
+ ASdf = 1<<5, /* fault */
+ ASdrdy = 1<<6, /* ready */
+ ASbsy = 1<<7, /* busy */
+
+ ASobs = 1<<1|1<<2|1<<4,
+};
+
+enum {
+ /* fis types */
+ H2dev = 0x27,
+ D2host = 0x34,
+
+ /* fis flags bits */
+ Fiscmd = 0x80,
+
+ /* ata bits */
+ Ataobs = 0xa0,
+ Atalba = 0x40,
+
+ /* nominal fis size (fits any fis) */
+ Fissize = 0x20,
+};
+
+/* sata device-to-host (0x27) fis layout */
+enum {
+ Ftype,
+ Fflags,
+ Fcmd,
+ Ffeat,
+ Flba0,
+ Flba8,
+ Flba16,
+ Fdev,
+ Flba24,
+ Flba32,
+ Flba40,
+ Ffeat8,
+ Fsc,
+ Fsc8,
+ Ficc, /* isochronous cmd completion */
+ Fcontrol,
+};
+
+/* sata host-to-device fis (0x34) differences */
+enum{
+ Fioport = 1,
+ Fstatus,
+ Frerror,
+};
+
+/* ata protcol type */
+enum{
+ Pnd = 0<<0, /* data direction */
+ Pin = 1<<0,
+ Pout = 2<<0,
+ Pdatam = 3<<0,
+
+ Ppio = 1<<2, /* ata protocol */
+ Pdma = 2<<2,
+ Pdmq = 3<<2,
+ Preset = 4<<2,
+ Pdiag = 5<<2,
+ Ppkt = 6<<2,
+ Pprotom = 7<<2,
+
+ P48 = 0<<5, /* command “size” */
+ P28 = 1<<5,
+ Pcmdszm = 1<<5,
+
+ Pssn = 0<<6, /* sector size */
+ P512 = 1<<6,
+ Pssm = 1<<6,
+};
+
+typedef struct Sfis Sfis;
+struct Sfis {
+ ushort feat;
+ uchar udma;
+ uchar speeds;
+ uint sig;
+ uint lsectsz;
+ uint physshift; /* log2(log/phys) */
+ uint physalign; /* location of lba0 within phys0 */
+ uint c; /* disgusting, no? */
+ uint h;
+ uint s;
+};
+
+enum {
+ Dlba = 1<<0, /* required for sata */
+ Dllba = 1<<1,
+ Dsmart = 1<<2,
+ Dpower = 1<<3,
+ Dnop = 1<<4,
+ Datapi = 1<<5,
+ Datapi16= 1<<6,
+ Data8 = 1<<7,
+ Dsct = 1<<8,
+ Dnflag = 9,
+};
+
+enum {
+ Pspinup = 1<<0,
+ Pidready = 1<<1,
+};
+
+void setfissig(Sfis*, uint);
+int txmodefis(Sfis*, uchar*, uchar);
+int atapirwfis(Sfis*, uchar*, uchar*, int, int);
+int featfis(Sfis*, uchar*, uchar);
+int flushcachefis(Sfis*, uchar*);
+int identifyfis(Sfis*, uchar*);
+int nopfis(Sfis*, uchar*, int);
+int rwfis(Sfis*, uchar*, int, int, uvlong);
+void skelfis(uchar*);
+void sigtofis(Sfis*, uchar*);
+uvlong fisrw(Sfis*, uchar*, int*);
+
+void idmove(char*, ushort*, int);
+vlong idfeat(Sfis*, ushort*);
+uvlong idwwn(Sfis*, ushort*);
+int idss(Sfis*, ushort*);
+int idpuis(ushort*);
+ushort id16(ushort*, int);
+uint id32(ushort*, int);
+uvlong id64(ushort*, int);
+char *pflag(char*, char*, Sfis*);
+uint fistosig(uchar*);
+
+/* scsi */
+typedef struct Cfis Cfis;
+struct Cfis {
+ uchar phyid;
+ uchar encid[8];
+ uchar tsasaddr[8];
+ uchar ssasaddr[8];
+ uchar ict[2];
+};
+
+void smpskelframe(Cfis*, uchar*, int);
+uint sashash(uvlong);
+uchar *sasbhash(uchar*, uchar*);
--- /dev/null
+++ b/libfis/fis.c
@@ -1,0 +1,543 @@
+/*
+ * sata fises and sas frames
+ * copyright © 2009-2010 erik quanstrom
+ */
+#include <u.h>
+#include <libc.h>
+#include <fis.h>
+
+static char *flagname[9] = {
+ "lba",
+ "llba",
+ "smart",
+ "power",
+ "nop",
+ "atapi",
+ "atapi16",
+ "ata8",
+ "sct",
+};
+
+/*
+ * ata8 standard (llba) cmd layout
+ *
+ * feature 16 bits
+ * count 16 bits
+ * lba 48 bits
+ * device 8 bits
+ * command 8 bits
+ *
+ * response:
+ *
+ * status 8 bits
+ * error 8 bits
+ * reason 8 bits
+ * count 8 bits
+ * sstatus 8 bits
+ * sactive 8 bits
+*/
+
+/*
+ * sata fis layout for fistype 0x27: host-to-device:
+ *
+ * 0 fistype
+ * 1 fis flags
+ * 2 ata command
+ * 3 features
+ * 4 sector lba low 7:0
+ * 5 cyl low lba mid 15:8
+ * 6 cyl hi lba hi 23:16
+ * 7 device / head
+ * 8 sec exp lba 31:24
+ * 9 cy low e lba 39:32
+ * 10 cy hi e lba 48:40
+ * 11 features (exp)
+ * 12 sector count
+ * 13 sector count (exp)
+ * 14 r
+ * 15 control
+ */
+
+void
+setfissig(Sfis *x, uint sig)
+{
+ x->sig = sig;
+}
+
+void
+skelfis(uchar *c)
+{
+ memset(c, 0, Fissize);
+ c[Ftype] = H2dev;
+ c[Fflags] = Fiscmd;
+ c[Fdev] = Ataobs;
+}
+
+int
+nopfis(Sfis*, uchar *c, int srst)
+{
+ skelfis(c);
+ if(srst){
+ c[Fflags] &= ~Fiscmd;
+ c[Fcontrol] = 1<<2;
+ return Preset|P28;
+ }
+ return Pnd|P28;
+}
+
+int
+txmodefis(Sfis *f, uchar *c, uchar d)
+{
+ int m;
+
+ /* hack */
+ if((f->sig >> 16) == 0xeb14)
+ return -1;
+ m = 0x40;
+ if(d == 0xff){
+ d = 0;
+ m = 0;
+ }
+ skelfis(c);
+ c[Fcmd] = 0xef;
+ c[Ffeat] = 3; /* set transfer mode */
+ c[Fsc] = m | d; /* sector count */
+ return Pnd|P28;
+}
+
+int
+featfis(Sfis*, uchar *c, uchar f)
+{
+ skelfis(c);
+ c[Fcmd] = 0xef;
+ c[Ffeat] = f;
+ return Pnd|P28;
+}
+
+int
+identifyfis(Sfis *f, uchar *c)
+{
+ static uchar tab[] = { 0xec, 0xa1, };
+
+ skelfis(c);
+ c[Fcmd] = tab[f->sig>>16 == 0xeb14];
+ return Pin|Ppio|P28|P512;
+}
+
+int
+flushcachefis(Sfis *f, uchar *c)
+{
+ static uchar tab[2] = {0xe7, 0xea};
+ static uchar ptab[2] = {Pnd|P28, Pnd|P48};
+ int llba;
+
+ llba = (f->feat & Dllba) != 0;
+ skelfis(c);
+ c[Fcmd] = tab[llba];
+ return ptab[llba];
+}
+
+static ushort
+gbit16(void *a)
+{
+ ushort j;
+ uchar *i;
+
+ i = a;
+ j = i[1] << 8;
+ j |= i[0];
+ return j;
+}
+
+static uint
+gbit32(void *a)
+{
+ uint j;
+ uchar *i;
+
+ i = a;
+ j = i[3] << 24;
+ j |= i[2] << 16;
+ j |= i[1] << 8;
+ j |= i[0];
+ return j;
+}
+
+static uvlong
+gbit64(void *a)
+{
+ uchar *i;
+
+ i = a;
+ return (uvlong)gbit32(i+4) << 32 | gbit32(a);
+}
+
+ushort
+id16(ushort *id, int i)
+{
+ return gbit16(id+i);
+}
+
+uint
+id32(ushort *id, int i)
+{
+ return gbit32(id+i);
+}
+
+uvlong
+id64(ushort *id, int i)
+{
+ return gbit64(id+i);
+}
+
+/* acs-2 §7.18.7.4 */
+static ushort puistab[] = {
+ 0x37c8, Pspinup,
+ 0x738c, Pspinup | Pidready,
+ 0x8c73, 0,
+ 0xc837, Pidready,
+};
+
+int
+idpuis(ushort *id)
+{
+ ushort u, i;
+
+ u = gbit16(id + 2);
+ for(i = 0; i < nelem(puistab); i += 2)
+ if(u == puistab[i])
+ return puistab[i + 1];
+ return Pidready; /* annoying cdroms */
+}
+
+static ushort
+onesc(ushort *id)
+{
+ ushort u;
+
+ u = gbit16(id);
+ if(u == 0xffff)
+ u = 0;
+ return u;
+}
+
+enum{
+ Idmasp = 1<<8,
+ Ilbasp = 1<<9,
+ Illba = 1<<10,
+};
+
+vlong
+idfeat(Sfis *f, ushort *id)
+{
+ int i, j;
+ vlong s;
+
+ f->feat = 0;
+ if(f->sig>>16 == 0xeb14)
+ f->feat |= Datapi;
+ i = gbit16(id + 49);
+ if((i & Ilbasp) == 0){
+ if((gbit16(id + 53) & 1) == 0){
+ f->c = gbit16(id + 1);
+ f->h = gbit16(id + 3);
+ f->s = gbit16(id + 6);
+ }else{
+ f->c = gbit16(id + 54);
+ f->h = gbit16(id + 55);
+ f->s = gbit16(id + 56);
+ }
+ s = f->c*f->h*f->s;
+ }else{
+ f->c = f->h = f->s = 0;
+ f->feat |= Dlba;
+ j = gbit16(id + 83) | gbit16(id + 86);
+ if(j & Illba){
+ f->feat |= Dllba;
+ s = gbit64(id + 100);
+ }else
+ s = gbit32(id + 60);
+ }
+ f->udma = 0xff;
+ if(i & Idmasp)
+ if(gbit16(id + 53) & 4)
+ for(i = gbit16(id + 88) & 0x7f; i; i >>= 1)
+ f->udma++;
+
+ if(f->feat & Datapi){
+ i = gbit16(id + 0);
+ if(i & 1)
+ f->feat |= Datapi16;
+ }
+
+ i = gbit16(id+83);
+ if((i>>14) == 1){
+ if(i & (1<<3))
+ f->feat |= Dpower;
+ i = gbit16(id + 82);
+ if(i & 1)
+ f->feat |= Dsmart;
+ if(i & (1<<14))
+ f->feat |= Dnop;
+ }
+ i = onesc(id + 80);
+ if(i & 1<<8){
+ f->feat |= Data8;
+ i = onesc(id + 222); /* sata? */
+ j = onesc(id + 76);
+ if(i != 0 && i >> 12 == 1 && j != 0){
+ j >>= 1;
+ f->speeds = j & 7;
+ /*
+ * not acceptable for comreset to
+ * wipe out device configuration.
+ * reject drive.
+ i = gbit16(id + 78) & gbit16(id + 79);
+ if((i & 1<<6) == 0)
+ return -1;
+ */
+ }
+ }
+ if(gbit16(id + 206) & 1)
+ f->feat |= Dsct;
+ idss(f, id);
+ return s;
+}
+
+int
+idss(Sfis *f, ushort *id)
+{
+ uint sw, i, pa;
+
+ if(f->sig>>16 == 0xeb14)
+ return 0;
+ f->lsectsz = 512;
+ f->physshift = 0;
+ f->physalign = 0;
+ i = gbit16(id + 106);
+ if(i >> 14 != 1)
+ return f->lsectsz;
+ if((i & (1<<12)) && (sw = gbit32(id + 117)) >= 256)
+ f->lsectsz = sw * 2;
+ if(i & 1<<13){
+ f->physshift = i & 7;
+ if((pa = gbit16(id + 209)) & 0x4000)
+ f->physalign = pa & 0x3fff;
+ }
+ return f->lsectsz;
+}
+
+uvlong
+idwwn(Sfis*, ushort *id)
+{
+ uvlong u;
+
+ u = 0;
+ if(id[108]>>12 == 5){
+ u |= (uvlong)gbit16(id + 108) << 48;
+ u |= (uvlong)gbit16(id + 109) << 32;
+ u |= gbit16(id + 110) << 16;
+ u |= gbit16(id + 111) << 0;
+ }
+ return u;
+}
+
+void
+idmove(char *p, ushort *u, int n)
+{
+ int i;
+ char *op, *e, *s;
+
+ op = p;
+ s = (char*)u;
+ for(i = 0; i < n; i += 2){
+ *p++ = s[i + 1];
+ *p++ = s[i + 0];
+ }
+ *p = 0;
+ while(p > op && *--p == ' ')
+ *p = 0;
+ e = p;
+ p = op;
+ while(*p == ' ')
+ p++;
+ memmove(op, p, n - (e - p));
+}
+
+char*
+pflag(char *s, char *e, Sfis *f)
+{
+ ushort i, u;
+
+ u = f->feat;
+ for(i = 0; i < Dnflag; i++)
+ if(u & (1 << i))
+ s = seprint(s, e, "%s ", flagname[i]);
+ return seprint(s, e, "\n");
+}
+
+int
+atapirwfis(Sfis *f, uchar *c, uchar *cdb, int cdblen, int ndata)
+{
+ int fill, len;
+
+ fill = f->feat&Datapi16? 16: 12;
+ if((len = cdblen) > fill)
+ len = fill;
+ memmove(c + 0x40, cdb, len);
+ memset(c + 0x40 + len, 0, fill - len);
+
+ c[Ftype] = H2dev;
+ c[Fflags] = Fiscmd;
+ c[Fcmd] = Ataobs;
+ if(ndata != 0)
+ c[Ffeat] = 1; /* dma */
+ else
+ c[Ffeat] = 0; /* features (exp); */
+ c[Flba0] = 0;
+ c[Flba8] = ndata;
+ c[Flba16] = ndata >> 8;
+ c[Fdev] = Ataobs;
+ memset(c + 8, 0, Fissize - 8);
+ return P28|Ppkt;
+}
+
+int
+rwfis(Sfis *f, uchar *c, int rw, int nsect, uvlong lba)
+{
+ uchar acmd, llba, udma;
+ static uchar tab[2][2][2] = { 0x20, 0x24, 0x30, 0x34, 0xc8, 0x25, 0xca, 0x35, };
+ static uchar ptab[2][2][2] = {
+ Pin|Ppio|P28, Pin|Ppio|P48,
+ Pout|Ppio|P28, Pout|Ppio|P48,
+ Pin|Pdma|P28, Pin|Pdma|P48,
+ Pout|Pdma|P28, Pout|Pdma|P48,
+ };
+
+ udma = f->udma != 0xff;
+ llba = (f->feat & Dllba) != 0;
+ acmd = tab[udma][rw][llba];
+
+ c[Ftype] = 0x27;
+ c[Fflags] = 0x80;
+ c[Fcmd] = acmd;
+ c[Ffeat] = 0;
+
+ c[Flba0] = lba;
+ c[Flba8] = lba >> 8;
+ c[Flba16] = lba >> 16;
+ c[Fdev] = Ataobs | Atalba;
+ if(llba == 0)
+ c[Fdev] |= (lba>>24) & 0xf;
+
+ c[Flba24] = lba >> 24;
+ c[Flba32] = lba >> 32;
+ c[Flba40] = lba >> 40;
+ c[Ffeat8] = 0;
+
+ c[Fsc] = nsect;
+ c[Fsc8] = nsect >> 8;
+ c[Ficc] = 0;
+ c[Fcontrol] = 0;
+
+ memset(c + 16, 0, Fissize - 16);
+ return ptab[udma][rw][llba];
+}
+
+uvlong
+fisrw(Sfis *, uchar *c, int *n)
+{
+ uvlong lba;
+
+ lba = c[Flba0];
+ lba |= c[Flba8] << 8;
+ lba |= c[Flba16] << 16;
+ lba |= c[Flba24] << 24;
+ lba |= (uvlong)(c[Flba32] | c[Flba40]<<8) << 32;
+
+ *n = c[Fsc];
+ *n |= c[Fsc8] << 8;
+
+ return lba;
+}
+
+void
+sigtofis(Sfis *f, uchar *c)
+{
+ uint u;
+
+ u = f->sig;
+ memset(c, 0, Fissize);
+ c[Ftype] = 0x34;
+ c[Fflags] = 0x00;
+ c[Fcmd] = 0x50;
+ c[Ffeat] = 0x01;
+ c[Flba0] = u >> 8;
+ c[Flba8] = u >> 16;
+ c[Flba16] = u >> 24;
+ c[Fdev] = Ataobs;
+ c[Fsc] = u;
+}
+
+uint
+fistosig(uchar *u)
+{
+ return u[Fsc] | u[Flba0]<<8 | u[Flba8]<<16 | u[Flba16]<<24;
+}
+
+
+/* sas smp */
+void
+smpskelframe(Cfis *f, uchar *c, int m)
+{
+ memset(c, 0, Fissize);
+ c[Ftype] = 0x40;
+ c[Fflags] = m;
+ if(f->phyid)
+ c[Flba32] = f->phyid;
+}
+
+uint
+sashash(uvlong u)
+{
+ uint poly, msb, l, r;
+ uvlong m;
+
+ r = 0;
+ poly = 0x01db2777;
+ msb = 0x01000000;
+ for(m = 1ull<<63; m > 0; m >>= 1){
+ l = 0;
+ if(m & u)
+ l = msb;
+ r <<= 1;
+ r ^= l;
+ if(r & msb)
+ r ^= poly;
+ }
+ return r & 0xffffff;
+}
+
+uchar*
+sasbhash(uchar *t, uchar *s)
+{
+ uint poly, msb, l, r, i, j;
+
+ r = 0;
+ poly = 0x01db2777;
+ msb = 0x01000000;
+ for(i = 0; i < 8; i++)
+ for(j = 0x80; j != 0; j >>= 1){
+ l = 0;
+ if(s[i] & j)
+ l = msb;
+ r <<= 1;
+ r ^= l;
+ if(r & msb)
+ r ^= poly;
+ }
+ t[0] = r>>16;
+ t[1] = r>>8;
+ t[2] = r;
+ return t;
+}
--- /dev/null
+++ b/libfis/mkfile
@@ -1,0 +1,9 @@
+<../mkconfig
+
+LIB=libfis.a
+
+OFILES=fis.$O
+
+HFILES=$ROOT/include/fis.h
+
+<$ROOT/mkfiles/mksyslib-$SHELLTYPE
--- /dev/null
+++ b/os/pc/ahci.h
@@ -1,0 +1,334 @@
+/*
+ * advanced host controller interface (sata)
+ * © 2007-9 coraid, inc
+ */
+
+/* pci configuration */
+enum {
+ Abar = 5,
+};
+
+/*
+ * ahci memory configuration
+ *
+ * 0000-0023 generic host control
+ * 0024-009f reserved
+ * 00a0-00ff vendor specific.
+ * 0100-017f port 0
+ * ...
+ * 1080-1100 port 31
+ */
+
+/* cap bits: supported features */
+enum {
+ H64a = 1<<31, /* 64-bit addressing */
+ Hncq = 1<<30, /* ncq */
+ Hsntf = 1<<29, /* snotification reg. */
+ Hmps = 1<<28, /* mech pres switch */
+ Hss = 1<<27, /* staggered spinup */
+ Halp = 1<<26, /* aggressive link pm */
+ Hal = 1<<25, /* activity led */
+ Hclo = 1<<24, /* command-list override */
+ Hiss = 1<<20, /* for interface speed */
+ Ham = 1<<18, /* ahci-mode only */
+ Hpm = 1<<17, /* port multiplier */
+ Hfbs = 1<<16, /* fis-based switching */
+ Hpmb = 1<<15, /* multiple-block pio */
+ Hssc = 1<<14, /* slumber state */
+ Hpsc = 1<<13, /* partial-slumber state */
+ Hncs = 1<<8, /* n command slots */
+ Hcccs = 1<<7, /* coal */
+ Hems = 1<<6, /* enclosure mgmt. */
+ Hxs = 1<<5, /* external sata */
+ Hnp = 1<<0, /* n ports */
+};
+
+/* ghc bits */
+enum {
+ Hae = 1<<31, /* enable ahci */
+ Hie = 1<<1, /* " interrupts */
+ Hhr = 1<<0, /* hba reset */
+};
+
+/* cap2 bits */
+enum {
+ Apts = 1<<2, /* automatic partial to slumber */
+ Nvmp = 1<<1, /* nvmhci present; nvram */
+ Boh = 1<<0, /* bios/os handoff supported */
+};
+
+/* bios bits */
+enum {
+ Bos = 1<<0,
+ Oos = 1<<1,
+};
+
+/* emctl bits */
+enum {
+ Pm = 1<<27, /* port multiplier support */
+ Alhd = 1<<26, /* activity led hardware driven */
+ Xonly = 1<<25, /* rx messages not supported */
+ Smb = 1<<24, /* single msg buffer; rx limited */
+ Esgpio = 1<<19, /* sgpio messages supported */
+ Eses2 = 1<<18, /* ses-2 supported */
+ Esafte = 1<<17, /* saf-te supported */
+ Elmt = 1<<16, /* led msg types support */
+ Emrst = 1<<9, /* reset all em logic */
+ Tmsg = 1<<8, /* transmit message */
+ Mr = 1<<0, /* message rx'd */
+ Emtype = Esgpio | Eses2 | Esafte | Elmt,
+};
+
+typedef struct {
+ ulong cap;
+ ulong ghc;
+ ulong isr;
+ ulong pi; /* ports implemented */
+ ulong ver;
+ ulong ccc; /* coaleasing control */
+ ulong cccports;
+ ulong emloc;
+ ulong emctl;
+ ulong cap2;
+ ulong bios;
+} Ahba;
+
+enum {
+ Acpds = 1<<31, /* cold port detect status */
+ Atfes = 1<<30, /* task file error status */
+ Ahbfs = 1<<29, /* hba fatal */
+ Ahbds = 1<<28, /* hba error (parity error) */
+ Aifs = 1<<27, /* interface fatal §6.1.2 */
+ Ainfs = 1<<26, /* interface error (recovered) */
+ Aofs = 1<<24, /* too many bytes from disk */
+ Aipms = 1<<23, /* incorrect prt mul status */
+ Aprcs = 1<<22, /* PhyRdy change status Pxserr.diag.n */
+ Adpms = 1<<7, /* mechanical presence status */
+ Apcs = 1<<6, /* port connect diag.x */
+ Adps = 1<<5, /* descriptor processed */
+ Aufs = 1<<4, /* unknown fis diag.f */
+ Asdbs = 1<<3, /* set device bits fis received w/ i bit set */
+ Adss = 1<<2, /* dma setup */
+ Apio = 1<<1, /* pio setup fis */
+ Adhrs = 1<<0, /* device to host register fis */
+
+ IEM = Acpds|Atfes|Ahbds|Ahbfs|Ahbds|Aifs|Ainfs|Aprcs|Apcs|Adps|
+ Aufs|Asdbs|Adss|Adhrs,
+ Ifatal = Ahbfs|Ahbds|Aifs,
+};
+
+/* serror bits */
+enum {
+ SerrX = 1<<26, /* exchanged */
+ SerrF = 1<<25, /* unknown fis */
+ SerrT = 1<<24, /* transition error */
+ SerrS = 1<<23, /* link sequence */
+ SerrH = 1<<22, /* handshake */
+ SerrC = 1<<21, /* crc */
+ SerrD = 1<<20, /* not used by ahci */
+ SerrB = 1<<19, /* 10-tp-8 decode */
+ SerrW = 1<<18, /* comm wake */
+ SerrI = 1<<17, /* phy internal */
+ SerrN = 1<<16, /* phyrdy change */
+
+ ErrE = 1<<11, /* internal */
+ ErrP = 1<<10, /* ata protocol violation */
+ ErrC = 1<<9, /* communication */
+ ErrT = 1<<8, /* transient */
+ ErrM = 1<<1, /* recoverd comm */
+ ErrI = 1<<0, /* recovered data integrety */
+
+ ErrAll = ErrE|ErrP|ErrC|ErrT|ErrM|ErrI,
+ SerrAll = SerrX|SerrF|SerrT|SerrS|SerrH|SerrC|SerrD|SerrB|SerrW|
+ SerrI|SerrN|ErrAll,
+ SerrBad = 0x7f<<19,
+};
+
+/* cmd register bits */
+enum {
+ Aicc = 1<<28, /* interface communcations control. 4 bits */
+ Aasp = 1<<27, /* aggressive slumber & partial sleep */
+ Aalpe = 1<<26, /* aggressive link pm enable */
+ Adlae = 1<<25, /* drive led on atapi */
+ Aatapi = 1<<24, /* device is atapi */
+ Apste = 1<<23, /* automatic slumber to partial cap */
+ Afbsc = 1<<22, /* fis-based switching capable */
+ Aesp = 1<<21, /* external sata port */
+ Acpd = 1<<20, /* cold presence detect */
+ Ampsp = 1<<19, /* mechanical pres. */
+ Ahpcp = 1<<18, /* hot plug capable */
+ Apma = 1<<17, /* pm attached */
+ Acps = 1<<16, /* cold presence state */
+ Acr = 1<<15, /* cmdlist running */
+ Afr = 1<<14, /* fis running */
+ Ampss = 1<<13, /* mechanical presence switch state */
+ Accs = 1<<8, /* current command slot 12:08 */
+ Afre = 1<<4, /* fis enable receive */
+ Aclo = 1<<3, /* command list override */
+ Apod = 1<<2, /* power on dev (requires cold-pres. detect) */
+ Asud = 1<<1, /* spin-up device; requires ss capability */
+ Ast = 1<<0, /* start */
+
+ Arun = Ast|Acr|Afre|Afr,
+ Apwr = Apod|Asud,
+};
+
+/* ctl register bits */
+enum {
+ Aipm = 1<<8, /* interface power mgmt. 3=off */
+ Aspd = 1<<4,
+ Adet = 1<<0, /* device detection */
+};
+
+/* sstatus register bits */
+enum{
+ /* sstatus det */
+ Smissing = 0<<0,
+ Spresent = 1<<0,
+ Sphylink = 3<<0,
+ Sbist = 4<<0,
+ Smask = 7<<0,
+
+ /* sstatus speed */
+ Gmissing = 0<<4,
+ Gi = 1<<4,
+ Gii = 2<<4,
+ Giii = 3<<4,
+ Gmask = 7<<4,
+
+ /* sstatus ipm */
+ Imissing = 0<<8,
+ Iactive = 1<<8,
+ Isleepy = 2<<8,
+ Islumber = 6<<8,
+ Imask = 7<<8,
+
+ SImask = Smask | Imask,
+ SSmask = Smask | Isleepy,
+};
+
+#define sstatus scr0
+#define sctl scr2
+#define serror scr1
+#define sactive scr3
+#define ntf scr4
+
+typedef struct {
+ ulong list; /* PxCLB must be 1kb aligned */
+ ulong listhi;
+ ulong fis; /* 256-byte aligned */
+ ulong fishi;
+ ulong isr;
+ ulong ie; /* interrupt enable */
+ ulong cmd;
+ ulong res1;
+ ulong task;
+ ulong sig;
+ ulong scr0;
+ ulong scr2;
+ ulong scr1;
+ ulong scr3;
+ ulong ci; /* command issue */
+ ulong scr4;
+ ulong fbs;
+ ulong res2[11];
+ ulong vendor[4];
+} Aport;
+
+/* in host's memory; not memory mapped */
+typedef struct {
+ uchar *base;
+ uchar *d;
+ uchar *p;
+ uchar *r;
+ uchar *u;
+ ulong *devicebits;
+} Afis;
+
+enum {
+ Lprdtl = 1<<16, /* physical region descriptor table len */
+ Lpmp = 1<<12, /* port multiplier port */
+ Lclear = 1<<10, /* clear busy on R_OK */
+ Lbist = 1<<9,
+ Lreset = 1<<8,
+ Lpref = 1<<7, /* prefetchable */
+ Lwrite = 1<<6,
+ Latapi = 1<<5,
+ Lcfl = 1<<0, /* command fis length in double words */
+};
+
+/* in hosts memory; memory mapped */
+typedef struct {
+ ulong flags;
+ ulong len;
+ ulong ctab;
+ ulong ctabhi;
+ uchar reserved[16];
+} Alist;
+
+typedef struct {
+ ulong dba;
+ ulong dbahi;
+ ulong pad;
+ ulong count;
+} Aprdt;
+
+typedef struct {
+ uchar cfis[0x40];
+ uchar atapi[0x10];
+ uchar pad[0x30];
+ Aprdt prdt;
+} Actab;
+
+/* enclosure message header */
+enum {
+ Mled = 0,
+ Msafte = 1,
+ Mses2 = 2,
+ Msgpio = 3,
+};
+
+typedef struct {
+ uchar dummy;
+ uchar msize;
+ uchar dsize;
+ uchar type;
+ uchar hba; /* bits 0:4 are the port */
+ uchar pm;
+ uchar led[2];
+} Aledmsg;
+
+enum {
+ Aled = 1<<0,
+ Locled = 1<<3,
+ Errled = 1<<6,
+
+ Ledoff = 0,
+ Ledon = 1,
+};
+
+typedef struct {
+ uint encsz;
+ ulong *enctx;
+ ulong *encrx;
+} Aenc;
+
+enum {
+ Ferror = 1,
+ Fdone = 2,
+};
+
+typedef struct {
+ QLock;
+ Rendez;
+ uchar flag;
+ Sfis;
+ Afis fis;
+ Alist *list;
+ Actab *ctab;
+} Aportm;
+
+typedef struct {
+ Aport *p;
+ Aportm *m;
+} Aportc;
--- /dev/null
+++ b/os/pc/sdiahci.c
@@ -1,0 +1,2537 @@
+/*
+ * intel/amd ahci sata controller
+ * copyright © 2007-10 coraid, inc.
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "../port/error.h"
+#include "../port/sd.h"
+#include <fis.h>
+#include "ahci.h"
+#include "../port/led.h"
+
+#pragma varargck type "T" int
+#define dprint(...) if(debug) print(__VA_ARGS__); else USED(debug)
+#define idprint(...) if(prid) print(__VA_ARGS__); else USED(prid)
+#define aprint(...) if(datapi) print(__VA_ARGS__); else USED(datapi)
+#define ledprint(...) if(dled) print(__VA_ARGS__); else USED(dled)
+#define Tname(c) tname[(c)->type]
+#define Ticks MACHP(0)->ticks
+
+enum {
+ NCtlr = 4,
+ NCtlrdrv= 32,
+ NDrive = NCtlr*NCtlrdrv,
+
+ Fahdrs = 4,
+
+ Read = 0,
+ Write,
+
+ Eesb = 1<<0, /* must have (Eesb & Emtype) == 0 */
+};
+
+/* pci space configuration */
+enum {
+ Pmap = 0x90,
+ Ppcs = 0x91,
+ Prev = 0xa8,
+};
+
+enum {
+ Tesb,
+ Tich,
+ Tsb600,
+ Tjmicron,
+ Tahci,
+};
+
+static char *tname[] = {
+ "63xxesb",
+ "ich",
+ "sb600",
+ "jmicron",
+ "ahci",
+};
+
+enum {
+ Dnull,
+ Dmissing,
+ Dnew,
+ Dready,
+ Derror,
+ Dreset,
+ Doffline,
+ Dportreset,
+ Dlast,
+};
+
+static char *diskstates[Dlast] = {
+ "null",
+ "missing",
+ "new",
+ "ready",
+ "error",
+ "reset",
+ "offline",
+ "portreset",
+};
+
+extern SDifc sdiahciifc;
+typedef struct Ctlr Ctlr;
+
+enum {
+ DMautoneg,
+ DMsatai,
+ DMsataii,
+ DMsataiii,
+ DMlast,
+};
+
+static char *modes[DMlast] = {
+ "auto",
+ "satai",
+ "sataii",
+ "sataiii",
+};
+
+typedef struct Htab Htab;
+struct Htab {
+ ulong bit;
+ char *name;
+};
+
+typedef struct {
+ Lock;
+
+ Ctlr *ctlr;
+ SDunit *unit;
+ char name[10];
+ Aport *port;
+ Aportm portm;
+ Aportc portc; /* redundant ptr to port and portm. */
+ Ledport;
+
+ uchar drivechange;
+ uchar nodma;
+ uchar state;
+
+ uvlong sectors;
+ uint secsize;
+ ulong totick;
+ ulong lastseen;
+ uint wait;
+ uchar mode;
+ uchar active;
+
+ char serial[20+1];
+ char firmware[8+1];
+ char model[40+1];
+ uvlong wwn;
+
+ ushort info[0x200];
+
+ /*
+ * ahci allows non-sequential ports.
+ * to avoid this hassle, we let
+ * driveno ctlr*NCtlrdrv + unit
+ * portno nth available port
+ */
+ uint driveno;
+ uint portno;
+} Drive;
+
+struct Ctlr {
+ Lock;
+
+ int type;
+ int enabled;
+ SDev *sdev;
+ Pcidev *pci;
+
+ uchar *mmio;
+ ulong *lmmio;
+ Ahba *hba;
+ Aenc;
+ uint enctype;
+
+ Drive rawdrive[NCtlrdrv];
+ Drive* drive[NCtlrdrv];
+ int ndrive;
+
+ uint missirq;
+};
+
+static Ctlr iactlr[NCtlr];
+static SDev sdevs[NCtlr];
+static int niactlr;
+
+static Drive *iadrive[NDrive];
+static int niadrive;
+
+static int debug;
+static int prid = 1;
+static int datapi;
+static int dled;
+
+static char stab[] = {
+[0] 'i', 'm',
+[8] 't', 'c', 'p', 'e',
+[16] 'N', 'I', 'W', 'B', 'D', 'C', 'H', 'S', 'T', 'F', 'X'
+};
+
+static void
+serrstr(ulong r, char *s, char *e)
+{
+ int i;
+
+ e -= 3;
+ for(i = 0; i < nelem(stab) && s < e; i++)
+ if(r & (1<<i) && stab[i]){
+ *s++ = stab[i];
+ if(SerrBad & (1<<i))
+ *s++ = '*';
+ }
+ *s = 0;
+}
+
+static char ntab[] = "0123456789abcdef";
+
+static void
+preg(uchar *reg, int n)
+{
+ char buf[25*3+1], *e;
+ int i;
+
+ e = buf;
+ for(i = 0; i < n; i++){
+ *e++ = ntab[reg[i] >> 4];
+ *e++ = ntab[reg[i] & 0xf];
+ *e++ = ' ';
+ }
+ *e++ = '\n';
+ *e = 0;
+ dprint(buf);
+}
+
+static void
+dreg(char *s, Aport *p)
+{
+ dprint("%stask=%lux; cmd=%lux; ci=%lux; is=%lux\n",
+ s, p->task, p->cmd, p->ci, p->isr);
+}
+
+static void
+esleep(int ms)
+{
+ if(waserror())
+ return;
+ tsleep(&up->sleep, return0, 0, ms);
+ poperror();
+}
+
+typedef struct {
+ Aport *p;
+ int i;
+} Asleep;
+
+static int
+ahciclear(void *v)
+{
+ Asleep *s;
+
+ s = v;
+ return (s->p->ci & s->i) == 0;
+}
+
+static void
+aesleep(Aportm *m, Asleep *a, int ms)
+{
+ if(waserror())
+ return;
+ tsleep(m, ahciclear, a, ms);
+ poperror();
+}
+
+static int
+ahciwait(Aportc *c, int ms)
+{
+ Aport *p;
+ Asleep as;
+
+ p = c->p;
+ p->ci = 1;
+ as.p = p;
+ as.i = 1;
+ aesleep(c->m, &as, ms);
+ if((p->task & 1) == 0 && p->ci == 0)
+ return 0;
+ dreg("ahciwait fail/timeout ", c->p);
+ return -1;
+}
+
+static Alist*
+mkalist(Aportm *m, uint flags, uchar *data, int len)
+{
+ Actab *t;
+ Alist *l;
+ Aprdt *p;
+ uvlong pa;
+
+ t = m->ctab;
+ if(data && len > 0){
+ pa = PCIWADDR(data);
+ p = &t->prdt;
+ p->dba = pa;
+ p->dbahi = pa>>32;
+ p->count = 1<<31 | len - 2 | 1;
+ flags |= 1<<16;
+ }
+ pa = PCIWADDR(t);
+ l = m->list;
+ l->flags = flags | 0x5;
+ l->len = 0;
+ l->ctab = pa;
+ l->ctabhi = pa>>32;
+ return l;
+}
+
+static int
+nop(Aportc *pc)
+{
+ uchar *c;
+
+ if((pc->m->feat & Dnop) == 0)
+ return -1;
+ c = pc->m->ctab->cfis;
+ nopfis(pc->m, c, 0);
+ mkalist(pc->m, Lwrite, 0, 0);
+ return ahciwait(pc, 3*1000);
+}
+
+static int
+setfeatures(Aportc *pc, uchar f, uint w)
+{
+ uchar *c;
+
+ c = pc->m->ctab->cfis;
+ featfis(pc->m, c, f);
+ mkalist(pc->m, Lwrite, 0, 0);
+ return ahciwait(pc, w);
+}
+
+/*
+ * 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(Aportc *pc, uchar f)
+{
+ uchar *c;
+
+ c = pc->m->ctab->cfis;
+ if(txmodefis(pc->m, c, f) == -1)
+ return 0;
+ mkalist(pc->m, Lwrite, 0, 0);
+ return ahciwait(pc, 3*1000);
+}
+
+static void
+asleep(int ms)
+{
+ if(up == nil || !islo())
+ delay(ms);
+ else
+ esleep(ms);
+}
+
+static void
+ahciportreset(Aportc *c, uint mode)
+{
+ ulong *cmd, i;
+ Aport *p;
+
+ p = c->p;
+ cmd = &p->cmd;
+ *cmd &= ~(Afre|Ast);
+ for(i = 0; i < 500; i += 25){
+ if((*cmd & Acr) == 0)
+ break;
+ asleep(25);
+ }
+ if((*cmd & Apwr) != Apwr)
+ *cmd |= Apwr;
+ p->sctl = 3*Aipm | 0*Aspd | Adet;
+ delay(1);
+ p->sctl = 3*Aipm | mode*Aspd;
+}
+
+static int
+ahciflushcache(Aportc *pc)
+{
+ uchar *c;
+
+ c = pc->m->ctab->cfis;
+ flushcachefis(pc->m, c);
+ mkalist(pc->m, Lwrite, 0, 0);
+
+ if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){
+ dprint("ahciflushcache fail [task %lux]\n", pc->p->task);
+// preg(pc->m->fis.r, 20);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+ahciidentify0(Aportc *pc, void *id)
+{
+ uchar *c;
+ Actab *t;
+
+ t = pc->m->ctab;
+ c = t->cfis;
+ memset(id, 0, 0x200);
+ identifyfis(pc->m, c);
+ mkalist(pc->m, 0, id, 0x200);
+ return ahciwait(pc, 3*1000);
+}
+
+static vlong
+ahciidentify(Aportc *pc, ushort *id, uint *ss, char *d)
+{
+ int i, n;
+ vlong s;
+ Aportm *m;
+
+ m = pc->m;
+ for(i = 0;; i++){
+ if(i > 5 || ahciidentify0(pc, id) != 0)
+ return -1;
+ n = idpuis(id);
+ if(n & Pspinup && setfeatures(pc, 7, 20*1000) == -1)
+ print("%s: puis spinup fail\n", d);
+ if(n & Pidready)
+ break;
+ print("%s: puis waiting\n", d);
+ }
+ s = idfeat(m, id);
+ *ss = idss(m, id);
+ if(s == -1 || (m->feat&Dlba) == 0){
+ if((m->feat&Dlba) == 0)
+ dprint("%s: no lba support\n", d);
+ return -1;
+ }
+ return s;
+}
+
+static int
+ahciquiet(Aport *a)
+{
+ ulong *p, i;
+
+ p = &a->cmd;
+ *p &= ~Ast;
+ for(i = 0; i < 500; i += 50){
+ if((*p & Acr) == 0)
+ goto stop;
+ asleep(50);
+ }
+ return -1;
+stop:
+ if((a->task & (ASdrq|ASbsy)) == 0){
+ *p |= Ast;
+ return 0;
+ }
+
+ *p |= Aclo;
+ for(i = 0; i < 500; i += 50){
+ if((*p & Aclo) == 0)
+ goto stop1;
+ asleep(50);
+ }
+ return -1;
+stop1:
+ /* extra check */
+ dprint("ahci: clo clear [task %lux]\n", a->task);
+ if(a->task & ASbsy)
+ return -1;
+ *p |= Afre | Ast;
+ return 0;
+}
+
+static int
+ahcicomreset(Aportc *pc)
+{
+ uchar *c;
+
+ dreg("comreset ", pc->p);
+ if(ahciquiet(pc->p) == -1){
+ dprint("ahci: ahciquiet failed\n");
+ return -1;
+ }
+ dreg("comreset ", pc->p);
+
+ c = pc->m->ctab->cfis;
+ nopfis(pc->m, c, 1);
+ mkalist(pc->m, Lclear | Lreset, 0, 0);
+ if(ahciwait(pc, 500) == -1){
+ dprint("ahci: comreset1 failed\n");
+ return -1;
+ }
+ microdelay(250);
+ dreg("comreset ", pc->p);
+
+ nopfis(pc->m, c, 0);
+ mkalist(pc->m, Lwrite, 0, 0);
+ if(ahciwait(pc, 150) == -1){
+ dprint("ahci: comreset2 failed\n");
+ return -1;
+ }
+ dreg("comreset ", pc->p);
+ return 0;
+}
+
+static int
+ahciidle(Aport *port)
+{
+ ulong *p, i, r;
+
+ p = &port->cmd;
+ if((*p & Arun) == 0)
+ return 0;
+ *p &= ~Ast;
+ r = 0;
+ for(i = 0; i < 500; i += 25){
+ if((*p & Acr) == 0)
+ goto stop;
+ asleep(25);
+ }
+ r = -1;
+stop:
+ if((*p & Afre) == 0)
+ return r;
+ *p &= ~Afre;
+ for(i = 0; i < 500; i += 25){
+ if((*p & Afre) == 0)
+ return 0;
+ asleep(25);
+ }
+ return -1;
+}
+
+/*
+ * §6.2.2.1 first part; comreset handled by reset disk.
+ * - remainder is handled by configdisk.
+ * - ahcirecover is a quick recovery from a failed command.
+ */
+static int
+ahciswreset(Aportc *pc)
+{
+ int i;
+
+ i = ahciidle(pc->p);
+ pc->p->cmd |= Afre;
+ if(i == -1)
+ return -1;
+ if(pc->p->task & (ASdrq|ASbsy))
+ return -1;
+ return 0;
+}
+
+static int
+ahcirecover(Aportc *pc)
+{
+ ahciswreset(pc);
+ pc->p->cmd |= Ast;
+ if(settxmode(pc, pc->m->udma) == -1)
+ return -1;
+ return 0;
+}
+
+static void*
+malign(int size, int align)
+{
+ void *v;
+
+ v = xspanalloc(size, align, 0);
+ memset(v, 0, size);
+ return v;
+}
+
+static void
+setupfis(Afis *f)
+{
+ f->base = malign(0x100, 0x100);
+ f->d = f->base + 0;
+ f->p = f->base + 0x20;
+ f->r = f->base + 0x40;
+ f->u = f->base + 0x60;
+ f->devicebits = (ulong*)(f->base + 0x58);
+}
+
+static void
+ahciwakeup(Aportc *c, uint mode)
+{
+ ushort s;
+ Aport *p;
+
+ p = c->p;
+ s = p->sstatus;
+ if((s & Isleepy) == 0)
+ return;
+ if((s & Smask) != Spresent){
+ dprint("ahci: slumbering drive missing [ss %.3ux]\n", s);
+ return;
+ }
+ ahciportreset(c, mode);
+ dprint("ahci: wake %.3ux -> %.3lux\n", s, c->p->sstatus);
+}
+
+static int
+ahciconfigdrive(Ahba *h, Aportc *c, int mode)
+{
+ uvlong pa;
+ Aportm *m;
+ Aport *p;
+ int i;
+
+ p = c->p;
+ m = c->m;
+
+ if(m->list == 0){
+ setupfis(&m->fis);
+ m->list = malign(sizeof *m->list, 1024);
+ m->ctab = malign(sizeof *m->ctab, 128);
+ }
+
+ if(ahciidle(p) == -1){
+ dprint("ahci: port not idle\n");
+ return -1;
+ }
+
+ pa = PCIWADDR(m->list);
+ p->list = pa;
+ p->listhi = pa>>32;
+ pa = PCIWADDR(m->fis.base);
+ p->fis = pa;
+ p->fishi = pa>>32;
+
+ p->cmd |= Afre;
+
+ if((p->cmd & Apwr) != Apwr)
+ p->cmd |= Apwr;
+
+ if((h->cap & Hss) != 0){
+ dprint("ahci: spin up ... [%.3lux]\n", p->sstatus);
+ for(i = 0; i < 1400; i += 50){
+ if((p->sstatus & Sbist) != 0)
+ break;
+ if((p->sstatus & Smask) == Sphylink)
+ break;
+ asleep(50);
+ }
+ }
+
+ if((p->sstatus & SSmask) == (Isleepy | Spresent))
+ ahciwakeup(c, mode);
+
+ p->serror = SerrAll;
+ p->ie = IEM;
+
+ /* we will get called again once phylink has been established */
+ if((p->sstatus & Smask) != Sphylink)
+ return 0;
+
+ /* disable power managment sequence from book. */
+ p->sctl = 3*Aipm | mode*Aspd | 0*Adet;
+ p->cmd &= ~Aalpe;
+
+ p->cmd |= Afre | Ast;
+
+ return 0;
+}
+
+static int
+ahcienable(Ahba *h)
+{
+ h->ghc |= Hie;
+ return 0;
+}
+
+static int
+ahcidisable(Ahba *h)
+{
+ h->ghc &= ~Hie;
+ return 0;
+}
+
+static int
+countbits(ulong u)
+{
+ int i, n;
+
+ n = 0;
+ for(i = 0; i < 32; i++)
+ if(u & (1<<i))
+ n++;
+ return n;
+}
+
+static int
+ahciconf(Ctlr *c)
+{
+ uint u;
+ Ahba *h;
+
+ h = c->hba = (Ahba*)c->mmio;
+ u = h->cap;
+
+ if((u & Ham) == 0)
+ h->ghc |= Hae;
+
+ return countbits(h->pi);
+}
+
+static int
+ahcihandoff(Ahba *h)
+{
+ int wait;
+
+ if((h->cap2 & Boh) == 0)
+ return 0;
+ h->bios |= Oos;
+ for(wait = 0; wait < 2000; wait += 100){
+ if((h->bios & Bos) == 0)
+ return 0;
+ delay(100);
+ }
+ iprint("ahci: bios handoff timed out\n");
+ return -1;
+}
+
+static int
+ahcihbareset(Ahba *h)
+{
+ int wait;
+
+ h->ghc |= Hhr;
+ for(wait = 0; wait < 1000; wait += 100){
+ if((h->ghc & Hhr) == 0)
+ return 0;
+ delay(100);
+ }
+ return -1;
+}
+
+static char*
+dnam(Drive *d)
+{
+ char *s;
+
+ s = d->name;
+ if(d->unit && d->unit->name)
+ s = d->unit->name;
+ return s;
+}
+
+static int
+identify(Drive *d)
+{
+ uchar oserial[21];
+ ushort *id;
+ vlong osectors, s;
+ SDunit *u;
+
+ id = d->info;
+ s = ahciidentify(&d->portc, id, &d->secsize, dnam(d));
+ if(s == -1)
+ return -1;
+ osectors = d->sectors;
+ memmove(oserial, d->serial, sizeof d->serial);
+
+ d->sectors = s;
+
+ idmove(d->serial, id+10, 20);
+ idmove(d->firmware, id+23, 8);
+ idmove(d->model, id+27, 40);
+ d->wwn = idwwn(d->portc.m, 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;
+ d->nodma = 0;
+ u->sectors = 0;
+ }
+ return 0;
+}
+
+static void
+clearci(Aport *p)
+{
+ if(p->cmd & Ast){
+ p->cmd &= ~Ast;
+ p->cmd |= Ast;
+ }
+}
+
+static int
+ignoreahdrs(Drive *d)
+{
+ return d->portm.feat & Datapi && d->ctlr->type == Tsb600;
+}
+
+static void
+updatedrive(Drive *d)
+{
+ ulong f, cause, serr, s0, pr, ewake;
+ Aport *p;
+ static ulong last;
+
+ pr = 1;
+ ewake = 0;
+ f = 0;
+ p = d->port;
+ cause = p->isr;
+ if(d->ctlr->type == Tjmicron)
+ cause &= ~Aifs;
+ serr = p->serror;
+ p->isr = cause;
+
+ if(p->ci == 0){
+ f |= Fdone;
+ pr = 0;
+ }else if(cause & Adps){
+ pr = 0;
+ }else if(cause & Atfes){
+ f |= Ferror;
+ ewake = 1;
+ pr = 0;
+ }
+ if(cause & Ifatal){
+ ewake = 1;
+ dprint("%s: fatal\n", dnam(d));
+ }
+ if(cause & Adhrs){
+ if(p->task & 33){
+ if(ignoreahdrs(d) && serr & ErrE)
+ f |= Fahdrs;
+ dprint("%s: Adhrs cause %lux serr %lux task %lux\n",
+ dnam(d), cause, serr, p->task);
+ f |= Ferror;
+ ewake = 1;
+ }
+ pr = 0;
+ }
+ if(p->task & 1 && last != cause)
+ dprint("%s: err ca %lux serr %lux task %lux sstat %.3lux\n",
+ dnam(d), cause, serr, p->task, p->sstatus);
+ if(pr)
+ dprint("%s: upd %lux ta %lux\n", dnam(d), cause, p->task);
+
+ if(cause & (Aprcs|Aifs)){
+ s0 = d->state;
+ switch(p->sstatus & Smask){
+ case Smissing:
+ d->state = Dmissing;
+ break;
+ case Spresent:
+ if((p->sstatus & Imask) == Islumber)
+ d->state = Dnew;
+ else
+ d->state = Derror;
+ break;
+ case Sphylink:
+ /* power mgnt crap for suprise removal */
+ p->ie |= Aprcs|Apcs; /* is this required? */
+ d->state = Dreset;
+ break;
+ case Sbist:
+ d->state = Doffline;
+ break;
+ }
+ dprint("%s: updatedrive: %s → %s [ss %.3lux]\n",
+ dnam(d), diskstates[s0], diskstates[d->state], p->sstatus);
+ if(s0 == Dready && d->state != Dready)
+ dprint("%s: pulled\n", dnam(d));
+ if(d->state != Dready)
+ f |= Ferror;
+ if(d->state != Dready || p->ci)
+ ewake = 1;
+ }
+ p->serror = serr;
+ if(ewake)
+ clearci(p);
+ if(f){
+ d->portm.flag = f;
+ wakeup(&d->portm);
+ }
+ last = cause;
+}
+
+static void
+dstatus(Drive *d, int s)
+{
+ dprint("%s: dstatus: %s → %s from pc=%p\n", dnam(d),
+ diskstates[d->state], diskstates[s], getcallerpc(&d));
+
+ ilock(d);
+ d->state = s;
+ iunlock(d);
+}
+
+static void
+configdrive(Drive *d)
+{
+ if(ahciconfigdrive(d->ctlr->hba, &d->portc, d->mode) == -1){
+ dstatus(d, Dportreset);
+ return;
+ }
+
+ ilock(d);
+ switch(d->port->sstatus & Smask){
+ default:
+ case Smissing:
+ d->state = Dmissing;
+ break;
+ case Spresent:
+ if(d->state == Dnull)
+ d->state = Dportreset;
+ break;
+ case Sphylink:
+ if(d->state == Dready)
+ break;
+ d->wait = 0;
+ d->state = Dnew;
+ break;
+ case Sbist:
+ d->state = Doffline;
+ break;
+ }
+ iunlock(d);
+
+ dprint("%s: configdrive: %s\n", dnam(d), diskstates[d->state]);
+}
+
+static void
+resetdisk(Drive *d)
+{
+ uint state, det, stat;
+ Aport *p;
+
+ p = d->port;
+ det = p->sctl & 7;
+ stat = p->sstatus & Smask;
+ state = (p->cmd>>28) & 0xf;
+ dprint("%s: resetdisk [icc %ux; det %.3ux; sdet %.3ux]\n", dnam(d), state, det, stat);
+
+ ilock(d);
+ if(d->state != Dready && d->state != Dnew)
+ d->portm.flag |= Ferror;
+ if(stat != Sphylink)
+ d->state = Dportreset;
+ else
+ d->state = Dreset;
+ clearci(p); /* satisfy sleep condition. */
+ wakeup(&d->portm);
+ iunlock(d);
+
+ if(stat != Sphylink)
+ return;
+
+ qlock(&d->portm);
+ if(p->cmd&Ast && ahciswreset(&d->portc) == -1)
+ dstatus(d, Dportreset); /* get a bigger stick. */
+ else
+ configdrive(d);
+ qunlock(&d->portm);
+}
+
+static int
+newdrive(Drive *d)
+{
+ char *s;
+ Aportc *c;
+ Aportm *m;
+
+ c = &d->portc;
+ m = &d->portm;
+
+ qlock(c->m);
+ setfissig(m, c->p->sig);
+ if(identify(d) == -1){
+ dprint("%s: identify failure\n", dnam(d));
+ goto lose;
+ }
+ if(settxmode(c, m->udma) == -1){
+ dprint("%s: can't set udma mode\n", dnam(d));
+ goto lose;
+ }
+ if(m->feat & Dpower && setfeatures(c, 0x85, 3*1000) == -1){
+ dprint("%s: can't disable apm\n", dnam(d));
+ m->feat &= ~Dpower;
+ if(ahcirecover(c) == -1)
+ goto lose;
+ }
+ dstatus(d, Dready);
+ qunlock(c->m);
+
+ s = "";
+ if(m->feat & Dllba)
+ s = "L";
+ idprint("%s: %sLBA %,llud sectors\n", dnam(d), s, d->sectors);
+ idprint(" %s %s %s %s\n", d->model, d->firmware, d->serial,
+ d->drivechange? "[newdrive]": "");
+ return 0;
+
+lose:
+ idprint("%s: can't be initialized\n", dnam(d));
+ dstatus(d, Dnull);
+ qunlock(c->m);
+ return -1;
+}
+
+enum {
+ Nms = 256,
+ Mcomrwait = 1*1024/Nms - 1,
+ Mphywait = 2*1024/Nms - 1,
+ Midwait = 16*1024/Nms - 1,
+};
+
+static void
+hangck(Drive *d)
+{
+ if(d->active && d->totick != 0 && (long)(Ticks - d->totick) > 0){
+ dprint("%s: drive hung [task %lux; ci %lux; serr %lux]%s\n",
+ dnam(d), d->port->task, d->port->ci, d->port->serror,
+ d->nodma == 0 ? "; disabling dma" : "");
+ d->nodma = 1;
+ d->state = Dreset;
+ }
+}
+
+static ushort olds[NCtlr*NCtlrdrv];
+
+static void
+doportreset(Drive *d)
+{
+ qlock(&d->portm);
+ ahciportreset(&d->portc, d->mode);
+ qunlock(&d->portm);
+
+ dprint("ahci: portreset: %s [task %lux; ss %.3lux]\n",
+ diskstates[d->state], d->port->task, d->port->sstatus);
+}
+
+/* drive must be locked */
+static void
+statechange(Drive *d)
+{
+ switch(d->state){
+ case Dnull:
+ case Doffline:
+ if(d->unit)
+ if(d->unit->sectors != 0){
+ d->sectors = 0;
+ d->drivechange = 1;
+ }
+ case Dready:
+ d->wait = 0;
+ }
+}
+
+static uint
+maxmode(Ctlr *c)
+{
+ return (c->hba->cap & 0xf*Hiss)/Hiss;
+}
+
+static void iainterrupt(Ureg*, void *);
+
+static void
+checkdrive(Drive *d, int i)
+{
+ ushort s, sig;
+
+ if(d->ctlr->enabled == 0)
+ return;
+ if(d->driveno == 0)
+ iainterrupt(0, d->ctlr); /* check for missed irq's */
+
+ ilock(d);
+ s = d->port->sstatus;
+ if(s)
+ d->lastseen = Ticks;
+ if(s != olds[i]){
+ dprint("%s: status: %.3ux -> %.3ux: %s\n",
+ dnam(d), olds[i], s, diskstates[d->state]);
+ olds[i] = s;
+ d->wait = 0;
+ }
+ hangck(d);
+ switch(d->state){
+ case Dnull:
+ case Dready:
+ break;
+ case Dmissing:
+ case Dnew:
+ switch(s & (Iactive|Smask)){
+ case Spresent:
+ ahciwakeup(&d->portc, d->mode);
+ case Smissing:
+ break;
+ default:
+ dprint("%s: unknown status %.3ux\n", dnam(d), s);
+ /* fall through */
+ case Iactive: /* active, no device */
+ if(++d->wait&Mphywait)
+ break;
+reset:
+ if(d->mode == 0)
+ d->mode = maxmode(d->ctlr);
+ else
+ d->mode--;
+ if(d->mode == DMautoneg){
+ d->wait = 0;
+ d->state = Dportreset;
+ goto portreset;
+ }
+ dprint("%s: reset; new mode %s\n", dnam(d),
+ modes[d->mode]);
+ iunlock(d);
+ resetdisk(d);
+ ilock(d);
+ break;
+ case Iactive | Sphylink:
+ if(d->unit == nil)
+ break;
+ if((++d->wait&Midwait) == 0){
+ dprint("%s: slow reset [task %lux; ss %.3ux; wait %d]\n",
+ dnam(d), d->port->task, s, d->wait);
+ goto reset;
+ }
+ s = (uchar)d->port->task;
+ sig = d->port->sig >> 16;
+ if(s == 0x7f || s&ASbsy ||
+ (sig != 0xeb14 && (s & ASdrdy) == 0))
+ break;
+ iunlock(d);
+ newdrive(d);
+ ilock(d);
+ break;
+ }
+ break;
+ case Doffline:
+ if(d->wait++ & Mcomrwait)
+ break;
+ case Derror:
+ case Dreset:
+ dprint("%s: reset [%s]: mode %d; status %.3ux\n",
+ dnam(d), diskstates[d->state], d->mode, s);
+ iunlock(d);
+ resetdisk(d);
+ ilock(d);
+ break;
+ case Dportreset:
+portreset:
+ if(d->wait++ & Mcomrwait)
+ break;
+ if(d->wait > Mcomrwait && (s & Iactive) == 0){
+ d->state = Dnull; /* stuck in portreset */
+ break;
+ }
+ dprint("%s: portreset [%s]: mode %d; status %.3ux\n",
+ dnam(d), diskstates[d->state], d->mode, s);
+ d->portm.flag |= Ferror;
+ clearci(d->port);
+ wakeup(&d->portm);
+ if((s & Smask) == Smissing){
+ d->state = Dmissing;
+ break;
+ }
+ iunlock(d);
+ doportreset(d);
+ ilock(d);
+ break;
+ }
+ statechange(d);
+ iunlock(d);
+}
+
+static void
+satakproc(void*)
+{
+ int i;
+
+ while(waserror())
+ ;
+ for(;;){
+ tsleep(&up->sleep, return0, 0, Nms);
+ for(i = 0; i < niadrive; i++)
+ checkdrive(iadrive[i], i);
+ }
+}
+
+static void
+iainterrupt(Ureg *u, void *a)
+{
+ int i;
+ ulong cause, m;
+ Ctlr *c;
+ Drive *d;
+
+ c = a;
+ ilock(c);
+ cause = c->hba->isr;
+ for(i = 0; cause; i++){
+ m = 1 << i;
+ if((cause & m) == 0)
+ continue;
+ cause &= ~m;
+ d = c->rawdrive + i;
+ ilock(d);
+ if(d->port != nil && d->port->isr && c->hba->pi & m)
+ updatedrive(d);
+ c->hba->isr = m;
+ iunlock(d);
+ }
+ if(u == 0 && i > 0)
+ c->missirq++;
+ iunlock(c);
+}
+
+static int
+ahciencreset(Ctlr *c)
+{
+ Ahba *h;
+ int i;
+
+ if(c->enctype == Eesb)
+ return 0;
+ h = c->hba;
+ h->emctl |= Emrst;
+ for(i = 0; i < 1000; i++){
+ if((h->emctl & Emrst) == 0)
+ return 0;
+ esleep(1);
+ }
+ print("%s: ahciencreset: hung ctlr\n", Tname(c));
+ return -1;
+}
+
+/*
+ * from the standard: (http://en.wikipedia.org/wiki/IBPI)
+ * rebuild is preferred as locate+fail; alternate 1hz fail
+ * we're going to assume no locate led.
+ */
+
+enum {
+ Ledsleep = 125, /* 8hz */
+
+ N0 = Ledon*Aled,
+ L0 = Ledon*Aled | Ledon*Locled,
+ L1 = Ledon*Aled | Ledoff*Locled,
+ R0 = Ledon*Aled | Ledon*Locled | Ledon*Errled,
+ R1 = Ledon*Aled | Ledoff*Errled,
+ S0 = Ledon*Aled | Ledon*Locled /*| Ledon*Errled*/, /* botch */
+ S1 = Ledon*Aled | Ledoff*Errled,
+ P0 = Ledon*Aled | Ledon*Errled,
+ P1 = Ledon*Aled | Ledoff*Errled,
+ F0 = Ledon*Aled | Ledon*Errled,
+ C0 = Ledon*Aled | Ledon*Locled,
+ C1 = Ledon*Aled | Ledoff*Locled,
+
+};
+
+//static ushort led3[Ibpilast*8] = {
+//[Ibpinone*8] 0, 0, 0, 0, 0, 0, 0, 0,
+//[Ibpinormal*8] N0, N0, N0, N0, N0, N0, N0, N0,
+//[Ibpirebuild*8] R0, R0, R0, R0, R1, R1, R1, R1,
+//[Ibpilocate*8] L0, L1, L0, L1, L0, L1, L0, L1,
+//[Ibpispare*8] S0, S1, S0, S1, S1, S1, S1, S1,
+//[Ibpipfa*8] P0, P1, P0, P1, P1, P1, P1, P1, /* first 1 sec */
+//[Ibpifail*8] F0, F0, F0, F0, F0, F0, F0, F0,
+//[Ibpicritarray*8] C0, C0, C0, C0, C1, C1, C1, C1,
+//[Ibpifailarray*8] C0, C1, C0, C1, C0, C1, C0, C1,
+//};
+
+static ushort led2[Ibpilast*8] = {
+[Ibpinone*8] 0, 0, 0, 0, 0, 0, 0, 0,
+[Ibpinormal*8] N0, N0, N0, N0, N0, N0, N0, N0,
+[Ibpirebuild*8] R0, R0, R0, R0, R1, R1, R1, R1,
+[Ibpilocate*8] L0, L0, L0, L0, L0, L0, L0, L0,
+[Ibpispare*8] S0, S0, S0, S0, S1, S1, S1, S1,
+[Ibpipfa*8] P0, P1, P0, P1, P1, P1, P1, P1, /* first 1 sec */
+[Ibpifail*8] F0, F0, F0, F0, F0, F0, F0, F0,
+[Ibpicritarray*8] C0, C0, C0, C0, C1, C1, C1, C1,
+[Ibpifailarray*8] C0, C1, C0, C1, C0, C1, C0, C1,
+};
+
+static int
+ledstate(Ledport *p, uint seq)
+{
+ ushort i;
+
+ if(p->led == Ibpipfa && seq%32 >= 8)
+ i = P1;
+ else
+ i = led2[8*p->led + seq%8];
+ if(i != p->ledbits){
+ p->ledbits = i;
+ ledprint("ledstate %,.011ub %ud\n", p->ledbits, seq);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+blink(Drive *d, ulong t)
+{
+ Ahba *h;
+ Ctlr *c;
+ Aledmsg msg;
+
+ if(ledstate(d, t) == 0)
+ return 0;
+ c = d->ctlr;
+ h = c->hba;
+
+ /* ensure last message has been transmitted */
+ if(h->emctl & Tmsg)
+ return -1;
+
+ switch(c->enctype){
+ default:
+ panic("%s: bad led type %d", dnam(d), c->enctype);
+ case Elmt:
+ memset(&msg, 0, sizeof msg);
+ msg.type = Mled;
+ msg.dsize = 0;
+ msg.msize = sizeof msg - 4;
+ msg.led[0] = d->ledbits;
+ msg.led[1] = d->ledbits>>8;
+ msg.pm = 0;
+ msg.hba = d->driveno;
+ memmove(c->enctx, &msg, sizeof msg);
+ break;
+ }
+ h->emctl |= Tmsg;
+ return 1;
+}
+
+enum {
+ Esbdrv0 = 4, /* start pos in bits */
+ Esbiota = 3, /* shift in bits */
+ Esbact = 1,
+ Esbloc = 2,
+ Esberr = 4,
+};
+
+uint
+esbbits(uint s)
+{
+ uint i, e; /* except after c */
+
+ e = 0;
+ for(i = 0; i < 3; i++)
+ e |= ((s>>3*i & 7) != 0)<<i;
+ return e;
+}
+
+static int
+blinkesb(Ctlr *c, ulong t)
+{
+ uint i, s, u[32/4];
+ uvlong v;
+ Drive *d;
+
+ s = 0;
+ for(i = 0; i < c->ndrive; i++){
+ d = c->drive[i];
+ s |= ledstate(d, t); /* no port mapping */
+ }
+ if(s == 0)
+ return 0;
+ memset(u, 0, sizeof u);
+ for(i = 0; i < c->ndrive; i++){
+ d = c->drive[i];
+ s = Esbdrv0 + Esbiota*i;
+ v = esbbits(d->ledbits) * (1ull << s%32);
+ u[s/32 + 0] |= v;
+ u[s/32 + 1] |= v>>32;
+ }
+ for(i = 0; i < c->encsz; i++)
+ c->enctx[i] = u[i];
+ return 1;
+}
+
+static long
+ahciledr(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
+ahciledw(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);
+}
+
+static void
+ledkproc(void*)
+{
+ uchar map[NCtlr];
+ uint i, j, t0, t1;
+ Ctlr *c;
+ Drive *d;
+
+ j = 0;
+ memset(map, 0, sizeof map);
+ for(i = 0; i < niactlr; i++)
+ if(iactlr[i].enctype != 0){
+ if(ahciencreset(iactlr + i) == -1)
+ continue;
+ map[i] = 1;
+ j++;
+ }
+ if(j == 0)
+ pexit("no work", 1);
+ for(i = 0; i < niadrive; i++){
+ d = iadrive[i];
+ d->nled = 3; /* hardcoded */
+ if(d->ctlr->enctype == Eesb)
+ d->nled = 3;
+ d->ledbits = -1;
+ }
+ for(i = 0; ; i++){
+ t0 = Ticks;
+ for(j = 0; j < niadrive; ){
+ d = iadrive[j];
+ c = d->ctlr;
+ if(map[c - iactlr] == 0)
+ j++;
+ else
+ if(c->enctype == Eesb){
+ blinkesb(c, i);
+ j += c->ndrive;
+ }else{
+ blink(d, i);
+ j++;
+ }
+ }
+ t1 = Ticks;
+ esleep(Ledsleep - TK2MS(t1 - t0));
+ }
+}
+
+static int
+waitready(Drive *d)
+{
+ ulong s, i, δ;
+
+ for(i = 0;; i += 250){
+ if(d->state == Dreset || d->state == Dportreset || d->state == Dnew)
+ return 1;
+ ilock(d);
+ s = d->port->sstatus;
+ if(d->state == Dready && (s & Smask) == Sphylink){
+ iunlock(d);
+ return 0;
+ }
+ δ = Ticks - d->lastseen;
+ if(d->state == Dnull || δ > 10*1000)
+ break;
+ if((s & Imask) == 0 && δ > 1500)
+ break;
+ if(i >= 15*1000){
+ d->state = Doffline;
+ iunlock(d);
+ print("%s: not responding; offline\n", dnam(d));
+ return -1;
+ }
+ iunlock(d);
+ esleep(250);
+ }
+ iunlock(d);
+ return -1;
+}
+
+static int
+iaverify(SDunit *u)
+{
+ Ctlr *c;
+ Drive *d;
+
+ c = u->dev->ctlr;
+ d = c->drive[u->subno];
+ ilock(c);
+ ilock(d);
+ if(d->unit == nil){
+ d->unit = u;
+ if(c->enctype != 0)
+ sdaddfile(u, "led", 0644, eve, ahciledr, ahciledw);
+ }
+ iunlock(d);
+ iunlock(c);
+ checkdrive(d, d->driveno); /* c->d0 + d->driveno */
+ return 1;
+}
+
+static int
+iaonline(SDunit *u)
+{
+ int r;
+ Ctlr *c;
+ Drive *d;
+
+ c = u->dev->ctlr;
+ d = c->drive[u->subno];
+
+ while(d->state != Dmissing && waitready(d) == 1)
+ esleep(1);
+
+ dprint("%s: iaonline: %s\n", dnam(d), diskstates[d->state]);
+
+ ilock(d);
+ if(d->portm.feat & Datapi){
+ r = d->drivechange;
+ d->drivechange = 0;
+ iunlock(d);
+ if(r != 0)
+ scsiverify(u);
+ return scsionline(u);
+ }
+ r = 0;
+ if(d->drivechange){
+ d->drivechange = 0;
+ r = 2;
+ }else if(d->state == Dready)
+ r = 1;
+ if(r){
+ u->sectors = d->sectors;
+ u->secsize = d->secsize;
+ }
+ iunlock(d);
+
+ return r;
+}
+
+static int
+iaenable(SDev *s)
+{
+ char name[32];
+ Ctlr *c;
+ static int once;
+
+ c = s->ctlr;
+ ilock(c);
+ if(c->enabled){
+ iunlock(c);
+ return 1;
+ }
+ if(c->ndrive == 0)
+ panic("iaenable: zero s->ctlr->ndrive");
+ snprint(name, sizeof name, "%s (%s)", s->name, s->ifc->name);
+ intrenable(c->pci->intl, iainterrupt, c, c->pci->tbdf, name);
+ /* supposed to squelch leftover interrupts here. */
+ ahcienable(c->hba);
+ c->enabled = 1;
+ iunlock(c);
+
+ if(once == 0)
+ kproc("iasata", satakproc, nil, 0);
+ if(++once == niactlr)
+ kproc("ialed", ledkproc, nil, 0);
+
+ return 1;
+}
+
+static int
+iadisable(SDev *s)
+{
+ char name[32];
+ Ctlr *c;
+
+ c = s->ctlr;
+ ilock(c);
+ ahcidisable(c->hba);
+ snprint(name, sizeof name, "%s (%s)", s->name, s->ifc->name);
+ intrdisable(c->pci->intl, iainterrupt, c, c->pci->tbdf, name);
+ c->enabled = 0;
+ iunlock(c);
+ return 1;
+}
+
+static Alist*
+ahcibuild(Drive *d, int rw, void *data, int nsect, vlong lba)
+{
+ uchar *c;
+ uint flags;
+ Aportm *m;
+
+ m = &d->portm;
+ c = m->ctab->cfis;
+ rwfis(m, c, rw, nsect, lba);
+ flags = Lpref;
+ if(rw == SDwrite)
+ flags |= Lwrite;
+ return mkalist(m, flags, data, nsect * d->secsize);
+}
+
+static Alist*
+ahcibuildpkt(Drive *d, SDreq *r, void *data, int n)
+{
+ uint flags;
+ Aportm *m;
+ uchar *c;
+ Actab *t;
+
+ m = &d->portm;
+ t = m->ctab;
+ c = t->cfis;
+
+ atapirwfis(m, c, r->cmd, r->clen, 0x2000);
+ if((n & 15) != 0 || d->nodma)
+ c[Ffeat] &= ~1; /* use pio */
+ else if(c[Ffeat] & 1 && d->info[62] & (1<<15)) /* dma direction */
+ c[Ffeat] = (c[Ffeat] & ~(1<<2)) | ((r->write == 0) << 2);
+ flags = Lpref | Latapi;
+ if(r->write != 0 && data)
+ flags |= Lwrite;
+ return mkalist(m, flags, data, n);
+}
+
+static Alist*
+ahcibuildfis(Drive *d, SDreq *r, void *data, uint n)
+{
+ uint flags;
+ uchar *c;
+ Aportm *m;
+
+ if((r->ataproto & Pprotom) == Ppkt)
+ return ahcibuildpkt(d, r, data, n);
+
+ m = &d->portm;
+ c = m->ctab->cfis;
+ memmove(c, r->cmd, r->clen);
+ flags = Lpref;
+ if(r->write || n == 0)
+ flags |= Lwrite;
+ return mkalist(m, flags, data, n);
+}
+
+static int
+lockready(Drive *d)
+{
+ int i;
+
+ qlock(&d->portm);
+ while ((i = waitready(d)) == 1) {
+ qunlock(&d->portm);
+ esleep(1);
+ qlock(&d->portm);
+ }
+ return i;
+}
+
+static int
+flushcache(Drive *d)
+{
+ int i;
+
+ i = -1;
+ if(lockready(d) == 0)
+ i = ahciflushcache(&d->portc);
+ qunlock(&d->portm);
+ return i;
+}
+
+static int
+io(Drive *d, uint proto, int to, int interrupt)
+{
+ uint task, flag, stat, rv;
+ Aport *p;
+ Asleep as;
+
+ switch(waitready(d)){
+ case -1:
+ return SDeio;
+ case 1:
+ return SDretry;
+ }
+
+ ilock(d);
+ d->portm.flag = 0;
+ iunlock(d);
+ p = d->port;
+ p->ci = 1;
+
+ as.p = p;
+ as.i = 1;
+ d->totick = 0;
+ if(to > 0)
+ d->totick = Ticks + MS2TK(to) | 1; /* fix fencepost */
+ d->active++;
+
+ while(waserror())
+ if(interrupt){
+ d->active--;
+ d->port->ci = 0;
+ if(ahcicomreset(&d->portc) == -1)
+ dstatus(d, Dreset);
+ return SDtimeout;
+ }
+
+ sleep(&d->portm, ahciclear, &as);
+ poperror();
+
+ d->active--;
+ ilock(d);
+ stat = d->state;
+ flag = d->portm.flag;
+ task = d->port->task;
+ iunlock(d);
+
+ rv = SDok;
+ if(proto & Ppkt && stat == Dready){
+ rv = task >> 8 + 4 & 0xf;
+ flag &= ~Fahdrs;
+ flag |= Fdone;
+ }else if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && stat == Dready){
+ d->port->ci = 0;
+ ahcirecover(&d->portc);
+ task = d->port->task;
+ flag &= ~Fdone; /* either an error or do-over */
+ }
+ if(flag == 0){
+ print("%s: retry\n", dnam(d));
+ return SDretry;
+ }
+ if(flag & (Fahdrs | Ferror)){
+ if((task & Eidnf) == 0)
+ print("%s: i/o error %ux\n", dnam(d), task);
+ return SDcheck;
+ }
+ return rv;
+}
+
+static int
+iariopkt(SDreq *r, Drive *d)
+{
+ int try, to;
+ uchar *cmd;
+ Alist *l;
+
+ cmd = r->cmd;
+ aprint("%s: %.2ux %.2ux %c %d %p\n", dnam(d), cmd[0], cmd[2],
+ "rw"[r->write], r->dlen, r->data);
+
+ r->rlen = 0;
+
+ /*
+ * prevent iaonline() to hang forever by timing out
+ * inquiry and capacity commands after 5 seconds.
+ */
+ to = 30*1000;
+ switch(cmd[0]){
+ case 0x9e: if(cmd[1] != 0x10) break;
+ case 0x25:
+ case 0x12:
+ to = 5*1000;
+ break;
+ }
+
+ for(try = 0; try < 10; try++){
+ qlock(&d->portm);
+ l = ahcibuildpkt(d, r, r->data, r->dlen);
+ r->status = io(d, Ppkt, to, 0);
+ switch(r->status){
+ case SDeio:
+ qunlock(&d->portm);
+ return SDeio;
+ case SDretry:
+ qunlock(&d->portm);
+ continue;
+ }
+ r->rlen = l->len;
+ qunlock(&d->portm);
+ return SDok;
+ }
+ print("%s: bad disk\n", dnam(d));
+ return r->status = SDcheck;
+}
+
+static long
+ahcibio(SDunit *u, int lun, int write, void *a, long count, uvlong lba)
+{
+ int n, rw, try, status, max;
+ uchar *data;
+ Ctlr *c;
+ Drive *d;
+
+ c = u->dev->ctlr;
+ d = c->drive[u->subno];
+ if(d->portm.feat & Datapi)
+ return scsibio(u, lun, write, a, count, lba);
+
+ max = 128;
+ if(d->portm.feat & Dllba){
+ max = 8192; /* ahci maximum */
+ if(c->type == Tsb600)
+ max = 255; /* errata */
+ }
+ rw = write? SDwrite: SDread;
+ data = a;
+ dprint("%s: bio: %llud %c %lud %p\n",
+ dnam(d), lba, "rw"[rw], count, data);
+
+ for(try = 0; try < 10;){
+ n = count;
+ if(n > max)
+ n = max;
+ qlock(&d->portm);
+ ahcibuild(d, rw, data, n, lba);
+ status = io(d, Pdma, 5000, 0);
+ qunlock(&d->portm);
+ switch(status){
+ case SDeio:
+ return -1;
+ case SDretry:
+ try++;
+ continue;
+ }
+ try = 0;
+ count -= n;
+ lba += n;
+ data += n * u->secsize;
+ if(count == 0)
+ return data - (uchar*)a;
+ }
+ print("%s: bad disk\n", dnam(d));
+ return -1;
+}
+
+static int
+iario(SDreq *r)
+{
+ int i, n, count, rw;
+ uchar *cmd;
+ uvlong lba;
+ Ctlr *c;
+ Drive *d;
+ SDunit *u;
+
+ u = r->unit;
+ c = u->dev->ctlr;
+ d = c->drive[u->subno];
+ if(d->portm.feat & Datapi)
+ return iariopkt(r, d);
+ cmd = r->cmd;
+
+ 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, &rw)) != SDnostatus)
+ return i;
+ n = ahcibio(u, r->lun, r->write, r->data, count, lba);
+ if(n == -1)
+ return SDeio;
+ r->rlen = n;
+ return SDok;
+}
+
+static uchar bogusrfis[16] = {
+[Ftype] 0x34,
+[Fioport] 0x40,
+[Fstatus] 0x50,
+[Fdev] 0xa0,
+};
+
+static void
+sdr0(Drive *d)
+{
+ uchar *c;
+
+ c = d->portm.fis.r;
+ memmove(c, bogusrfis, sizeof bogusrfis);
+ coherence();
+}
+
+static int
+sdr(SDreq *r, Drive *d, int st)
+{
+ uchar *c;
+ uint t;
+
+ if((r->ataproto & Pprotom) == Ppkt){
+ t = d->port->task;
+ if(t & ASerr)
+ st = t >> 8 + 4 & 0xf;
+ }
+ c = d->portm.fis.r;
+ memmove(r->cmd, c, 16);
+ r->status = st;
+ if(st == SDcheck)
+ st = SDok;
+ return st;
+}
+
+static int
+fisreqchk(Sfis *f, SDreq *r)
+{
+ if((r->ataproto & Pprotom) == Ppkt)
+ return SDnostatus;
+ /*
+ * handle oob requests;
+ * restrict & sanitize commands
+ */
+ if(r->clen != 16)
+ error(Eio);
+ if(r->cmd[0] == 0xf0){
+ sigtofis(f, r->cmd);
+ r->status = SDok;
+ return SDok;
+ }
+ r->cmd[0] = 0x27;
+ r->cmd[1] = 0x80;
+ r->cmd[7] |= 0xa0;
+ return SDnostatus;
+}
+
+static int
+iaataio(SDreq *r)
+{
+ int try;
+ Ctlr *c;
+ Drive *d;
+ SDunit *u;
+ Alist *l;
+
+ u = r->unit;
+ c = u->dev->ctlr;
+ d = c->drive[u->subno];
+
+ if((r->status = fisreqchk(&d->portm, r)) != SDnostatus)
+ return r->status;
+ r->rlen = 0;
+ sdr0(d);
+ for(try = 0; try < 10; try++){
+ qlock(&d->portm);
+ l = ahcibuildfis(d, r, r->data, r->dlen);
+ r->status = io(d, r->ataproto & Pprotom, -1, 1);
+ switch(r->status){
+ case SDtimeout:
+ qunlock(&d->portm);
+ return sdsetsense(r, SDcheck, 11, 0, 6);
+ case SDeio:
+ qunlock(&d->portm);
+ return SDeio;
+ case SDretry:
+ qunlock(&d->portm);
+ continue;
+ }
+ r->rlen = (r->ataproto & Pprotom) == Ppkt ? l->len : r->dlen;
+ try = sdr(r, d, r->status);
+ qunlock(&d->portm);
+ return try;
+ }
+ print("%s: bad disk\n", dnam(d));
+ return r->status = SDeio;
+}
+
+enum{
+ Ghc = 0x04/4, /* global host control */
+ Pi = 0x0c/4, /* ports implemented */
+ Cmddec = 1<<15, /* enable command block decode */
+
+ /* Ghc bits */
+ Ahcien = 1<<31, /* ahci enable */
+};
+
+static void
+iasetupahci(Ctlr *c)
+{
+ if(c->type != Tich)
+ return;
+
+ pcicfgw16(c->pci, 0x40, pcicfgr16(c->pci, 0x40) & ~Cmddec);
+ pcicfgw16(c->pci, 0x42, pcicfgr16(c->pci, 0x42) & ~Cmddec);
+
+ c->lmmio[Ghc] |= Ahcien;
+ c->lmmio[Pi] = (1 << 6) - 1; /* 6 ports (supposedly ro pi reg) */
+
+ /* enable ahci mode; from ich9 datasheet */
+ pcicfgw16(c->pci, 0x90, 1<<6 | 1<<5);
+
+ /* configure drives 0-5 as ahci sata (c.f. errata) */
+ pcicfgw16(c->pci, 0x92, pcicfgr16(c->pci, 0x92) | 0xf);
+}
+
+static void
+sbsetupahci(Pcidev *p)
+{
+ print("sbsetupahci: tweaking %.4ux ccru %.2ux ccrp %.2ux\n",
+ p->did, p->ccru, p->ccrp);
+ pcicfgw8(p, 0x40, pcicfgr8(p, 0x40) | 1);
+ pcicfgw8(p, PciCCRu, 6);
+ pcicfgw8(p, PciCCRp, 1);
+ p->ccru = 6;
+ p->ccrp = 1;
+}
+
+static int
+esbenc(Ctlr *c)
+{
+ c->encsz = 1;
+ c->enctx = (ulong*)(c->mmio + 0xa0);
+ c->enctype = Eesb;
+ c->enctx[0] = 0;
+ return 0;
+}
+
+static int
+ahciencinit(Ctlr *c)
+{
+ ulong type, sz, o, *bar;
+ Ahba *h;
+
+ h = c->hba;
+ if(c->type == Tesb)
+ return esbenc(c);
+ if((h->cap & Hems) == 0)
+ return -1;
+ type = h->emctl & Emtype;
+ switch(type){
+ case Esgpio:
+ case Eses2:
+ case Esafte:
+ return -1;
+ case Elmt:
+ break;
+ default:
+ return -1;
+ }
+
+ sz = h->emloc & 0xffff;
+ o = h->emloc>>16;
+ if(sz == 0 || o == 0)
+ return -1;
+ bar = c->lmmio;
+ dprint("size = %.4lux; loc = %.4lux*4\n", sz, o);
+ c->encsz = sz;
+ c->enctx = bar + o;
+ if((h->emctl & Xonly) == 0){
+ if(h->emctl & Smb)
+ c->encrx = bar + o;
+ else
+ c->encrx = bar + o*2;
+ }
+ c->enctype = type;
+ return 0;
+}
+
+static int
+didtype(Pcidev *p)
+{
+ int type;
+
+ type = Tahci;
+ switch(p->vid){
+ default:
+ return -1;
+ case 0x8086:
+ if((p->did & 0xffff) == 0x1e02)
+ return Tich; /* c210 */
+ if((p->did & 0xffff) == 0x8c02)
+ return Tich; /* c220 */
+ if((p->did & 0xffff) == 0x24d1)
+ return Tich; /* 82801eb/er */
+ if((p->did & 0xffff) == 0x2653)
+ return Tich; /* 82801fbm */
+ if((p->did & 0xfffc) == 0x2680)
+ return Tesb;
+ if((p->did & 0xfffb) == 0x27c1)
+ return Tich; /* 82801g[bh]m */
+ if((p->did & 0xffff) == 0x2822)
+ return Tich; /* 82801 SATA RAID */
+ if((p->did & 0xffff) == 0x2821)
+ return Tich; /* 82801h[roh] */
+ if((p->did & 0xfffe) == 0x2824)
+ return Tich; /* 82801h[b] */
+ if((p->did & 0xfeff) == 0x2829)
+ return Tich; /* ich8 */
+ if((p->did & 0xfffe) == 0x2922)
+ return Tich; /* ich9 */
+ if((p->did & 0xffff) == 0x3a02)
+ return Tich; /* 82801jd/do */
+ if((p->did & 0xfefe) == 0x3a22)
+ return Tich; /* ich10, pch */
+ if((p->did & 0xfff7) == 0x3b28)
+ return Tich; /* pchm */
+ if((p->did & 0xfffe) == 0x3b22)
+ return Tich; /* pch */
+ break;
+ case 0x1002:
+ if(p->ccru == 1 || p->ccrp != 1)
+ if(p->did == 0x4380 || p->did == 0x4390)
+ sbsetupahci(p);
+ type = Tsb600;
+ break;
+ case 0x1106:
+ /*
+ * unconfirmed report that the programming
+ * interface is set incorrectly.
+ */
+ if(p->did == 0x3349)
+ return Tahci;
+ break;
+ case 0x1022:
+ /* Hudson SATA Controller [AHCI mode] */
+ if((p->did & 0xfffe) == 0x7800){
+ sbsetupahci(p);
+ return Tahci;
+ }
+ break;
+ case 0x10de:
+ case 0x1039:
+ case 0x1b21: /* ASMedia */
+ case 0x1b4b:
+ case 0x11ab:
+ break;
+ case 0x197b:
+ case 0x10b9:
+ type = Tjmicron;
+ break;
+ }
+ if(p->ccrb == Pcibcstore && p->ccru == 6 && p->ccrp == 1)
+ return type;
+ return -1;
+}
+
+static SDev*
+iapnp(void)
+{
+ int i, n, nunit, type;
+ uvlong io;
+ Ctlr *c;
+ Drive *d;
+ Pcidev *p;
+ SDev *s;
+ static int done;
+
+ if(done)
+ return nil;
+ done = 1;
+
+ if(getconf("*noahci") != nil)
+ return nil;
+
+ if(getconf("*ahcidebug") != nil){
+ debug = 1;
+ datapi = 1;
+ }
+
+ memset(olds, 0xff, sizeof olds);
+ p = nil;
+ while((p = pcimatch(p, 0, 0)) != nil){
+ if((type = didtype(p)) == -1)
+ continue;
+ io = p->mem[Abar].bar;
+ if(io == 0 || (io & 1) != 0 || p->mem[Abar].size < 0x180)
+ continue;
+ io &= ~0xf;
+ if(niactlr == NCtlr){
+ print("iapnp: %s: too many controllers\n", tname[type]);
+ break;
+ }
+ c = iactlr + niactlr;
+ s = sdevs + niactlr;
+ memset(c, 0, sizeof *c);
+ memset(s, 0, sizeof *s);
+ c->mmio = vmap(io, p->mem[Abar].size);
+ if(c->mmio == nil){
+ print("%s: can't map %llux\n", Tname(c), io);
+ continue;
+ }
+ c->lmmio = (ulong*)c->mmio;
+ c->pci = p;
+ c->type = type;
+
+ s->ifc = &sdiahciifc;
+ s->idno = 'E';
+ s->ctlr = c;
+ c->sdev = s;
+
+ pcienable(p);
+ ahcihandoff((Ahba*)c->mmio);
+ if(p->vid == 0x8086)
+ iasetupahci(c);
+ nunit = ahciconf(c);
+ if(nunit < 1){
+ vunmap(c->mmio, p->mem[Abar].size);
+ pcidisable(p);
+ continue;
+ }
+ c->ndrive = s->nunit = nunit;
+
+ /* map the drives -- they don't all need to be enabled. */
+ memset(c->rawdrive, 0, sizeof c->rawdrive);
+ n = 0;
+ for(i = 0; i < NCtlrdrv; i++){
+ d = c->rawdrive + i;
+ d->portno = i;
+ d->driveno = -1;
+ d->sectors = 0;
+ d->serial[0] = ' ';
+ d->ctlr = c;
+ if((c->hba->pi & 1<<i) == 0)
+ continue;
+ io = 0x100 + 0x80*i;
+ if((io + 0x80) > p->mem[Abar].size)
+ continue;
+ d->port = (Aport*)(c->mmio + io);
+ d->portc.p = d->port;
+ d->portc.m = &d->portm;
+ d->driveno = n++;
+ snprint(d->name, sizeof d->name, "iahci%d.%d", niactlr, i);
+ c->drive[d->driveno] = d;
+ iadrive[niadrive + d->driveno] = d;
+ }
+ pcisetbme(c->pci);
+ for(i = 0; i < n; i++){
+ c->drive[i]->mode = DMautoneg;
+ configdrive(c->drive[i]);
+ }
+ ahciencinit(c);
+
+ niadrive += n;
+ niactlr++;
+ sdadddevs(s);
+ i = (c->hba->cap >> 21) & 1;
+ print("#S/%s: %s: sata-%s with %d ports\n", s->name,
+ Tname(c), "I\0II" + i*2, nunit);
+ }
+ return nil;
+}
+
+static Htab ctab[] = {
+ Aasp, "asp",
+ Aalpe , "alpe ",
+ Adlae, "dlae",
+ Aatapi, "atapi",
+ Apste, "pste",
+ Afbsc, "fbsc",
+ Aesp, "esp",
+ Acpd, "cpd",
+ Ampsp, "mpsp",
+ Ahpcp, "hpcp",
+ Apma, "pma",
+ Acps, "cps",
+ Acr, "cr",
+ Afr, "fr",
+ Ampss, "mpss",
+ Apod, "pod",
+ Asud, "sud",
+ Ast, "st",
+};
+
+static char*
+capfmt(char *p, char *e, Htab *t, int n, ulong cap)
+{
+ uint i;
+
+ *p = 0;
+ for(i = 0; i < n; i++)
+ if(cap & t[i].bit)
+ p = seprint(p, e, "%s ", t[i].name);
+ return p;
+}
+
+static int
+iarctl(SDunit *u, char *p, int l)
+{
+ char buf[32], *e, *op;
+ Aport *o;
+ Ctlr *c;
+ Drive *d;
+
+ if((c = u->dev->ctlr) == nil)
+ return 0;
+ d = c->drive[u->subno];
+ o = d->port;
+
+ e = p+l;
+ op = p;
+ 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);
+ if(d->wwn != 0)
+ p = seprint(p, e, "wwn\t%ullx\n", d->wwn);
+ p = seprint(p, e, "flag\t");
+ p = pflag(p, e, &d->portm);
+ p = seprint(p, e, "udma\t%d\n", d->portm.udma);
+ }else
+ p = seprint(p, e, "no disk present [%s]\n", diskstates[d->state]);
+ serrstr(o->serror, buf, buf + sizeof buf - 1);
+ p = seprint(p, e, "reg\ttask %lux cmd %lux serr %lux %s ci %lux is %lux "
+ "sig %lux sstatus %.3lux\n", o->task, o->cmd, o->serror, buf,
+ o->ci, o->isr, o->sig, o->sstatus);
+ p = seprint(p, e, "cmd\t");
+ p = capfmt(p, e, ctab, nelem(ctab), o->cmd);
+ p = seprint(p, e, "\n");
+ p = seprint(p, e, "mode\t%s %s\n", modes[d->mode], modes[maxmode(c)]);
+ p = seprint(p, e, "geometry %llud %d\n", d->sectors, d->secsize);
+ p = seprint(p, e, "alignment %d %d\n",
+ d->secsize<<d->portm.physshift, d->portm.physalign);
+ p = seprint(p, e, "missirq\t%ud\n", c->missirq);
+ return p - op;
+}
+
+static void
+forcemode(Drive *d, char *mode)
+{
+ int i;
+
+ for(i = 0; i < nelem(modes); i++)
+ if(strcmp(mode, modes[i]) == 0)
+ break;
+ if(i == nelem(modes))
+ i = 0;
+ ilock(d);
+ d->mode = i;
+ iunlock(d);
+}
+
+static void
+forcestate(Drive *d, char *state)
+{
+ int i;
+
+ for(i = 0; i < nelem(diskstates); i++)
+ if(strcmp(state, diskstates[i]) == 0)
+ break;
+ if(i == nelem(diskstates))
+ error(Ebadctl);
+ dstatus(d, i);
+}
+
+static int
+runsettxmode(Drive *d, char *s)
+{
+ int i;
+ Aportc *c;
+ Aportm *m;
+
+ c = &d->portc;
+ m = &d->portm;
+
+ i = 1;
+ if(lockready(d) == 0){
+ m->udma = atoi(s);
+ if(settxmode(c, m->udma) == 0)
+ i = 0;
+ }
+ qunlock(m);
+ return i;
+}
+
+
+static int
+iawctl(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], "mode") == 0)
+ forcemode(d, f[1]? f[1]: "satai");
+ else if(strcmp(f[0], "state") == 0)
+ forcestate(d, f[1]? f[1]: "null");
+ else if(strcmp(f[0], "txmode") == 0){
+ if(runsettxmode(d, f[1]? f[1]: "0"))
+ cmderror(cmd, "bad txmode / stuck port");
+ }else
+ cmderror(cmd, Ebadctl);
+ return 0;
+}
+
+static char *
+portr(char *p, char *e, uint x)
+{
+ int i, a;
+
+ p[0] = 0;
+ a = -1;
+ for(i = 0; i < 32; i++){
+ if((x & (1<<i)) == 0){
+ if(a != -1 && i - 1 != a)
+ p = seprint(p, e, "-%d", i - 1);
+ a = -1;
+ continue;
+ }
+ if(a == -1){
+ if(i > 0)
+ p = seprint(p, e, ", ");
+ p = seprint(p, e, "%d", a = i);
+ }
+ }
+ if(a != -1 && i - 1 != a)
+ p = seprint(p, e, "-%d", i - 1);
+ return p;
+}
+
+static Htab htab[] = {
+ H64a, "64a",
+ Hncq, "ncq",
+ Hsntf, "ntf",
+ Hmps, "mps",
+ Hss, "ss",
+ Halp, "alp",
+ Hal, "led",
+ Hclo, "clo",
+ Ham, "am",
+ Hpm, "pm",
+ Hfbs, "fbs",
+ Hpmb, "pmb",
+ Hssc, "slum",
+ Hpsc, "pslum",
+ Hcccs, "coal",
+ Hems, "ems",
+ Hxs, "xs",
+};
+
+static Htab htab2[] = {
+ Apts, "apts",
+ Nvmp, "nvmp",
+ Boh, "boh",
+};
+
+static Htab emtab[] = {
+ Pm, "pm",
+ Alhd, "alhd",
+ Xonly, "xonly",
+ Smb, "smb",
+ Esgpio, "esgpio",
+ Eses2, "eses2",
+ Esafte, "esafte",
+ Elmt, "elmt",
+};
+
+static char*
+iartopctl(SDev *s, char *p, char *e)
+{
+ char pr[25];
+ ulong cap;
+ Ahba *h;
+ Ctlr *c;
+
+ c = s->ctlr;
+ h = c->hba;
+ cap = h->cap;
+ p = seprint(p, e, "sd%c ahci %s port %#p: ", s->idno, Tname(c), h);
+ p = capfmt(p, e, htab, nelem(htab), cap);
+ p = capfmt(p, e, htab2, nelem(htab2), h->cap2);
+ p = capfmt(p, e, emtab, nelem(emtab), h->emctl);
+ portr(pr, pr + sizeof pr, h->pi);
+ return seprint(p, e,
+ "iss %ld ncs %ld np %ld ghc %lux isr %lux pi %lux %s ver %lux\n",
+ (cap>>20) & 0xf, (cap>>8) & 0x1f, 1 + (cap & 0x1f),
+ h->ghc, h->isr, h->pi, pr, h->ver);
+}
+
+static int
+iawtopctl(SDev *, Cmdbuf *cmd)
+{
+ int *v;
+ char **f;
+
+ f = cmd->f;
+ v = 0;
+
+ if(strcmp(f[0], "debug") == 0)
+ v = &debug;
+ else if(strcmp(f[0], "idprint") == 0)
+ v = &prid;
+ else if(strcmp(f[0], "aprint") == 0)
+ v = &datapi;
+ else if(strcmp(f[0], "ledprint") == 0)
+ v = &dled;
+ else
+ cmderror(cmd, Ebadctl);
+
+ switch(cmd->nf){
+ default:
+ cmderror(cmd, Ebadarg);
+ case 1:
+ *v ^= 1;
+ break;
+ case 2:
+ if(f[1])
+ *v = strcmp(f[1], "on") == 0;
+ else
+ *v ^= 1;
+ break;
+ }
+ return 0;
+}
+
+SDifc sdiahciifc = {
+ "ahci",
+
+ iapnp,
+ nil, /* legacy */
+ iaenable,
+ iadisable,
+
+ iaverify,
+ iaonline,
+ iario,
+ iarctl,
+ iawctl,
+
+ ahcibio,
+ nil, /* probe */
+ nil, /* clear */
+ iartopctl,
+ iawtopctl,
+ iaataio,
+};
--- /dev/null
+++ b/os/pc64/ahci.h
@@ -1,0 +1,334 @@
+/*
+ * advanced host controller interface (sata)
+ * © 2007-9 coraid, inc
+ */
+
+/* pci configuration */
+enum {
+ Abar = 5,
+};
+
+/*
+ * ahci memory configuration
+ *
+ * 0000-0023 generic host control
+ * 0024-009f reserved
+ * 00a0-00ff vendor specific.
+ * 0100-017f port 0
+ * ...
+ * 1080-1100 port 31
+ */
+
+/* cap bits: supported features */
+enum {
+ H64a = 1<<31, /* 64-bit addressing */
+ Hncq = 1<<30, /* ncq */
+ Hsntf = 1<<29, /* snotification reg. */
+ Hmps = 1<<28, /* mech pres switch */
+ Hss = 1<<27, /* staggered spinup */
+ Halp = 1<<26, /* aggressive link pm */
+ Hal = 1<<25, /* activity led */
+ Hclo = 1<<24, /* command-list override */
+ Hiss = 1<<20, /* for interface speed */
+ Ham = 1<<18, /* ahci-mode only */
+ Hpm = 1<<17, /* port multiplier */
+ Hfbs = 1<<16, /* fis-based switching */
+ Hpmb = 1<<15, /* multiple-block pio */
+ Hssc = 1<<14, /* slumber state */
+ Hpsc = 1<<13, /* partial-slumber state */
+ Hncs = 1<<8, /* n command slots */
+ Hcccs = 1<<7, /* coal */
+ Hems = 1<<6, /* enclosure mgmt. */
+ Hxs = 1<<5, /* external sata */
+ Hnp = 1<<0, /* n ports */
+};
+
+/* ghc bits */
+enum {
+ Hae = 1<<31, /* enable ahci */
+ Hie = 1<<1, /* " interrupts */
+ Hhr = 1<<0, /* hba reset */
+};
+
+/* cap2 bits */
+enum {
+ Apts = 1<<2, /* automatic partial to slumber */
+ Nvmp = 1<<1, /* nvmhci present; nvram */
+ Boh = 1<<0, /* bios/os handoff supported */
+};
+
+/* bios bits */
+enum {
+ Bos = 1<<0,
+ Oos = 1<<1,
+};
+
+/* emctl bits */
+enum {
+ Pm = 1<<27, /* port multiplier support */
+ Alhd = 1<<26, /* activity led hardware driven */
+ Xonly = 1<<25, /* rx messages not supported */
+ Smb = 1<<24, /* single msg buffer; rx limited */
+ Esgpio = 1<<19, /* sgpio messages supported */
+ Eses2 = 1<<18, /* ses-2 supported */
+ Esafte = 1<<17, /* saf-te supported */
+ Elmt = 1<<16, /* led msg types support */
+ Emrst = 1<<9, /* reset all em logic */
+ Tmsg = 1<<8, /* transmit message */
+ Mr = 1<<0, /* message rx'd */
+ Emtype = Esgpio | Eses2 | Esafte | Elmt,
+};
+
+typedef struct {
+ ulong cap;
+ ulong ghc;
+ ulong isr;
+ ulong pi; /* ports implemented */
+ ulong ver;
+ ulong ccc; /* coaleasing control */
+ ulong cccports;
+ ulong emloc;
+ ulong emctl;
+ ulong cap2;
+ ulong bios;
+} Ahba;
+
+enum {
+ Acpds = 1<<31, /* cold port detect status */
+ Atfes = 1<<30, /* task file error status */
+ Ahbfs = 1<<29, /* hba fatal */
+ Ahbds = 1<<28, /* hba error (parity error) */
+ Aifs = 1<<27, /* interface fatal §6.1.2 */
+ Ainfs = 1<<26, /* interface error (recovered) */
+ Aofs = 1<<24, /* too many bytes from disk */
+ Aipms = 1<<23, /* incorrect prt mul status */
+ Aprcs = 1<<22, /* PhyRdy change status Pxserr.diag.n */
+ Adpms = 1<<7, /* mechanical presence status */
+ Apcs = 1<<6, /* port connect diag.x */
+ Adps = 1<<5, /* descriptor processed */
+ Aufs = 1<<4, /* unknown fis diag.f */
+ Asdbs = 1<<3, /* set device bits fis received w/ i bit set */
+ Adss = 1<<2, /* dma setup */
+ Apio = 1<<1, /* pio setup fis */
+ Adhrs = 1<<0, /* device to host register fis */
+
+ IEM = Acpds|Atfes|Ahbds|Ahbfs|Ahbds|Aifs|Ainfs|Aprcs|Apcs|Adps|
+ Aufs|Asdbs|Adss|Adhrs,
+ Ifatal = Ahbfs|Ahbds|Aifs,
+};
+
+/* serror bits */
+enum {
+ SerrX = 1<<26, /* exchanged */
+ SerrF = 1<<25, /* unknown fis */
+ SerrT = 1<<24, /* transition error */
+ SerrS = 1<<23, /* link sequence */
+ SerrH = 1<<22, /* handshake */
+ SerrC = 1<<21, /* crc */
+ SerrD = 1<<20, /* not used by ahci */
+ SerrB = 1<<19, /* 10-tp-8 decode */
+ SerrW = 1<<18, /* comm wake */
+ SerrI = 1<<17, /* phy internal */
+ SerrN = 1<<16, /* phyrdy change */
+
+ ErrE = 1<<11, /* internal */
+ ErrP = 1<<10, /* ata protocol violation */
+ ErrC = 1<<9, /* communication */
+ ErrT = 1<<8, /* transient */
+ ErrM = 1<<1, /* recoverd comm */
+ ErrI = 1<<0, /* recovered data integrety */
+
+ ErrAll = ErrE|ErrP|ErrC|ErrT|ErrM|ErrI,
+ SerrAll = SerrX|SerrF|SerrT|SerrS|SerrH|SerrC|SerrD|SerrB|SerrW|
+ SerrI|SerrN|ErrAll,
+ SerrBad = 0x7f<<19,
+};
+
+/* cmd register bits */
+enum {
+ Aicc = 1<<28, /* interface communcations control. 4 bits */
+ Aasp = 1<<27, /* aggressive slumber & partial sleep */
+ Aalpe = 1<<26, /* aggressive link pm enable */
+ Adlae = 1<<25, /* drive led on atapi */
+ Aatapi = 1<<24, /* device is atapi */
+ Apste = 1<<23, /* automatic slumber to partial cap */
+ Afbsc = 1<<22, /* fis-based switching capable */
+ Aesp = 1<<21, /* external sata port */
+ Acpd = 1<<20, /* cold presence detect */
+ Ampsp = 1<<19, /* mechanical pres. */
+ Ahpcp = 1<<18, /* hot plug capable */
+ Apma = 1<<17, /* pm attached */
+ Acps = 1<<16, /* cold presence state */
+ Acr = 1<<15, /* cmdlist running */
+ Afr = 1<<14, /* fis running */
+ Ampss = 1<<13, /* mechanical presence switch state */
+ Accs = 1<<8, /* current command slot 12:08 */
+ Afre = 1<<4, /* fis enable receive */
+ Aclo = 1<<3, /* command list override */
+ Apod = 1<<2, /* power on dev (requires cold-pres. detect) */
+ Asud = 1<<1, /* spin-up device; requires ss capability */
+ Ast = 1<<0, /* start */
+
+ Arun = Ast|Acr|Afre|Afr,
+ Apwr = Apod|Asud,
+};
+
+/* ctl register bits */
+enum {
+ Aipm = 1<<8, /* interface power mgmt. 3=off */
+ Aspd = 1<<4,
+ Adet = 1<<0, /* device detection */
+};
+
+/* sstatus register bits */
+enum{
+ /* sstatus det */
+ Smissing = 0<<0,
+ Spresent = 1<<0,
+ Sphylink = 3<<0,
+ Sbist = 4<<0,
+ Smask = 7<<0,
+
+ /* sstatus speed */
+ Gmissing = 0<<4,
+ Gi = 1<<4,
+ Gii = 2<<4,
+ Giii = 3<<4,
+ Gmask = 7<<4,
+
+ /* sstatus ipm */
+ Imissing = 0<<8,
+ Iactive = 1<<8,
+ Isleepy = 2<<8,
+ Islumber = 6<<8,
+ Imask = 7<<8,
+
+ SImask = Smask | Imask,
+ SSmask = Smask | Isleepy,
+};
+
+#define sstatus scr0
+#define sctl scr2
+#define serror scr1
+#define sactive scr3
+#define ntf scr4
+
+typedef struct {
+ ulong list; /* PxCLB must be 1kb aligned */
+ ulong listhi;
+ ulong fis; /* 256-byte aligned */
+ ulong fishi;
+ ulong isr;
+ ulong ie; /* interrupt enable */
+ ulong cmd;
+ ulong res1;
+ ulong task;
+ ulong sig;
+ ulong scr0;
+ ulong scr2;
+ ulong scr1;
+ ulong scr3;
+ ulong ci; /* command issue */
+ ulong scr4;
+ ulong fbs;
+ ulong res2[11];
+ ulong vendor[4];
+} Aport;
+
+/* in host's memory; not memory mapped */
+typedef struct {
+ uchar *base;
+ uchar *d;
+ uchar *p;
+ uchar *r;
+ uchar *u;
+ ulong *devicebits;
+} Afis;
+
+enum {
+ Lprdtl = 1<<16, /* physical region descriptor table len */
+ Lpmp = 1<<12, /* port multiplier port */
+ Lclear = 1<<10, /* clear busy on R_OK */
+ Lbist = 1<<9,
+ Lreset = 1<<8,
+ Lpref = 1<<7, /* prefetchable */
+ Lwrite = 1<<6,
+ Latapi = 1<<5,
+ Lcfl = 1<<0, /* command fis length in double words */
+};
+
+/* in hosts memory; memory mapped */
+typedef struct {
+ ulong flags;
+ ulong len;
+ ulong ctab;
+ ulong ctabhi;
+ uchar reserved[16];
+} Alist;
+
+typedef struct {
+ ulong dba;
+ ulong dbahi;
+ ulong pad;
+ ulong count;
+} Aprdt;
+
+typedef struct {
+ uchar cfis[0x40];
+ uchar atapi[0x10];
+ uchar pad[0x30];
+ Aprdt prdt;
+} Actab;
+
+/* enclosure message header */
+enum {
+ Mled = 0,
+ Msafte = 1,
+ Mses2 = 2,
+ Msgpio = 3,
+};
+
+typedef struct {
+ uchar dummy;
+ uchar msize;
+ uchar dsize;
+ uchar type;
+ uchar hba; /* bits 0:4 are the port */
+ uchar pm;
+ uchar led[2];
+} Aledmsg;
+
+enum {
+ Aled = 1<<0,
+ Locled = 1<<3,
+ Errled = 1<<6,
+
+ Ledoff = 0,
+ Ledon = 1,
+};
+
+typedef struct {
+ uint encsz;
+ ulong *enctx;
+ ulong *encrx;
+} Aenc;
+
+enum {
+ Ferror = 1,
+ Fdone = 2,
+};
+
+typedef struct {
+ QLock;
+ Rendez;
+ uchar flag;
+ Sfis;
+ Afis fis;
+ Alist *list;
+ Actab *ctab;
+} Aportm;
+
+typedef struct {
+ Aport *p;
+ Aportm *m;
+} Aportc;
--- a/os/pc64/mkfile
+++ b/os/pc64/mkfile
@@ -101,6 +101,9 @@
apic.$O squidboy.$O: mp.h
archmp.$O archacpi.$O: mp.h
+$SDEV: ../port/sd.h
+sdiahci.$O: ahci.h
+
# to be moved to port/interp
bench.h:D: ../../module/bench.m
rm -f $target && limbo -a -I../../module ../../module/bench.m > $target
--- a/os/pc64/pc64
+++ b/os/pc64/pc64
@@ -40,6 +40,7 @@
# esp
# il
lib
+ fis
interp
keyring
draw
@@ -102,8 +103,7 @@
cga
uarti8250
-# below from 9front
-# sdiahci pci sdscsi led
+ sdiahci pci sdscsi led
sdvirtio pci sdscsi
vgasoft =cur swcursor
--- /dev/null
+++ b/os/port/led.c
@@ -1,0 +1,62 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "../port/error.h"
+#include "fns.h"
+#include "led.h"
+
+static char *ibpinames[Ibpilast] = {
+[Ibpinone] "none",
+[Ibpinormal] "normal",
+[Ibpilocate] "locate",
+[Ibpifail] "fail",
+[Ibpirebuild] "rebuild",
+[Ibpipfa] "pfa",
+[Ibpispare] "spare",
+[Ibpicritarray] "critarray",
+[Ibpifailarray] "failarray",
+};
+
+char*
+ledname(int c)
+{
+ if(c >= 0 && c < Ibpilast)
+ return ibpinames[c];
+ return "bad index";
+}
+
+ int
+name2led(char *s)
+{
+ int i;
+
+ for(i = 0; i < nelem(ibpinames); i++)
+ if(strcmp(ibpinames[i], s) == 0)
+ return i;
+ return -1;
+}
+
+long
+ledr(Ledport *p, Chan*, void *a, long n, vlong off)
+{
+ char buf[64];
+
+ snprint(buf, sizeof buf, "%s\n", ledname(p->led));
+ return readstr(off, a, n, buf);
+}
+
+long
+ledw(Ledport *p, Chan*, void *a, long n, vlong)
+{
+ int i;
+ Cmdbuf *cb;
+
+ cb = parsecmd(a, n);
+ i = cb->nf < 1 ? -1 : name2led(cb->f[0]);
+ free(cb);
+ if(i == -1)
+ error(Ebadarg);
+ p->led = i;
+ return n;
+}
--- /dev/null
+++ b/os/port/led.h
@@ -1,0 +1,26 @@
+typedef struct Ledport Ledport;
+
+struct Ledport {
+ uchar nled;
+ uchar led;
+ ushort ledbits; /* implementation dependent */
+};
+
+/* http://en.wikipedia.org/wiki/IBPI */
+enum {
+ Ibpinone,
+ Ibpinormal,
+ Ibpilocate,
+ Ibpifail,
+ Ibpirebuild,
+ Ibpipfa,
+ Ibpispare,
+ Ibpicritarray,
+ Ibpifailarray,
+ Ibpilast,
+};
+
+char *ledname(int);
+int name2led(char*);
+long ledr(Ledport*, Chan*, void*, long, vlong);
+long ledw(Ledport*, Chan*, void*, long, vlong);