intel ahci bootstrap driver. full boot support requires forthcoming patch to cpu kernel driver to recognize drives before nvram is read. Reference: /n/sources/patch/applied/sd63xxesb-9load Date: Sat Jun 16 14:26:16 CES 2007 Signed-off-by: quanstro@quanstro.net --- /sys/src/boot/pc/sd63xxesb.c Thu Jan 1 00:00:00 1970 +++ /sys/src/boot/pc/sd63xxesb.c Sat Jun 16 14:24:03 2007 @@ -0,0 +1,1624 @@ +// intel 63[12]xesb ahci bootstrap sata controller +// copyright © 2007 coraid, inc. + +#include "u.h" +#include "lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "error.h" +#include "sd.h" +#include "ahci.h" + +#define dprint(...) if(debug == 1) print(__VA_ARGS__); else USED(debug) +#define idprint(...) if(prid == 1) print(__VA_ARGS__); else USED(prid) +#define aprint(...) if(datapi == 1) print(__VA_ARGS__); else USED(datapi); + +enum{ + NCtlr = 2, + NCtlrdrv = 8, + NDrive = NCtlr*NCtlrdrv, + + Read = 0, + Write +}; + +// pci space configurtion +enum{ + Pmap = 0x90, + Ppcs = 0x91, + Prev = 0xa8, +}; + +enum{ + Dnull, + Dmissing, + Dnew, + Dready, + Derror, + Dreset, + Doffline, + Dportreset, + Dlast +}; + +static char *diskstates[Dlast] = { + "null", + "missing", + "new", + "ready", + "error", + "reset", + "offline", + "portreset", +}; + +extern SDifc sd63xxesbifc; +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 intick; + 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 + int portno; // controller port number != driveno when not all ports are enabled. +}Drive; + +struct Ctlr{ + Lock; + + int irq; + int tbdf; + int enabled; + SDev *sdev; + Pcidev *pci; + + uchar *mmio; + ulong *lmmio; + Ahba *hba; + + Drive rawdrive[NCtlrdrv]; + Drive* drive[NCtlrdrv]; + int ndrive; +}; + +static Ctlr iactlr[NCtlr]; +static SDev sdevs[NCtlr]; +static int niactlr; + +static Drive *iadrive[NDrive]; +static int niadrive; + +static int prid = 0; +static int datapi = 0; + +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<>4]; + *e++ = ntab[reg[i]&0xf]; + *e++ = ' '; + } + *e++ = '\n'; + *e = 0; + dprint(buf); +} + +static void +dreg(char *s, Aport *p) +{ + dprint("%stask=%ux; cmd=%ux; ci=%ux; is=%ux\n", s, p->task, p->cmd, p->ci, p->isr); +} + +static void +esleep(int ms) +{ + delay(ms); +} + +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 *, Asleep *a, int ms) +{ + ulong start; + + start = m->ticks; + while((a->p->ci&a->i) != 0) + if(TK2MS(m->ticks-start) >= ms) + break; +} + +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) +{ + Actab *t; + Alist *l; + uchar *c; + + 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) +{ + Actab *t; + Alist *l; + uchar *c; + + 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) +{ + Actab *t; + Alist *l; + uchar *c; + + 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) +{ + delay(ms); +} + +static int +ahciportreset(Aportc *c) +{ + u32int *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 ushort +gbit16(void *a) +{ + uchar *i; + ushort j; + + i = a; + j = i[1]<<8; + j |= i[0]; + return j; +} + +static u32int +gbit32(void *a) +{ + uchar *i; + u32int j; + + 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) +{ + Actab *t; + Alist *l; + Aprdt *p; + uchar *c; + 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) +{ + Aportm *m; + int i, sig; + vlong s; + + 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) + goto done; + if(i&(1<<3)) + m->feat |= Dpower; + i = gbit16(id+82); + if(i&1) + m->feat |= Dsmart; + if(i&(1<<14)) + m->feat |= Dnop; +done: + return s; +} + +static int +ahciquiet(Aport *a) +{ + u32int *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("clo clear %x\n", a->task); + if(a->task&ASbsy) + return -1; + *p |= Ast; + return 0; +} + +static int +ahciidle(Aport *port) +{ + u32int *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. +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; +} + +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 = (u32int*)(f->base+0x58); +} + +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){ + dprint("configdrive: spinning up ... [%ux]\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; + + // 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<hba = (Ahba*)c->mmio; + u = h->cap; + + if((u&Hsam) == 0) + h->ghc |= Hae; + + print("ahci hba sss %d; ncs %d; coal %d; mports %d; led %d; clo %d; ems %d;\n", + (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) +{ + char *op, *e; + int i; + + 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; + p = op; + while(*p == ' ') + p++; + memmove(op, p, n-(e-p)); +} + +static int +identify(Drive *d) +{ + SDunit *u; + uchar oserial[21]; + u16int *id; + vlong osectors, s; + + 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); + + d->sectors = s; + d->smartrs = 0; + + idmove(d->serial, id+10, 20); + idmove(d->firmware, id+23, 8); + idmove(d->model, id+27, 40); + + 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 == 0 || 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) == 0) + return; + p->cmd &= ~Ast; + p->cmd |= Ast; +} + +static void +updatedrive(Drive *d) +{ + u32int cause, serr, s0, pr, ewake; + char *name; + Aport *p; + static u32int 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; + pr = 0; + }else if(cause&Adps) + pr = 0; + if(cause&Ifatal){ + ewake = 1; + dprint("Fatal\n"); + } + if(cause&Adhrs){ + if(p->task&33){ + dprint("Adhrs cause = %ux; serr = %ux; task=%ux\n", cause, serr, p->task); + d->portm.flag |= Ferror; + ewake = 1; + } + pr = 0; + } + + if(pr) + dprint("%s: upd %ux ta %ux\n", name, cause, p->task); + if(cause&(Aprcs|Aifs)){ + s0 = d->state; + switch(p->sstatus&7){ + case 0: + d->state = Dmissing; + break; + case 1: + d->state = Derror; + break; + case 3: + /* power mgnt crap for suprise removal */ + p->ie |= Aprcs|Apcs; // is this required? + d->state = Dreset; + break; + case 4: + d->state = Doffline; + break; + } + dprint("%s: %s → %s [Apcrs] %ux\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); + last = cause; +} + +static void +pstatus(Drive *d, ulong s) +{ + // bogus code because the first interrupt is currently dropped. + // likely my fault. serror is may be cleared at the wrong time. + switch(s){ + case 0: + d->state = Dmissing; + break; + case 2: // should this be missing? need testcase. + dprint("pstatus 2\n"); + case 3: + d->wait = 0; + d->state = Dnew; + break; + case 4: + d->state = Doffline; + 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("resetdisk: icc %ux det %d sdet %d\n", state, det, stat); + if(stat != 3){ + ilock(d); + d->state = Dportreset; + iunlock(d); + return; + } + ilock(d); + state = d->state; + if(d->state != Dready || d->state != Dnew) + d->portm.flag |= Ferror; + clearci(p); // satify sleep condition. + iunlock(d); + + qlock(&d->portm); + + if(p->cmd&Ast && ahciswreset(&d->portc) == -1){ + ilock(d); + d->state = Dportreset; // get a bigger stick. + iunlock(d); + goto out; + } + + ilock(d); + d->state = Dmissing; + iunlock(d); + + configdrive(d); +out: + dprint("resetdisk: %s → %s\n", diskstates[state], diskstates[d->state]); + qunlock(&d->portm); +} + +static int +newdrive(Drive *d) +{ + char *name, *s; + 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 loose; + } + if(identify(d) == -1){ + dprint("%s: identify failure\n", name); + goto loose; + } + if(m->feat&Dpower) + if(setfeatures(c, 0x85) == -1){ + m->feat &= ~Dpower; + if(ahcirecover(c) == -1) + goto loose; + } + + ilock(d); + d->state = Dready; + iunlock(d); + + qunlock(c->m); + + s = ""; + if(m->feat&Dllba) + s="L"; + idprint("%s: %sLBA %lld sectors\n", d->unit->name, s, d->sectors); + idprint(" %s %s %s %s\n", d->model, d->firmware, d->serial, d->mediachange?"[mediachange]":""); + + return 0; + +loose: + qunlock(&d->portm); + 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) + if(d->active && TK2MS(m->ticks-d->intick) > 5000){ + dprint("%s: drive hung; resetting [%ux] ci=%x\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("ahciportreset fails\n"); + else + i = 0; + qunlock(&d->portm); + dprint("portreset → %s [task %ux]\n", diskstates[d->state], d->port->task); + return i; +} + +static void +checkdrive(Drive *d, int i) +{ + ushort s; + char *name; + + ilock(d); + name = d->unit->name; + s = d->port->sstatus; + 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: + break; + case Dmissing: + case Dnew: + switch(s&0x107){ + case 0: + case 1: + break; + default: + dprint("%s: unknown status %04ux\n", name, s); + case 0x100: + 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: + if((++d->wait&Midwait) == 0){ + dprint("%s: slow reset %04ux task=%ux; %d\n", name, s, d->port->task, d->wait); + goto reset; + } + s = d->port->task&0xff; + if(s == 0x7f || ((d->port->sig>>16) != 0xeb14 && (s&~0x17) != (1<<6))) + break; + iunlock(d); + newdrive(d); + ilock(d); + } + break; + case Dready: + break; + case Doffline: + if(d->wait++&Mcomrwait) + break; + 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) + if((s&0x100) == 0) + break; + dprint("%s: portreset [%s]: mode %d; status %04ux\n", name, diskstates[d->state], d->mode, s); + d->portm.flag |= Ferror; + clearci(d->port); + if((s&7) == 0){ + d->state = Dmissing; + break; + } + iunlock(d); + doportreset(d); + ilock(d); + break; + } + iunlock(d); +} + +static void +iainterrupt(Ureg*, void *a) +{ + Ctlr *c; + Drive *d; + ulong cause, m; + int i; + + c = a; + ilock(c); +// check drive here! + cause = c->hba->isr; + for(i = 0; i < c->ndrive; i++){ + m = 1<rawdrive+i; + ilock(d); + if(d->port->isr) + if(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); + return 1; +} + +static int +iaenable(SDev *s) +{ + Ctlr *c; + + c = s->ctlr; + ilock(c); + if(c->enabled) + goto done; + pcisetbme(c->pci); + setvec(c->irq+VectorPIC, iainterrupt, c); + // supposed to squelch leftover interrupts here. + ahcienable(c->hba); +done: + c->enabled = 1; + iunlock(c); + return 1; +} + +static int +iadisable(SDev *s) +{ + Ctlr *c; + + c = s->ctlr; + ilock(c); + ahcidisable(c->hba); +// intrdisable(c->irq, iainterrupt, c, c->tbdf, name); + c->enabled = 0; + iunlock(c); + return 1; +} + +static int +iaonline(SDunit *unit) +{ + Ctlr *c; + Drive *d; + int r; + + 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 rests this after online is called; why? + unit->sectors = d->sectors; + unit->secsize = 512; + } else if(d->state == Dready) + r = 1; + iunlock(d); + + return r; +} + +/* returns locked list! */ +static Alist* +ahcibuild(Aportm *m, uchar *cmd, void *data, int n, vlong lba) +{ + Alist *l; + Actab *t; + Aprdt *p; + uchar *c, acmd, dir, llba; + static uchar tab[2][2] = {0xc8, 0x25, 0xca, 0x35}; + + 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; + p->count = (1<<31)|(512*n-2)|1; + + return l; +} + +static Alist* +ahcibuildpkt(Aportm *m, SDreq *r, void *data, int n) +{ + Alist *l; + Actab *t; + Aprdt *p; + uchar *c; + int fill, len; + + 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) +{ + u32int s, t, i; + + for(i = 0; i < 120; i++){ + ilock(d); + s = d->port->sstatus; + t = d->port->task; + iunlock(d); + if((s&0x100) == 0) + return -1; + if(d->state == Dready) + if((s&7) == 3) + return 0; + if((i+1)%30 == 0) + print("%s: waitready: [%s] task=%ux sstat=%ux\n", d->unit->name, diskstates[d->state], t, s); + esleep(1000); + } + print("%s: not responding; offline\n", d->unit->name); + ilock(d); + d->state = Doffline; + iunlock(d); + return -1; +} + +static int +iariopkt(SDreq *r, Drive *d) +{ + int n, count, try, max, flag, task; + uchar *cmd, *data; + char *name; + Aport *p; + Asleep as; + + cmd = r->cmd; + name = d->unit->name; + p = d->port; + + aprint("%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: + if(waitready(d) == -1) + return SDeio; + data = r->data; + n = count; + if(n > max) + n = max; + d->active++; + ahcibuildpkt(&d->portm, r, data, n); + ilock(d); + d->portm.flag = 0; + iunlock(d); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = m->ticks; + + while(ahciclear(&as) == 0) + ; + + 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; + } + d->active--; + 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); + esleep(1000); + goto retry; + } + if(flag&Ferror){ + 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 n, count, max, flag, task; + SDunit *unit; + Ctlr *c; + Drive *d; + uchar *cmd, *data; + char *name; + vlong lba; + Aport *p; + Asleep as; + + 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((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; + + if(waitready(d) == -1) + return SDeio; + data = r->data; + while(count > 0){ + n = count; + if(n > max) + n = max; + d->active++; + ahcibuild(&d->portm, cmd, data, n, lba); + ilock(d); + d->portm.flag = 0; + iunlock(d); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = m->ticks; + + while(ahciclear(&as) == 0) + ; + + 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; + } + d->active--; + qunlock(&d->portm); + if(flag == 0 || (flag&Ferror)){ + print("%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; // five ports. (supposedly ro pi register) + + // enable ahci mode. +// pcicfgw8(c->pci, 0x90, 0x40); +// pcicfgw16(c->pci, 0x90, (1<<6)|(1<<5)); //pedanticly proper for ich9. + pcicfgw8(c->pci, 0x90, (1<<6)|(1<<5)); //pedanticly proper for ich9. +} + +static SDev* +iapnp(void) +{ + int i, n, nunit; + ulong io; + Ctlr *c; + Pcidev *p; + SDev *head, *tail, *s; + Drive *d; + static int done; + + if(done++) + return nil; + + p = nil; + head = nil; + tail = nil; +loop: + while((p = pcimatch(p, 0x8086, 0)) != nil){ + if((p->did&0xfffc) != 0x2680) // esb + if((p->did&0xfffa) != 0x27c0) // 82801g[bh]m + continue; + if(niactlr == NCtlr){ + print("iapnp: too many controllers\n"); + 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 = (uchar*)upamalloc(io, p->mem[0].size, 0); + if(c->mmio == 0){ + print("iapnp: address 0x%luX in use did=%x\n", io, p->did); + continue; + } + c->lmmio = (ulong*)c->mmio; + c->pci = p; + if(p->did != 0x2681) + iasetupahci(c); + nunit = ahciconf(c); + //ahcihbareset((Ahba*)c->mmio); + if(iaahcimode(p) == -1) + break; + if(nunit < 1){ +// vunmap(c->mmio, p->mem[0].size); + continue; + } + niactlr++; + i = (c->hba->cap>>21)&1; + print("intel 63[12]xesb: sata-%s ports with %d ports\n", "I\0II"+i*2, nunit); + s->ifc = &sd63xxesbifc; + s->ctlr = c; + s->nunit = nunit; + s->idno = 'E'; + c->sdev = s; + c->irq = p->intl; + c->tbdf = p->tbdf; + c->ndrive = 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->ctlr = c; + if((c->hba->pi&(1<port = (Aport*)(c->mmio+0x80*i+0x100); + d->portc.p = d->port; + d->portc.m = &d->portm; + d->driveno = n++; + c->drive[i] = d; + iadrive[d->driveno] = d; + } + for(i = 0; i < n; i++) + if(ahciidle(c->drive[i]->port) == -1){ + dprint("intel 63[12]xesb: port %d wedged; abort\n", i); + goto loop; + } + for(i = 0; i < n; i++){ + c->drive[i]->mode = DMsatai; + configdrive(c->drive[i]); + } + + niadrive += nunit; + if(head) + tail->next = s; + else + head = s; + tail = s; + } + return head; +} + +static SDev* +iaid(SDev* sdev) +{ + Ctlr *c; + int i; + + for(; sdev; sdev = sdev->next){ + if(sdev->ifc != &sd63xxesbifc) + continue; + c = sdev->ctlr; + for(i = 0; i < NCtlr; i++) + if(c == iactlr+i) + sdev->idno = 'E'+i; + } + return nil; +} + +SDifc sd63xxesbifc = { + "iahci", + + iapnp, + nil, /* legacy */ + iaid, + iaenable, + iadisable, + + iaverify, + iaonline, + iario, + nil, + nil, + + scsibio, +}; + --- /sys/src/boot/pc/ahci.h Thu Jan 1 00:00:00 1970 +++ /sys/src/boot/pc/ahci.h Sat Jun 16 14:24:10 2007 @@ -0,0 +1,270 @@ +// ahci generic +// © 2007 coraid, inc + +// 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, +}; + +// 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 +enum{ + Hs64a = 1<<31, // supports 64-bit addressing + Hsncq = 1<<30, // " ncq + Hssntf = 1<<29, // " snotification reg. + Hsmps = 1<<28, // " mech pres switch + Hsss = 1<<27, // " staggered spinup + Hsalp = 1<<26, // " agressive link pm + Hsal = 1<<25, // " activity led + Hsclo = 1<<24, // " command-list override + Hiss = 1<<20, // " for interface speed. +// Hsnzo = 1<<19, + Hsam = 1<<18, // " ahci-mode only + Hspm = 1<<17, // " port multiplier +// Hfbss = 1<<16, + Hpmb = 1<<15, // " mutiple-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. + Hsxs = 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 +}; + +typedef struct{ + u32int cap; + u32int ghc; + u32int isr; + u32int pi; // ports implemented + u32int ver; + u32int ccc; // coaleasing control + u32int cccports; + u32int emloc; + u32int emctl; +} 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 with 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 = Atfes|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, // agressive slumber & partial sleep + Aalpe = 1<<26, // agressive link pm enable + Adlae = 1<<25, // drive led on atapi + Aatapi = 1<<24, // device is atapi + 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 device (requires cold-pres. detect) + Asud = 1<<1, // spin-up device; requires ss capability. + Ast = 1<<0, // start + + Arun = Ast|Acr|Afre|Afr, +}; + +// ctl register bits +enum{ + Aipm = 1<<8, // interface power mgmt. 3=off + Aspd = 1<<4, + Adet = 1<<0, // device detcection. +}; + +#define sstatus scr0 +#define sctl scr2 +#define serror scr1 +#define sactive scr3 + +typedef struct{ + u32int list; // PxCLB must be 1kb aligned. + u32int listhi; + u32int fis; // 256-byte aligned. + u32int fishi; + u32int isr; + u32int ie; // interrupt enable + u32int cmd; + u32int res1; + u32int task; + u32int sig; + u32int scr0; + u32int scr2; + u32int scr1; + u32int scr3; + u32int ci; // command issue + u32int ntf; + uchar res2[8]; + u32int vendor; +}Aport; + +// in hosts memory; not memory mapped +typedef struct{ + uchar *base; + uchar *d; + uchar *p; + uchar *r; + uchar *u; + u32int *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{ + u32int flags; + u32int len; + u32int ctab; + u32int ctabhi; + uchar reserved[16]; +}Alist; + +typedef struct{ + u32int dba; + u32int dbahi; + u32int pad; + u32int count; +}Aprdt; + +typedef struct{ + uchar cfis[0x40]; + uchar atapi[0x10]; + uchar pad[0x30]; + Aprdt prdt; +}Actab; + +enum{ + Ferror = 1, + Fdone = 2, +}; + +enum{ + Dllba = 1, + Dsmart = 1<<1, + Dpower = 1<<2, + Dnop = 1<<3, + Datapi = 1<<4, + Datapi16= 1<<5, +}; + +typedef struct{ +// QLock; +// Rendez; + uchar flag; + uchar feat; + uchar smart; + Afis fis; + Alist *list; + Actab *ctab; +}Aportm; + +typedef struct{ + Aport *p; + Aportm *m; +}Aportc;