diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/pc/sdiahci.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/pc/sdiahci.c')
-rwxr-xr-x | sys/src/9/pc/sdiahci.c | 2298 |
1 files changed, 2298 insertions, 0 deletions
diff --git a/sys/src/9/pc/sdiahci.c b/sys/src/9/pc/sdiahci.c new file mode 100755 index 000000000..73e46fc63 --- /dev/null +++ b/sys/src/9/pc/sdiahci.c @@ -0,0 +1,2298 @@ +/* + * intel/amd ahci sata controller + * copyright © 2007-8 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 "ahci.h" + +#define dprint(...) if(debug) iprint(__VA_ARGS__); else USED(debug) +#define idprint(...) if(prid) iprint(__VA_ARGS__); else USED(prid) +#define aprint(...) if(datapi) iprint(__VA_ARGS__); else USED(datapi) +#define Tname(c) tname[(c)->type] + +enum { + NCtlr = 4, + NCtlrdrv= 32, + NDrive = NCtlr*NCtlrdrv, + + Read = 0, + Write, +}; + +/* pci space configuration */ +enum { + Pmap = 0x90, + Ppcs = 0x91, + Prev = 0xa8, +}; + +enum { + Tesb, + Tich, + Tsb600, + Tunk, +}; + +#define Intel(x) ((x)->pci->vid == 0x8086) + +static char *tname[] = { + "63xxesb", + "ich", + "sb600", + "unk", +}; + +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, +}; + +static char *modename[] = { + "auto", + "satai", + "sataii", +}; + +static char *flagname[] = { + "llba", + "smart", + "power", + "nop", + "atapi", + "atapi16", +}; + +typedef struct { + Lock; + + Ctlr *ctlr; + SDunit *unit; + char name[10]; + Aport *port; + Aportm portm; + Aportc portc; /* redundant ptr to port and portm */ + + uchar mediachange; + uchar state; + uchar smartrs; + + uvlong sectors; + ulong secsize; + ulong intick; /* start tick of current transfer */ + ulong lastseen; + int wait; + uchar mode; /* DMautoneg, satai or sataii */ + uchar active; + + char serial[20+1]; + char firmware[8+1]; + char model[40+1]; + + ushort info[0x200]; + + int driveno; /* ctlr*NCtlrdrv + unit */ + /* controller port # != driveno when not all ports are enabled */ + int portno; +} Drive; + +struct Ctlr { + Lock; + + int type; + int enabled; + SDev *sdev; + Pcidev *pci; + + uchar *mmio; + ulong *lmmio; + Ahba *hba; + + Drive rawdrive[NCtlrdrv]; + Drive* drive[NCtlrdrv]; + int ndrive; + int mport; +}; + +static Ctlr iactlr[NCtlr]; +static SDev sdevs[NCtlr]; +static int niactlr; + +static Drive *iadrive[NDrive]; +static int niadrive; + +/* these are fiddled in iawtopctl() */ +static int debug; +static int prid = 1; +static int datapi; + +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) +{ + int i; + char buf[25*3+1], *e; + + 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("ahci: %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) +{ + Asleep as; + Aport *p; + + 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 timeout ", c->p); + return -1; +} + +static int +nop(Aportc *pc) +{ + uchar *c; + Actab *t; + Alist *l; + + if((pc->m->feat & Dnop) == 0) + return -1; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0x00; + c[7] = 0xa0; /* obsolete device bits */ + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + return ahciwait(pc, 3*1000); +} + +static int +setfeatures(Aportc *pc, uchar f) +{ + uchar *c; + Actab *t; + Alist *l; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0xef; + c[3] = f; + c[7] = 0xa0; /* obsolete device bits */ + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + return ahciwait(pc, 3*1000); +} + +static int +setudmamode(Aportc *pc, uchar f) +{ + uchar *c; + Actab *t; + Alist *l; + + /* hack */ + if((pc->p->sig >> 16) == 0xeb14) + return 0; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0xef; + c[3] = 3; /* set transfer mode */ + c[7] = 0xa0; /* obsolete device bits */ + c[12] = 0x40 | f; /* sector count */ + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + return ahciwait(pc, 3*1000); +} + +static void +asleep(int ms) +{ + if(up == nil) + delay(ms); + else + esleep(ms); +} + +static int +ahciportreset(Aportc *c) +{ + 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); + } + p->sctl = 1|(p->sctl&~7); + delay(1); + p->sctl &= ~7; + return 0; +} + +static int +smart(Aportc *pc, int n) +{ + uchar *c; + Actab *t; + Alist *l; + + if((pc->m->feat&Dsmart) == 0) + return -1; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0xb0; + c[3] = 0xd8 + n; /* able smart */ + c[5] = 0x4f; + c[6] = 0xc2; + c[7] = 0xa0; + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){ + dprint("ahci: smart fail %lux\n", pc->p->task); +// preg(pc->m->fis.r, 20); + return -1; + } + if(n) + return 0; + return 1; +} + +static int +smartrs(Aportc *pc) +{ + uchar *c; + Actab *t; + Alist *l; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0xb0; + c[3] = 0xda; /* return smart status */ + c[5] = 0x4f; + c[6] = 0xc2; + c[7] = 0xa0; + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + c = pc->m->fis.r; + if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){ + dprint("ahci: smart fail %lux\n", pc->p->task); + preg(c, 20); + return -1; + } + if(c[5] == 0x4f && c[6] == 0xc2) + return 1; + return 0; +} + +static int +ahciflushcache(Aportc *pc) +{ + uchar *c, llba; + Actab *t; + Alist *l; + static uchar tab[2] = {0xe7, 0xea}; + + llba = pc->m->feat&Dllba? 1: 0; + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = tab[llba]; + c[7] = 0xa0; + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){ + dprint("ahciflushcache: fail %lux\n", pc->p->task); +// preg( pc->m->fis.r, 20); + return -1; + } + return 0; +} + +static ushort +gbit16(void *a) +{ + uchar *i; + + i = a; + return i[1]<<8 | i[0]; +} + +static ulong +gbit32(void *a) +{ + ulong 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); +} + +static int +ahciidentify0(Aportc *pc, void *id, int atapi) +{ + uchar *c; + Actab *t; + Alist *l; + Aprdt *p; + static uchar tab[] = { 0xec, 0xa1, }; + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x80; + c[2] = tab[atapi]; + c[7] = 0xa0; /* obsolete device bits */ + + l = pc->m->list; + l->flags = 1<<16 | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + memset(id, 0, 0x100); + p = &t->prdt; + p->dba = PCIWADDR(id); + p->dbahi = 0; + p->count = 1<<31 | (0x200-2) | 1; + + return ahciwait(pc, 3*1000); +} + +static vlong +ahciidentify(Aportc *pc, ushort *id) +{ + int i, sig; + vlong s; + Aportm *m; + + m = pc->m; + m->feat = 0; + m->smart = 0; + i = 0; + sig = pc->p->sig >> 16; + if(sig == 0xeb14){ + m->feat |= Datapi; + i = 1; + } + if(ahciidentify0(pc, id, i) == -1) + return -1; + + i = gbit16(id+83) | gbit16(id+86); + if(i & (1<<10)){ + m->feat |= Dllba; + s = gbit64(id+100); + }else + s = gbit32(id+60); + + if(m->feat&Datapi){ + i = gbit16(id+0); + if(i&1) + m->feat |= Datapi16; + } + + i = gbit16(id+83); + if((i>>14) == 1) { + if(i & (1<<3)) + m->feat |= Dpower; + i = gbit16(id+82); + if(i & 1) + m->feat |= Dsmart; + if(i & (1<<14)) + m->feat |= Dnop; + } + 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 %lx\n", a->task); + if(a->task & ASbsy) + return -1; + *p |= Ast; + return 0; +} + +static int +ahcicomreset(Aportc *pc) +{ + uchar *c; + Actab *t; + Alist *l; + + dprint("ahcicomreset\n"); + dreg("ahci: comreset ", pc->p); + if(ahciquiet(pc->p) == -1){ + dprint("ahciquiet failed\n"); + return -1; + } + dreg("comreset ", pc->p); + + t = pc->m->ctab; + c = t->cfis; + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x00; + c[7] = 0xa0; /* obsolete device bits */ + c[15] = 1<<2; /* srst */ + + l = pc->m->list; + l->flags = Lclear | Lreset | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + if(ahciwait(pc, 500) == -1){ + dprint("ahcicomreset: first command failed\n"); + return -1; + } + microdelay(250); + dreg("comreset ", pc->p); + + memset(c, 0, 0x20); + c[0] = 0x27; + c[1] = 0x00; + c[7] = 0xa0; /* obsolete device bits */ + + l = pc->m->list; + l->flags = Lwrite | 0x5; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + if(ahciwait(pc, 150) == -1){ + dprint("ahcicomreset: second command 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(setudmamode(pc, 5) == -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(Aport *p) +{ + ushort s; + + s = p->sstatus; + if((s & 0xF00) != 0x600) + return; + if((s & 7) != 1){ /* not (device, no phy) */ + iprint("ahci: slumbering drive unwakable %ux\n", s); + return; + } + p->sctl = 3*Aipm | 0*Aspd | Adet; + delay(1); + p->sctl &= ~7; +// iprint("ahci: wake %ux -> %ux\n", s, p->sstatus); +} + +static int +ahciconfigdrive(Ahba *h, Aportc *c, int mode) +{ + Aportm *m; + Aport *p; + + 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(p->sstatus & 3 && h->cap & Hsss){ + /* device connected & staggered spin-up */ + dprint("ahci: configdrive: spinning up ... [%lux]\n", + p->sstatus); + p->cmd |= Apod|Asud; + asleep(1400); + } + + p->serror = SerrAll; + + p->list = PCIWADDR(m->list); + p->listhi = 0; + p->fis = PCIWADDR(m->fis.base); + p->fishi = 0; + p->cmd |= Afre|Ast; + + if((p->sstatus & 0xF0F) == 0x601) /* drive coming up in slumbering? */ + ahciwakeup(p); + + /* disable power managment sequence from book. */ + p->sctl = (3*Aipm) | (mode*Aspd) | (0*Adet); + p->cmd &= ~Aalpe; + + p->ie = IEM; + + 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 *ctlr) +{ + Ahba *h; + ulong u; + + h = ctlr->hba = (Ahba*)ctlr->mmio; + u = h->cap; + + if((u&Hsam) == 0) + h->ghc |= Hae; + + print("#S/sd%c: ahci %s port %#p: sss %ld ncs %ld coal %ld " + "mports %ld led %ld clo %ld ems %ld\n", + ctlr->sdev->idno, tname[ctlr->type], h, + (u>>27) & 1, (u>>8) & 0x1f, (u>>7) & 1, u & 0x1f, (u>>25) & 1, + (u>>24) & 1, (u>>6) & 1); + return countbits(h->pi); +} + +static int +ahcihbareset(Ahba *h) +{ + int wait; + + h->ghc |= 1; + for(wait = 0; wait < 1000; wait += 100){ + if(h->ghc == 0) + return 0; + delay(100); + } + return -1; +} + +static void +idmove(char *p, ushort *a, int n) +{ + int i; + char *op, *e; + + op = p; + for(i = 0; i < n/2; i++){ + *p++ = a[i] >> 8; + *p++ = a[i]; + } + *p = 0; + while(p > op && *--p == ' ') + *p = 0; + e = p; + for (p = op; *p == ' '; p++) + ; + memmove(op, p, n - (e - p)); +} + +static int +identify(Drive *d) +{ + ushort *id; + vlong osectors, s; + uchar oserial[21]; + SDunit *u; + + id = d->info; + s = ahciidentify(&d->portc, id); + if(s == -1){ + d->state = Derror; + return -1; + } + osectors = d->sectors; + memmove(oserial, d->serial, sizeof d->serial); + + u = d->unit; + d->sectors = s; + d->secsize = u->secsize; + if(d->secsize == 0) + d->secsize = 512; /* default */ + d->smartrs = 0; + + idmove(d->serial, id+10, 20); + idmove(d->firmware, id+23, 8); + idmove(d->model, id+27, 40); + + 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) != 0){ + d->mediachange = 1; + u->sectors = 0; + } + return 0; +} + +static void +clearci(Aport *p) +{ + if(p->cmd & Ast) { + p->cmd &= ~Ast; + p->cmd |= Ast; + } +} + +static void +updatedrive(Drive *d) +{ + ulong cause, serr, s0, pr, ewake; + char *name; + Aport *p; + static ulong last; + + pr = 1; + ewake = 0; + p = d->port; + cause = p->isr; + serr = p->serror; + p->isr = cause; + name = "??"; + if(d->unit && d->unit->name) + name = d->unit->name; + + if(p->ci == 0){ + d->portm.flag |= Fdone; + wakeup(&d->portm); + pr = 0; + }else if(cause & Adps) + pr = 0; + if(cause & Ifatal){ + ewake = 1; + dprint("ahci: updatedrive: fatal\n"); + } + if(cause & Adhrs){ + if(p->task & (1<<5|1)){ + dprint("ahci: Adhrs cause %lux serr %lux task %lux\n", + cause, serr, p->task); + d->portm.flag |= Ferror; + ewake = 1; + } + pr = 0; + } + if(p->task & 1 && last != cause) + dprint("%s: err ca %lux serr %lux task %lux sstat %lux\n", + name, cause, serr, p->task, p->sstatus); + if(pr) + dprint("%s: upd %lux ta %lux\n", name, cause, p->task); + + if(cause & (Aprcs|Aifs)){ + s0 = d->state; + switch(p->sstatus & 7){ + case 0: /* no device */ + d->state = Dmissing; + break; + case 1: /* device but no phy comm. */ + if((p->sstatus & 0xF00) == 0x600) + d->state = Dnew; /* slumbering */ + else + d->state = Derror; + break; + case 3: /* device & phy comm. estab. */ + /* power mgnt crap for surprise removal */ + p->ie |= Aprcs|Apcs; /* is this required? */ + d->state = Dreset; + break; + case 4: /* phy off-line */ + d->state = Doffline; + break; + } + dprint("%s: %s → %s [Apcrs] %lux\n", name, + diskstates[s0], diskstates[d->state], p->sstatus); + /* print pulled message here. */ + if(s0 == Dready && d->state != Dready) + idprint("%s: pulled\n", name); + if(d->state != Dready) + d->portm.flag |= Ferror; + ewake = 1; + } + p->serror = serr; + if(ewake){ + clearci(p); + wakeup(&d->portm); + } + last = cause; +} + +static void +pstatus(Drive *d, ulong s) +{ + /* + * bogus code because the first interrupt is currently dropped. + * likely my fault. serror may be cleared at the wrong time. + */ + switch(s){ + case 0: /* no device */ + d->state = Dmissing; + break; + case 1: /* device but no phy. comm. */ + break; + case 2: /* should this be missing? need testcase. */ + dprint("ahci: pstatus 2\n"); + /* fallthrough */ + case 3: /* device & phy. comm. */ + d->wait = 0; + d->state = Dnew; + break; + case 4: /* offline */ + d->state = Doffline; + break; + case 6: /* ? not sure this makes sense. TODO */ + d->state = Dnew; + break; + } +} + +static int +configdrive(Drive *d) +{ + if(ahciconfigdrive(d->ctlr->hba, &d->portc, d->mode) == -1) + return -1; + ilock(d); + pstatus(d, d->port->sstatus & 7); + iunlock(d); + return 0; +} + +static void +resetdisk(Drive *d) +{ + uint state, det, stat; + Aport *p; + + p = d->port; + det = p->sctl & 7; + stat = p->sstatus & 7; + state = (p->cmd>>28) & 0xf; + dprint("ahci: resetdisk: icc %ux det %d sdet %d\n", state, det, stat); + + ilock(d); + state = d->state; + if(d->state != Dready || d->state != Dnew) + d->portm.flag |= Ferror; + clearci(p); /* satisfy sleep condition. */ + wakeup(&d->portm); + if(stat != 3){ /* device absent or phy not communicating? */ + d->state = Dportreset; + iunlock(d); + return; + } + d->state = Derror; + iunlock(d); + + qlock(&d->portm); + if(p->cmd&Ast && ahciswreset(&d->portc) == -1){ + ilock(d); + d->state = Dportreset; /* get a bigger stick. */ + iunlock(d); + } else { + ilock(d); + d->state = Dmissing; + iunlock(d); + + configdrive(d); + } + dprint("ahci: resetdisk: %s → %s\n", + diskstates[state], diskstates[d->state]); + qunlock(&d->portm); +} + +static int +newdrive(Drive *d) +{ + char *name; + Aportc *c; + Aportm *m; + + c = &d->portc; + m = &d->portm; + + name = d->unit->name; + if(name == 0) + name = "??"; + + if(d->port->task == 0x80) + return -1; + qlock(c->m); + if(setudmamode(c, 5) == -1){ + dprint("%s: can't set udma mode\n", name); + goto lose; + } + if(identify(d) == -1){ + dprint("%s: identify failure\n", name); + goto lose; + } + if(m->feat & Dpower && setfeatures(c, 0x85) == -1){ + m->feat &= ~Dpower; + if(ahcirecover(c) == -1) + goto lose; + } + + ilock(d); + d->state = Dready; + iunlock(d); + + qunlock(c->m); + + idprint("%s: %sLBA %,llud sectors: %s %s %s %s\n", d->unit->name, + (m->feat & Dllba? "L": ""), d->sectors, d->model, d->firmware, + d->serial, d->mediachange? "[mediachange]": ""); + return 0; + +lose: + idprint("%s: can't be initialized\n", d->unit->name); + ilock(d); + d->state = Dnull; + iunlock(d); + qunlock(c->m); + return -1; +} + +enum { + Nms = 256, + Mphywait = 2*1024/Nms - 1, + Midwait = 16*1024/Nms - 1, + Mcomrwait = 64*1024/Nms - 1, +}; + +static void +westerndigitalhung(Drive *d) +{ + if((d->portm.feat&Datapi) == 0 && d->active && + TK2MS(MACHP(0)->ticks - d->intick) > 5000){ + dprint("%s: drive hung; resetting [%lux] ci %lx\n", + d->unit->name, d->port->task, d->port->ci); + d->state = Dreset; + } +} + +static ushort olds[NCtlr*NCtlrdrv]; + +static int +doportreset(Drive *d) +{ + int i; + + i = -1; + qlock(&d->portm); + if(ahciportreset(&d->portc) == -1) + dprint("ahci: doportreset: fails\n"); + else + i = 0; + qunlock(&d->portm); + dprint("ahci: doportreset: portreset → %s [task %lux]\n", + diskstates[d->state], d->port->task); + return i; +} + +/* drive must be locked */ +static void +statechange(Drive *d) +{ + switch(d->state){ + case Dnull: + case Doffline: + if(d->unit->sectors != 0){ + d->sectors = 0; + d->mediachange = 1; + } + /* fallthrough */ + case Dready: + d->wait = 0; + break; + } +} + +static void +checkdrive(Drive *d, int i) +{ + ushort s; + char *name; + + if(d == nil) { + print("checkdrive: nil d\n"); + return; + } + ilock(d); + if(d->unit == nil || d->port == nil) { + if(0) + print("checkdrive: nil d->%s\n", + d->unit == nil? "unit": "port"); + iunlock(d); + return; + } + name = d->unit->name; + s = d->port->sstatus; + if(s) + d->lastseen = MACHP(0)->ticks; + if(s != olds[i]){ + dprint("%s: status: %04ux -> %04ux: %s\n", + name, olds[i], s, diskstates[d->state]); + olds[i] = s; + d->wait = 0; + } + westerndigitalhung(d); + switch(d->state){ + case Dnull: + case Dready: + break; + case Dmissing: + case Dnew: + switch(s & 0x107){ + case 1: /* no device (pm), device but no phy. comm. */ + ahciwakeup(d->port); + /* fall through */ + case 0: /* no device */ + break; + default: + dprint("%s: unknown status %04ux\n", name, s); + /* fall through */ + case 0x100: /* active, no device */ + if(++d->wait&Mphywait) + break; +reset: + if(++d->mode > DMsataii) + d->mode = 0; + if(d->mode == DMsatai){ /* we tried everything */ + d->state = Dportreset; + goto portreset; + } + dprint("%s: reset; new mode %s\n", name, + modename[d->mode]); + iunlock(d); + resetdisk(d); + ilock(d); + break; + case 0x103: /* active, device, phy. comm. */ + if((++d->wait&Midwait) == 0){ + dprint("%s: slow reset %04ux task=%lux; %d\n", + name, s, d->port->task, d->wait); + goto reset; + } + s = (uchar)d->port->task; + if(s == 0x7f || ((d->port->sig >> 16) != 0xeb14 && + (s & ~0x17) != (1<<6))) + break; + iunlock(d); + newdrive(d); + ilock(d); + break; + } + break; + case Doffline: + if(d->wait++ & Mcomrwait) + break; + /* fallthrough */ + case Derror: + case Dreset: + dprint("%s: reset [%s]: mode %d; status %04ux\n", + name, diskstates[d->state], d->mode, s); + iunlock(d); + resetdisk(d); + ilock(d); + break; + case Dportreset: +portreset: + if(d->wait++ & 0xff && (s & 0x100) == 0) + break; + /* device is active */ + dprint("%s: portreset [%s]: mode %d; status %04ux\n", + name, diskstates[d->state], d->mode, s); + d->portm.flag |= Ferror; + clearci(d->port); + wakeup(&d->portm); + if((s & 7) == 0){ /* no device */ + d->state = Dmissing; + break; + } + iunlock(d); + doportreset(d); + ilock(d); + break; + } + statechange(d); + iunlock(d); +} + +static void +satakproc(void*) +{ + int i; + + for(;;){ + tsleep(&up->sleep, return0, 0, Nms); + for(i = 0; i < niadrive; i++) + checkdrive(iadrive[i], i); + } +} + +static void +iainterrupt(Ureg*, void *a) +{ + int i; + ulong cause, m; + Ctlr *c; + Drive *d; + + c = a; + ilock(c); + cause = c->hba->isr; + for(i = 0; i < c->mport; i++){ + m = 1 << i; + if((cause & m) == 0) + continue; + d = c->rawdrive + i; + ilock(d); + if(d->port->isr && c->hba->pi & m) + updatedrive(d); + c->hba->isr = m; + iunlock(d); + } + iunlock(c); +} + +static int +iaverify(SDunit *u) +{ + Ctlr *c; + Drive *d; + + c = u->dev->ctlr; + d = c->drive[u->subno]; + ilock(c); + ilock(d); + d->unit = u; + iunlock(d); + iunlock(c); + checkdrive(d, d->driveno); /* c->d0 + d->driveno */ + return 1; +} + +static int +iaenable(SDev *s) +{ + char name[32]; + Ctlr *c; + static int once; + + c = s->ctlr; + ilock(c); + if(!c->enabled) { + if(once == 0) { + once = 1; + kproc("iasata", satakproc, 0); + } + if(c->ndrive == 0) + panic("iaenable: zero s->ctlr->ndrive"); + pcisetbme(c->pci); + 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); + 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 int +iaonline(SDunit *unit) +{ + int r; + Ctlr *c; + Drive *d; + + c = unit->dev->ctlr; + d = c->drive[unit->subno]; + r = 0; + + if(d->portm.feat & Datapi && d->mediachange){ + r = scsionline(unit); + if(r > 0) + d->mediachange = 0; + return r; + } + + ilock(d); + if(d->mediachange){ + r = 2; + d->mediachange = 0; + /* devsd resets this after online is called; why? */ + unit->sectors = d->sectors; + unit->secsize = 512; /* default size */ + } else if(d->state == Dready) + r = 1; + iunlock(d); + return r; +} + +/* returns locked list! */ +static Alist* +ahcibuild(Drive *d, uchar *cmd, void *data, int n, vlong lba) +{ + uchar *c, acmd, dir, llba; + Alist *l; + Actab *t; + Aportm *m; + Aprdt *p; + static uchar tab[2][2] = { 0xc8, 0x25, 0xca, 0x35, }; + + m = &d->portm; + dir = *cmd != 0x28; + llba = m->feat&Dllba? 1: 0; + acmd = tab[dir][llba]; + qlock(m); + l = m->list; + t = m->ctab; + c = t->cfis; + + c[0] = 0x27; + c[1] = 0x80; + c[2] = acmd; + c[3] = 0; + + c[4] = lba; /* sector lba low 7:0 */ + c[5] = lba >> 8; /* cylinder low lba mid 15:8 */ + c[6] = lba >> 16; /* cylinder hi lba hi 23:16 */ + c[7] = 0xa0 | 0x40; /* obsolete device bits + lba */ + if(llba == 0) + c[7] |= (lba>>24) & 7; + + c[8] = lba >> 24; /* sector (exp) lba 31:24 */ + c[9] = lba >> 32; /* cylinder low (exp) lba 39:32 */ + c[10] = lba >> 48; /* cylinder hi (exp) lba 48:40 */ + c[11] = 0; /* features (exp); */ + + c[12] = n; /* sector count */ + c[13] = n >> 8; /* sector count (exp) */ + c[14] = 0; /* r */ + c[15] = 0; /* control */ + + *(ulong*)(c + 16) = 0; + + l->flags = 1<<16 | Lpref | 0x5; /* Lpref ?? */ + if(dir == Write) + l->flags |= Lwrite; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + p = &t->prdt; + p->dba = PCIWADDR(data); + p->dbahi = 0; + if(d->unit == nil) + panic("ahcibuild: nil d->unit"); + p->count = 1<<31 | (d->unit->secsize*n - 2) | 1; + + return l; +} + +static Alist* +ahcibuildpkt(Aportm *m, SDreq *r, void *data, int n) +{ + int fill, len; + uchar *c; + Alist *l; + Actab *t; + Aprdt *p; + + qlock(m); + l = m->list; + t = m->ctab; + c = t->cfis; + + fill = m->feat&Datapi16? 16: 12; + if((len = r->clen) > fill) + len = fill; + memmove(t->atapi, r->cmd, len); + memset(t->atapi+len, 0, fill-len); + + c[0] = 0x27; + c[1] = 0x80; + c[2] = 0xa0; + if(n != 0) + c[3] = 1; /* dma */ + else + c[3] = 0; /* features (exp); */ + + c[4] = 0; /* sector lba low 7:0 */ + c[5] = n; /* cylinder low lba mid 15:8 */ + c[6] = n >> 8; /* cylinder hi lba hi 23:16 */ + c[7] = 0xa0; /* obsolete device bits */ + + *(ulong*)(c + 8) = 0; + *(ulong*)(c + 12) = 0; + *(ulong*)(c + 16) = 0; + + l->flags = 1<<16 | Lpref | Latapi | 0x5; + if(r->write != 0 && data) + l->flags |= Lwrite; + l->len = 0; + l->ctab = PCIWADDR(t); + l->ctabhi = 0; + + if(data == 0) + return l; + + p = &t->prdt; + p->dba = PCIWADDR(data); + p->dbahi = 0; + p->count = 1<<31 | (n - 2) | 1; + + return l; +} + +static int +waitready(Drive *d) +{ + ulong s, i, δ; + + for(i = 0; i < 15000; i += 250){ + if(d->state == Dreset || d->state == Dportreset || + d->state == Dnew) + return 1; + δ = MACHP(0)->ticks - d->lastseen; + if(d->state == Dnull || δ > 10*1000) + return -1; + ilock(d); + s = d->port->sstatus; + iunlock(d); + if((s & 0x700) == 0 && δ > 1500) + return -1; /* no detect */ + if(d->state == Dready && (s & 7) == 3) + return 0; /* ready, present & phy. comm. */ + esleep(250); + } + print("%s: not responding; offline\n", d->unit->name); + ilock(d); + d->state = Doffline; + iunlock(d); + return -1; +} + +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 +iariopkt(SDreq *r, Drive *d) +{ + int n, count, try, max, flag, task; + char *name; + uchar *cmd, *data; + Aport *p; + Asleep as; + + cmd = r->cmd; + name = d->unit->name; + p = d->port; + + aprint("ahci: iariopkt: %02ux %02ux %c %d %p\n", + cmd[0], cmd[2], "rw"[r->write], r->dlen, r->data); + if(cmd[0] == 0x5a && (cmd[2] & 0x3f) == 0x3f) + return sdmodesense(r, cmd, d->info, sizeof d->info); + r->rlen = 0; + count = r->dlen; + max = 65536; + + try = 0; +retry: + data = r->data; + n = count; + if(n > max) + n = max; + ahcibuildpkt(&d->portm, r, data, n); + switch(waitready(d)){ + case -1: + qunlock(&d->portm); + return SDeio; + case 1: + qunlock(&d->portm); + esleep(1); + goto retry; + } + + ilock(d); + d->portm.flag = 0; + iunlock(d); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = MACHP(0)->ticks; + d->active++; + + while(waserror()) + ; + sleep(&d->portm, ahciclear, &as); + poperror(); + + d->active--; + ilock(d); + flag = d->portm.flag; + task = d->port->task; + iunlock(d); + + if(task & (Efatal<<8) || task & (ASbsy|ASdrq) && d->state == Dready){ + d->port->ci = 0; + ahcirecover(&d->portc); + task = d->port->task; + flag &= ~Fdone; /* either an error or do-over */ + } + qunlock(&d->portm); + if(flag == 0){ + if(++try == 10){ + print("%s: bad disk\n", name); + r->status = SDcheck; + return SDcheck; + } + print("%s: retry\n", name); + goto retry; + } + if(flag & Ferror){ + if((task&Eidnf) == 0) + print("%s: i/o error %ux\n", name, task); + r->status = SDcheck; + return SDcheck; + } + + data += n; + + r->rlen = data - (uchar*)r->data; + r->status = SDok; + return SDok; +} + +static int +iario(SDreq *r) +{ + int i, n, count, try, max, flag, task; + vlong lba; + char *name; + uchar *cmd, *data; + Aport *p; + Asleep as; + Ctlr *c; + Drive *d; + SDunit *unit; + + unit = r->unit; + c = unit->dev->ctlr; + d = c->drive[unit->subno]; + if(d->portm.feat & Datapi) + return iariopkt(r, d); + cmd = r->cmd; + name = d->unit->name; + p = d->port; + + if(r->cmd[0] == 0x35 || r->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, d->info, sizeof d->info)) != SDnostatus){ + r->status = i; + return i; + } + + if(*cmd != 0x28 && *cmd != 0x2a){ + print("%s: bad cmd 0x%.2ux\n", name, cmd[0]); + r->status = SDcheck; + return SDcheck; + } + + lba = cmd[2]<<24 | cmd[3]<<16 | cmd[4]<<8 | cmd[5]; + count = cmd[7]<<8 | cmd[8]; + if(r->data == nil) + return SDok; + if(r->dlen < count * unit->secsize) + count = r->dlen / unit->secsize; + max = 128; + + try = 0; +retry: + data = r->data; + while(count > 0){ + n = count; + if(n > max) + n = max; + ahcibuild(d, cmd, data, n, lba); + switch(waitready(d)){ + case -1: + qunlock(&d->portm); + return SDeio; + case 1: + qunlock(&d->portm); + esleep(1); + goto retry; + } + ilock(d); + d->portm.flag = 0; + iunlock(d); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = MACHP(0)->ticks; + d->active++; + + while(waserror()) + ; + sleep(&d->portm, ahciclear, &as); + poperror(); + + d->active--; + ilock(d); + flag = d->portm.flag; + task = d->port->task; + iunlock(d); + + if(task & (Efatal<<8) || + task & (ASbsy|ASdrq) && d->state == Dready){ + d->port->ci = 0; + ahcirecover(&d->portc); + task = d->port->task; + } + qunlock(&d->portm); + if(flag == 0){ + if(++try == 10){ + print("%s: bad disk\n", name); + r->status = SDeio; + return SDeio; + } + iprint("%s: retry %lld\n", name, lba); + goto retry; + } + if(flag & Ferror){ + iprint("%s: i/o error %ux @%,lld\n", name, task, lba); + r->status = SDeio; + return SDeio; + } + + count -= n; + lba += n; + data += n * unit->secsize; + } + r->rlen = data - (uchar*)r->data; + r->status = SDok; + return SDok; +} + +/* + * configure drives 0-5 as ahci sata (c.f. errata) + */ +static int +iaahcimode(Pcidev *p) +{ + dprint("iaahcimode: %ux %ux %ux\n", pcicfgr8(p, 0x91), pcicfgr8(p, 92), + pcicfgr8(p, 93)); + pcicfgw16(p, 0x92, pcicfgr32(p, 0x92) | 0xf); /* ports 0-3 */ +// pcicfgw8(p, 0x93, pcicfgr32(p, 9x93) | 3); /* ports 4-5 */ + return 0; +} + +static void +iasetupahci(Ctlr *c) +{ + /* disable cmd block decoding. */ + pcicfgw16(c->pci, 0x40, pcicfgr16(c->pci, 0x40) & ~(1<<15)); + pcicfgw16(c->pci, 0x42, pcicfgr16(c->pci, 0x42) & ~(1<<15)); + + c->lmmio[0x4/4] |= 1 << 31; /* enable ahci mode (ghc register) */ + c->lmmio[0xc/4] = (1 << 6) - 1; /* 5 ports. (supposedly ro pi reg.) */ + + /* enable ahci mode; from ich9 datasheet */ + pcicfgw16(c->pci, 0x90, 1<<6 | 1<<5); +} + +static int +didtype(Pcidev *p) +{ + switch(p->vid){ + case 0x8086: + if((p->did & 0xfffc) == 0x2680) + return Tesb; + /* + * 0x27c4 is the intel 82801 in compatibility (not sata) mode. + */ + if ((p->did & 0xfffb) == 0x27c1 || /* 82801g[bh]m ich7 */ + p->did == 0x2821 || /* 82801h[roh] */ + (p->did & 0xfffe) == 0x2824 || /* 82801h[b] */ + (p->did & 0xfeff) == 0x2829 || /* ich8/9m */ + (p->did & 0xfffe) == 0x2922 || /* ich9 */ + p->did == 0x3a02 || /* 82801jd/do */ + (p->did & 0xfefe) == 0x3a22 || /* ich10, pch */ + (p->did & 0xfff7) == 0x3b28) /* pchm */ + return Tich; + break; + case 0x1002: + if(p->did == 0x4380) + return Tsb600; + break; + } + if(p->ccrb == Pcibcstore && p->ccru == 6 && p->ccrp == 1) + return Tunk; + return -1; +} + +static SDev* +iapnp(void) +{ + int i, n, nunit, type; + ulong io; + Ctlr *c; + Drive *d; + Pcidev *p; + SDev *head, *tail, *s; + static int done; + + if(done++) + return nil; + + memset(olds, 0xff, sizeof olds); + p = nil; + head = tail = nil; +loop: + while((p = pcimatch(p, 0, 0)) != nil){ + type = didtype(p); + if (type == -1 || p->mem[Abar].bar == 0) + continue; + if(niactlr == NCtlr){ + print("ahci: 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); + io = p->mem[Abar].bar & ~0xf; + c->mmio = vmap(io, p->mem[Abar].size); + if(c->mmio == 0){ + print("ahci: %s: address 0x%luX in use did=%x\n", + Tname(c), io, p->did); + continue; + } + c->lmmio = (ulong*)c->mmio; + c->pci = p; + c->type = type; + + s->ifc = &sdiahciifc; + s->idno = 'E' + niactlr; + s->ctlr = c; + c->sdev = s; + + if(Intel(c) && p->did != 0x2681) + iasetupahci(c); + nunit = ahciconf(c); +// ahcihbareset((Ahba*)c->mmio); + if(Intel(c) && iaahcimode(p) == -1) + break; + if(nunit < 1){ + vunmap(c->mmio, p->mem[Abar].size); + continue; + } + c->ndrive = s->nunit = nunit; + c->mport = c->hba->cap & ((1<<5)-1); + + i = (c->hba->cap >> 21) & 1; + print("#S/sd%c: %s: sata-%s with %d ports\n", s->idno, + Tname(c), "I\0II" + i*2, 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; + d->port = (Aport*)(c->mmio + 0x80*i + 0x100); + d->portc.p = d->port; + d->portc.m = &d->portm; + d->driveno = n++; + c->drive[d->driveno] = d; + iadrive[niadrive + d->driveno] = d; + } + for(i = 0; i < n; i++) + if(ahciidle(c->drive[i]->port) == -1){ + dprint("ahci: %s: port %d wedged; abort\n", + Tname(c), i); + goto loop; + } + for(i = 0; i < n; i++){ + c->drive[i]->mode = DMsatai; + configdrive(c->drive[i]); + } + + niadrive += n; + niactlr++; + if(head) + tail->next = s; + else + head = s; + tail = s; + } + return head; +} + +static char* smarttab[] = { + "unset", + "error", + "threshold exceeded", + "normal" +}; + +static char * +pflag(char *s, char *e, uchar f) +{ + uchar i; + + for(i = 0; i < 8; i++) + if(f & (1 << i)) + s = seprint(s, e, "%s ", flagname[i]); + return seprint(s, e, "\n"); +} + +static int +iarctl(SDunit *u, char *p, int l) +{ + char buf[32]; + char *e, *op; + Aport *o; + Ctlr *c; + Drive *d; + + c = u->dev->ctlr; + if(c == nil) { +print("iarctl: nil u->dev->ctlr\n"); + 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->smartrs == 0xff) + p = seprint(p, e, "smart\tenable error\n"); + else if(d->smartrs == 0) + p = seprint(p, e, "smart\tdisabled\n"); + else + p = seprint(p, e, "smart\t%s\n", + smarttab[d->portm.smart]); + p = seprint(p, e, "flag\t"); + p = pflag(p, e, d->portm.feat); + }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 %04lux\n", o->task, o->cmd, o->serror, buf, + o->ci, o->isr, o->sig, o->sstatus); + if(d->unit == nil) + panic("iarctl: nil d->unit"); + p = seprint(p, e, "geometry %llud %lud\n", d->sectors, d->unit->secsize); + return p - op; +} + +static void +runflushcache(Drive *d) +{ + long t0; + + t0 = MACHP(0)->ticks; + if(flushcache(d) != 0) + error(Eio); + dprint("ahci: flush in %ld ms\n", MACHP(0)->ticks - t0); +} + +static void +forcemode(Drive *d, char *mode) +{ + int i; + + for(i = 0; i < nelem(modename); i++) + if(strcmp(mode, modename[i]) == 0) + break; + if(i == nelem(modename)) + i = 0; + ilock(d); + d->mode = i; + iunlock(d); +} + +static void +runsmartable(Drive *d, int i) +{ + if(waserror()){ + qunlock(&d->portm); + d->smartrs = 0; + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + d->smartrs = smart(&d->portc, i); + d->portm.smart = 0; + qunlock(&d->portm); + poperror(); +} + +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); + ilock(d); + d->state = i; + iunlock(d); +} + + +static int +iawctl(SDunit *u, Cmdbuf *cmd) +{ + char **f; + Ctlr *c; + Drive *d; + uint i; + + c = u->dev->ctlr; + d = c->drive[u->subno]; + f = cmd->f; + + if(strcmp(f[0], "flushcache") == 0) + runflushcache(d); + else if(strcmp(f[0], "identify") == 0){ + i = strtoul(f[1]? f[1]: "0", 0, 0); + if(i > 0xff) + i = 0; + dprint("ahci: %04d %ux\n", i, d->info[i]); + }else if(strcmp(f[0], "mode") == 0) + forcemode(d, f[1]? f[1]: "satai"); + else if(strcmp(f[0], "nop") == 0){ + if((d->portm.feat & Dnop) == 0){ + cmderror(cmd, "no drive support"); + return -1; + } + if(waserror()){ + qunlock(&d->portm); + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + nop(&d->portc); + qunlock(&d->portm); + poperror(); + }else if(strcmp(f[0], "reset") == 0) + forcestate(d, "reset"); + else if(strcmp(f[0], "smart") == 0){ + if(d->smartrs == 0){ + cmderror(cmd, "smart not enabled"); + return -1; + } + if(waserror()){ + qunlock(&d->portm); + d->smartrs = 0; + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + d->portm.smart = 2 + smartrs(&d->portc); + qunlock(&d->portm); + poperror(); + }else if(strcmp(f[0], "smartdisable") == 0) + runsmartable(d, 1); + else if(strcmp(f[0], "smartenable") == 0) + runsmartable(d, 0); + else if(strcmp(f[0], "state") == 0) + forcestate(d, f[1]? f[1]: "null"); + else{ + cmderror(cmd, Ebadctl); + return -1; + } + 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; +} + +/* must emit exactly one line per controller (sd(3)) */ +static char* +iartopctl(SDev *sdev, char *p, char *e) +{ + ulong cap; + char pr[25]; + Ahba *hba; + Ctlr *ctlr; + +#define has(x, str) if(cap & (x)) p = seprint(p, e, "%s ", (str)) + + ctlr = sdev->ctlr; + hba = ctlr->hba; + p = seprint(p, e, "sd%c ahci port %#p: ", sdev->idno, hba); + cap = hba->cap; + has(Hs64a, "64a"); + has(Hsalp, "alp"); + has(Hsam, "am"); + has(Hsclo, "clo"); + has(Hcccs, "coal"); + has(Hems, "ems"); + has(Hsal, "led"); + has(Hsmps, "mps"); + has(Hsncq, "ncq"); + has(Hssntf, "ntf"); + has(Hspm, "pm"); + has(Hpsc, "pslum"); + has(Hssc, "slum"); + has(Hsss, "ss"); + has(Hsxs, "sxs"); + portr(pr, pr + sizeof pr, hba->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), + hba->ghc, hba->isr, hba->pi, pr, hba->ver); +#undef has +} + +static int +iawtopctl(SDev *, Cmdbuf *cmd) +{ + int *v; + char **f; + + f = cmd->f; + v = 0; + + if (f[0] == nil) + return 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 + 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 = { + "iahci", + + iapnp, + nil, /* legacy */ + iaenable, + iadisable, + + iaverify, + iaonline, + iario, + iarctl, + iawctl, + + scsibio, + nil, /* probe */ + nil, /* clear */ + iartopctl, + iawtopctl, +}; |