Change-Id: I5cd052ce7bde5f6eb6de9557dfe6151174d1ef5f Signed-off-by: Ronald G. Minnich <[email protected]> --- kern/drivers/dev/sd.c | 1683 ++++++++++++++++++++++++++++++ kern/drivers/dev/sdiahci.c | 2461 ++++++++++++++++++++++++++++++++++++++++++++ kern/drivers/dev/sdscsi.c | 436 ++++++++ kern/include/ahci.h | 302 ++++++ kern/include/sd.h | 203 ++++ 5 files changed, 5085 insertions(+) create mode 100644 kern/drivers/dev/sd.c create mode 100644 kern/drivers/dev/sdiahci.c create mode 100644 kern/drivers/dev/sdscsi.c create mode 100644 kern/include/ahci.h create mode 100644 kern/include/sd.h
diff --git a/kern/drivers/dev/sd.c b/kern/drivers/dev/sd.c new file mode 100644 index 0000000..5ca4f5c --- /dev/null +++ b/kern/drivers/dev/sd.c @@ -0,0 +1,1683 @@ +/* + * This file is part of the UCB release of Plan 9. It is subject to the license + * terms in the LICENSE file found in the top-level directory of this + * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No + * part of the UCB release of Plan 9, including this file, may be copied, + * modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +/* + * Storage Device. + */ +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "../port/sd.h" + +extern Dev sddevtab; +extern SDifc* sdifc[]; + +static char Echange[] = "media or partition has changed"; + +static char devletters[] = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +static SDev *devs[sizeof devletters-1]; +static QLock devslock; + +enum { + Rawcmd, + Rawdata, + Rawstatus, +}; + +enum { + Qtopdir = 1, /* top level directory */ + Qtopbase, + Qtopctl = Qtopbase, + + Qunitdir, /* directory per unit */ + Qunitbase, + Qctl = Qunitbase, + Qraw, + Qpart, + + TypeLOG = 4, + NType = (1<<TypeLOG), + TypeMASK = (NType-1), + TypeSHIFT = 0, + + PartLOG = 8, + NPart = (1<<PartLOG), + PartMASK = (NPart-1), + PartSHIFT = TypeLOG, + + UnitLOG = 8, + NUnit = (1<<UnitLOG), + UnitMASK = (NUnit-1), + UnitSHIFT = (PartLOG+TypeLOG), + + DevLOG = 8, + NDev = (1 << DevLOG), + DevMASK = (NDev-1), + DevSHIFT = (UnitLOG+PartLOG+TypeLOG), + + Ncmd = 20, +}; + +#define TYPE(q) ((((uint32_t)(q).path)>>TypeSHIFT) & TypeMASK) +#define PART(q) ((((uint32_t)(q).path)>>PartSHIFT) & PartMASK) +#define UNIT(q) ((((uint32_t)(q).path)>>UnitSHIFT) & UnitMASK) +#define DEV(q) ((((uint32_t)(q).path)>>DevSHIFT) & DevMASK) +#define QID(d,u, p, t) (((d)<<DevSHIFT)|((u)<<UnitSHIFT)|\ + ((p)<<PartSHIFT)|((t)<<TypeSHIFT)) + + +void +sdaddpart(SDunit* unit, char* name, uint64_t start, uint64_t end) +{ + SDpart *pp; + int i, partno; + + /* + * Check name not already used + * and look for a free slot. + */ + if(unit->part != nil){ + partno = -1; + for(i = 0; i < unit->npart; i++){ + pp = &unit->part[i]; + if(!pp->valid){ + if(partno == -1) + partno = i; + break; + } + if(strcmp(name, pp->SDperm.name) == 0){ + if(pp->start == start && pp->end == end) + return; + error(Ebadctl); + } + } + } + else{ + if((unit->part = malloc(sizeof(SDpart)*SDnpart)) == nil) + error(Enomem); + unit->npart = SDnpart; + partno = 0; + } + + /* + * If no free slot found then increase the + * array size (can't get here with unit->part == nil). + */ + if(partno == -1){ + if(unit->npart >= NPart) + error(Enomem); + if((pp = malloc(sizeof(SDpart)*(unit->npart+SDnpart))) == nil) + error(Enomem); + memmove(pp, unit->part, sizeof(SDpart)*unit->npart); + free(unit->part); + unit->part = pp; + partno = unit->npart; + unit->npart += SDnpart; + } + + /* + * Check size and extent are valid. + */ + if(start > end || end > unit->sectors) + error(Eio); + pp = &unit->part[partno]; + pp->start = start; + pp->end = end; + kstrdup(&pp->SDperm.name, name); + kstrdup(&pp->SDperm.user, eve); + pp->SDperm.perm = 0640; + pp->valid = 1; +} + +static void +sddelpart(SDunit* unit, char* name) +{ + Proc *up = externup(); + int i; + SDpart *pp; + /* + * Look for the partition to delete. + * Can't delete if someone still has it open. + */ + pp = unit->part; + for(i = 0; i < unit->npart; i++){ + if(strcmp(name, pp->SDperm.name) == 0) + break; + pp++; + } + if(i >= unit->npart) + error(Ebadctl); + if(strcmp(up->user, pp->SDperm.user) && !iseve()) + error(Eperm); + pp->valid = 0; + pp->vers++; +} + +static void +sdincvers(SDunit *unit) +{ + int i; + + unit->vers++; + if(unit->part){ + for(i = 0; i < unit->npart; i++){ + unit->part[i].valid = 0; + unit->part[i].vers++; + } + } +} + +static int +sdinitpart(SDunit* unit) +{ +#if 0 + Mach *m; + int nf; + uint64_t start, end; + char *f[4], *p, *q, buf[10]; + + m = machp(); +#endif + if(unit->sectors > 0){ + unit->sectors = unit->secsize = 0; + sdincvers(unit); + } + + /* device must be connected or not; other values are trouble */ + if(unit->inquiry[0] & 0xC0) /* see SDinq0periphqual */ + return 0; + switch(unit->inquiry[0] & SDinq0periphtype){ + case SDperdisk: + case SDperworm: + case SDpercd: + case SDpermo: + break; + default: + return 0; + } + + if(unit->dev->ifc->online) + unit->dev->ifc->online(unit); + if(unit->sectors){ + sdincvers(unit); + sdaddpart(unit, "data", 0, unit->sectors); + + /* + * Use partitions passed from boot program, + * e.g. + * sdC0part=dos 63 123123/plan9 123123 456456 + * This happens before /boot sets hostname so the + * partitions will have the null-string for user. + * The gen functions patch it up. + */ +#if 0 + snprint(buf, sizeof buf, "%spart", unit->SDperm.name); + for(p = getconf(buf); p != nil; p = q){ + if(q = strchr(p, '/')) + *q++ = '\0'; + nf = tokenize(p, f, nelem(f)); + if(nf < 3) + continue; + + start = strtoull(f[1], 0, 0); + end = strtoull(f[2], 0, 0); + if(!waserror()){ + sdaddpart(unit, f[0], start, end); + poperror(); + } + } +#endif + } + + return 1; +} + +static int +sdindex(int idno) +{ + char *p; + + p = strchr(devletters, idno); + if(p == nil) + return -1; + return p-devletters; +} + +static SDev* +sdgetdev(int idno) +{ + SDev *sdev; + int i; + + if((i = sdindex(idno)) < 0) + return nil; + + qlock(&devslock); + if(sdev = devs[i]) + incref(&sdev->r); + qunlock(&devslock); + return sdev; +} + +static SDunit* +sdgetunit(SDev* sdev, int subno) +{ + SDunit *unit; + char buf[32]; + + /* + * Associate a unit with a given device and sub-unit + * number on that device. + * The device will be probed if it has not already been + * successfully accessed. + */ + qlock(&sdev->unitlock); + if(subno > sdev->nunit){ + qunlock(&sdev->unitlock); + return nil; + } + + unit = sdev->unit[subno]; + if(unit == nil){ + /* + * Probe the unit only once. This decision + * may be a little severe and reviewed later. + */ + if(sdev->unitflg[subno]){ + qunlock(&sdev->unitlock); + return nil; + } + if((unit = malloc(sizeof(SDunit))) == nil){ + qunlock(&sdev->unitlock); + return nil; + } + sdev->unitflg[subno] = 1; + + snprint(buf, sizeof(buf), "%s%d", sdev->name, subno); + kstrdup(&unit->SDperm.name, buf); + kstrdup(&unit->SDperm.user, eve); + unit->SDperm.perm = 0555; + unit->subno = subno; + unit->dev = sdev; + + if(sdev->enabled == 0 && sdev->ifc->enable) + sdev->ifc->enable(sdev); + sdev->enabled = 1; + + /* + * No need to lock anything here as this is only + * called before the unit is made available in the + * sdunit[] array. + */ + if(unit->dev->ifc->verify(unit) == 0){ + qunlock(&sdev->unitlock); + free(unit); + return nil; + } + sdev->unit[subno] = unit; + } + qunlock(&sdev->unitlock); + return unit; +} + +static void +sdreset(void) +{ + int i; + SDev *sdev; + + /* + * Probe all known controller types and register any devices found. + */ + for(i = 0; sdifc[i] != nil; i++){ + if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil) + continue; + sdadddevs(sdev); + } +} + +void +sdadddevs(SDev *sdev) +{ + int i, j, id; + SDev *next; + + for(; sdev; sdev=next){ + next = sdev->next; + + sdev->unit = (SDunit**)malloc(sdev->nunit * sizeof(SDunit*)); + sdev->unitflg = (int*)malloc(sdev->nunit * sizeof(int)); + if(sdev->unit == nil || sdev->unitflg == nil){ + print("sdadddevs: out of memory\n"); + giveup: + free(sdev->unit); + free(sdev->unitflg); + if(sdev->ifc->clear) + sdev->ifc->clear(sdev); + free(sdev); + continue; + } + id = sdindex(sdev->idno); + if(id == -1){ + print("sdadddevs: bad id number %d (%C)\n", id, id); + goto giveup; + } + qlock(&devslock); + for(i=0; i<nelem(devs); i++){ + if(devs[j = (id+i)%nelem(devs)] == nil){ + sdev->idno = devletters[j]; + devs[j] = sdev; + snprint(sdev->name, sizeof sdev->name, "sd%c", devletters[j]); + break; + } + } + qunlock(&devslock); + if(i == nelem(devs)){ + print("sdadddevs: out of device letters\n"); + goto giveup; + } + } +} + +// void +// sdrmdevs(SDev *sdev) +// { +// char buf[2]; +// +// snprint(buf, sizeof buf, "%c", sdev->idno); +// unconfigure(buf); +// } + +void +sdaddallconfs(void (*addconf)(SDunit *)) +{ + int i, u; + SDev *sdev; + + for(i = 0; i < nelem(devs); i++) /* each controller */ + for(sdev = devs[i]; sdev; sdev = sdev->next) + for(u = 0; u < sdev->nunit; u++) /* each drive */ + (*addconf)(sdev->unit[u]); +} + +static int +sd2gen(Chan* c, int i, Dir* dp) +{ + Qid q; + uint64_t l; + SDpart *pp; + SDperm *perm; + SDunit *unit; + SDev *sdev; + int rv; + + sdev = sdgetdev(DEV(c->qid)); + assert(sdev); + unit = sdev->unit[UNIT(c->qid)]; + + rv = -1; + switch(i){ + case Qctl: + mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qctl), + unit->vers, QTFILE); + perm = &unit->ctlperm; + if(emptystr(perm->user)){ + kstrdup(&perm->user, eve); + perm->perm = 0644; /* nothing secret in ctl */ + } + devdir(c, q, "ctl", 0, perm->user, perm->perm, dp); + rv = 1; + break; + + case Qraw: + mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qraw), + unit->vers, QTFILE); + perm = &unit->rawperm; + if(emptystr(perm->user)){ + kstrdup(&perm->user, eve); + perm->perm = DMEXCL|0600; + } + devdir(c, q, "raw", 0, perm->user, perm->perm, dp); + rv = 1; + break; + + case Qpart: + pp = &unit->part[PART(c->qid)]; + l = (pp->end - pp->start) * unit->secsize; + mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qpart), + unit->vers+pp->vers, QTFILE); + if(emptystr(pp->SDperm.user)) + kstrdup(&pp->SDperm.user, eve); + devdir(c, q, pp->SDperm.name, l, pp->SDperm.user, pp->SDperm.perm, dp); + rv = 1; + break; + } + + decref(&sdev->r); + return rv; +} + +static int +sd1gen(Chan* c, int i, Dir* dp) +{ + Qid q; + + switch(i){ + case Qtopctl: + mkqid(&q, QID(0, 0, 0, Qtopctl), 0, QTFILE); + devdir(c, q, "sdctl", 0, eve, 0644, dp); /* no secrets */ + return 1; + } + return -1; +} + +static int +sdgen(Chan* c, char* d, Dirtab* dir, int j, int s, Dir* dp) +{ + Proc *up = externup(); + Qid q = {}; + int64_t l; + int i, r; + SDpart *pp; + SDunit *unit; + SDev *sdev; + + switch(TYPE(c->qid)){ + case Qtopdir: + if(s == DEVDOTDOT){ + mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR); + sprint(up->genbuf, "#%C", sddevtab.dc); + devdir(c, q, up->genbuf, 0, eve, 0555, dp); + return 1; + } + + if(s+Qtopbase < Qunitdir) + return sd1gen(c, s+Qtopbase, dp); + s -= (Qunitdir-Qtopbase); + + qlock(&devslock); + for(i=0; i<nelem(devs); i++){ + if(devs[i]){ + if(s < devs[i]->nunit) + break; + s -= devs[i]->nunit; + } + } + + if(i == nelem(devs)){ + /* Run off the end of the list */ + qunlock(&devslock); + return -1; + } + + if((sdev = devs[i]) == nil){ + qunlock(&devslock); + return 0; + } + + incref(&sdev->r); + qunlock(&devslock); + + if((unit = sdev->unit[s]) == nil) + if((unit = sdgetunit(sdev, s)) == nil){ + decref(&sdev->r); + return 0; + } + + mkqid(&q, QID(sdev->idno, s, 0, Qunitdir), 0, QTDIR); + if(emptystr(unit->SDperm.user)) + kstrdup(&unit->SDperm.user, eve); + devdir(c, q, unit->SDperm.name, 0, unit->SDperm.user, unit->SDperm.perm, dp); + decref(&sdev->r); + return 1; + + case Qunitdir: + if(s == DEVDOTDOT){ + mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR); + sprint(up->genbuf, "#%C", sddevtab.dc); + devdir(c, q, up->genbuf, 0, eve, 0555, dp); + return 1; + } + + if((sdev = sdgetdev(DEV(c->qid))) == nil){ + devdir(c, c->qid, "unavailable", 0, eve, 0, dp); + return 1; + } + + unit = sdev->unit[UNIT(c->qid)]; + qlock(&unit->ctl); + + /* + * Check for media change. + * If one has already been detected, sectors will be zero. + * If there is one waiting to be detected, online + * will return > 1. + * Online is a bit of a large hammer but does the job. + */ + if(unit->sectors == 0 + || (unit->dev->ifc->online && unit->dev->ifc->online(unit) > 1)) + sdinitpart(unit); + + i = s+Qunitbase; + if(i < Qpart){ + r = sd2gen(c, i, dp); + qunlock(&unit->ctl); + decref(&sdev->r); + return r; + } + i -= Qpart; + if(unit->part == nil || i >= unit->npart){ + qunlock(&unit->ctl); + decref(&sdev->r); + break; + } + pp = &unit->part[i]; + if(!pp->valid){ + qunlock(&unit->ctl); + decref(&sdev->r); + return 0; + } + l = (pp->end - pp->start) * (int64_t)unit->secsize; + mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), i, Qpart), + unit->vers+pp->vers, QTFILE); + if(emptystr(pp->SDperm.user)) + kstrdup(&pp->SDperm.user, eve); + devdir(c, q, pp->SDperm.name, l, pp->SDperm.user, pp->SDperm.perm, dp); + qunlock(&unit->ctl); + decref(&sdev->r); + return 1; + case Qraw: + case Qctl: + case Qpart: + if((sdev = sdgetdev(DEV(c->qid))) == nil){ + devdir(c, q, "unavailable", 0, eve, 0, dp); + return 1; + } + unit = sdev->unit[UNIT(c->qid)]; + qlock(&unit->ctl); + r = sd2gen(c, TYPE(c->qid), dp); + qunlock(&unit->ctl); + decref(&sdev->r); + return r; + case Qtopctl: + return sd1gen(c, TYPE(c->qid), dp); + default: + break; + } + + return -1; +} + +static Chan* +sdattach(char* spec) +{ + Chan *c; + char *p; + SDev *sdev; + int idno, subno; + + if(*spec == '\0'){ + c = devattach(sddevtab.dc, spec); + mkqid(&c->qid, QID(0, 0, 0, Qtopdir), 0, QTDIR); + return c; + } + + if(spec[0] != 's' || spec[1] != 'd') + error(Ebadspec); + idno = spec[2]; + subno = strtol(&spec[3], &p, 0); + if(p == &spec[3]) + error(Ebadspec); + + if((sdev=sdgetdev(idno)) == nil) + error(Enonexist); + if(sdgetunit(sdev, subno) == nil){ + decref(&sdev->r); + error(Enonexist); + } + + c = devattach(sddevtab.dc, spec); + mkqid(&c->qid, QID(sdev->idno, subno, 0, Qunitdir), 0, QTDIR); + c->devno = (sdev->idno << UnitLOG) + subno; + decref(&sdev->r); + return c; +} + +static Walkqid* +sdwalk(Chan* c, Chan* nc, char** name, int nname) +{ + return devwalk(c, nc, name, nname, nil, 0, sdgen); +} + +static int32_t +sdstat(Chan* c, uint8_t* db, int32_t n) +{ + return devstat(c, db, n, nil, 0, sdgen); +} + +static Chan* +sdopen(Chan* c, int omode) +{ + Proc *up = externup(); + SDpart *pp; + SDunit *unit; + SDev *sdev; + uint8_t tp; + + c = devopen(c, omode, 0, 0, sdgen); + if((tp = TYPE(c->qid)) != Qctl && tp != Qraw && tp != Qpart) + return c; + + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + + unit = sdev->unit[UNIT(c->qid)]; + + switch(TYPE(c->qid)){ + case Qctl: + c->qid.vers = unit->vers; + break; + case Qraw: + c->qid.vers = unit->vers; + if(TAS(&unit->rawinuse) != 0){ + c->flag &= ~COPEN; + decref(&sdev->r); + error(Einuse); + } + unit->state = Rawcmd; + break; + case Qpart: + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + c->flag &= ~COPEN; + decref(&sdev->r); + nexterror(); + } + pp = &unit->part[PART(c->qid)]; + c->qid.vers = unit->vers+pp->vers; + qunlock(&unit->ctl); + poperror(); + break; + } + decref(&sdev->r); + return c; +} + +static void +sdclose(Chan* c) +{ + SDunit *unit; + SDev *sdev; + + if(c->qid.type & QTDIR) + return; + if(!(c->flag & COPEN)) + return; + + switch(TYPE(c->qid)){ + default: + break; + case Qraw: + sdev = sdgetdev(DEV(c->qid)); + if(sdev){ + unit = sdev->unit[UNIT(c->qid)]; + unit->rawinuse = 0; + decref(&sdev->r); + } + break; + } +} + +static int32_t +sdbio(Chan* c, int write, char* a, int32_t len, int64_t off) +{ + Proc *up = externup(); + int nchange; + uint8_t *b; + SDpart *pp; + SDunit *unit; + SDev *sdev; + int64_t bno; + int32_t l, max, nb, offset; + + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil){ + decref(&sdev->r); + error(Enonexist); + } + unit = sdev->unit[UNIT(c->qid)]; + if(unit == nil) + error(Enonexist); + + nchange = 0; + qlock(&unit->ctl); + while(waserror()){ + /* notification of media change; go around again */ + if(strcmp(up->errstr, Eio) == 0 && unit->sectors == 0 && nchange++ == 0){ + sdinitpart(unit); + continue; + } + + /* other errors; give up */ + qunlock(&unit->ctl); + decref(&sdev->r); + nexterror(); + } + pp = &unit->part[PART(c->qid)]; + if(unit->vers+pp->vers != c->qid.vers) + error(Echange); + + /* + * Check the request is within bounds. + * Removeable drives are locked throughout the I/O + * in case the media changes unexpectedly. + * Non-removeable drives are not locked during the I/O + * to allow the hardware to optimise if it can; this is + * a little fast and loose. + * It's assumed that non-removeable media parameters + * (sectors, secsize) can't change once the drive has + * been brought online. + */ + bno = (off/unit->secsize) + pp->start; + nb = ((off+len+unit->secsize-1)/unit->secsize) + pp->start - bno; + max = SDmaxio/unit->secsize; + if(nb > max) + nb = max; + if(bno+nb > pp->end) + nb = pp->end - bno; + if(bno >= pp->end || nb == 0){ + if(write) + error(Eio); + qunlock(&unit->ctl); + decref(&sdev->r); + poperror(); + return 0; + } + if(!(unit->inquiry[1] & SDinq1removable)){ + qunlock(&unit->ctl); + poperror(); + } + + b = sdmalloc(nb*unit->secsize); + if(b == nil) + error(Enomem); + if(waserror()){ + sdfree(b); + if(!(unit->inquiry[1] & SDinq1removable)) + decref(&sdev->r); /* gadverdamme! */ + nexterror(); + } + + offset = off%unit->secsize; + if(offset+len > nb*unit->secsize) + len = nb*unit->secsize - offset; + if(write){ + if(offset || (len%unit->secsize)){ + l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno); + if(l < 0) + error(Eio); + if(l < (nb*unit->secsize)){ + nb = l/unit->secsize; + l = nb*unit->secsize - offset; + if(len > l) + len = l; + } + } + memmove(b+offset, a, len); + l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno); + if(l < 0) + error(Eio); + if(l < offset) + len = 0; + else if(len > l - offset) + len = l - offset; + } + else{ + l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno); + if(l < 0) + error(Eio); + if(l < offset) + len = 0; + else if(len > l - offset) + len = l - offset; + memmove(a, b+offset, len); + } + sdfree(b); + poperror(); + + if(unit->inquiry[1] & SDinq1removable){ + qunlock(&unit->ctl); + poperror(); + } + + decref(&sdev->r); + return len; +} + +static int32_t +sdrio(SDreq* r, void* a, int32_t n) +{ + Proc *up = externup(); + void *data; + + if(n >= SDmaxio || n < 0) + error(Etoobig); + + data = nil; + if(n){ + if((data = sdmalloc(n)) == nil) + error(Enomem); + if(r->write) + memmove(data, a, n); + } + r->data = data; + r->dlen = n; + + if(waserror()){ + sdfree(data); + r->data = nil; + nexterror(); + } + + if(r->unit->dev->ifc->rio(r) != SDok) + error(Eio); + + if(!r->write && r->rlen > 0) + memmove(a, data, r->rlen); + sdfree(data); + r->data = nil; + poperror(); + + return r->rlen; +} + +/* + * SCSI simulation for non-SCSI devices + */ +int +sdsetsense(SDreq *r, int status, int key, int asc, int ascq) +{ + int len; + SDunit *unit; + + unit = r->unit; + unit->sense[2] = key; + unit->sense[12] = asc; + unit->sense[13] = ascq; + + r->status = status; + if(status == SDcheck && !(r->flags & SDnosense)){ + /* request sense case from sdfakescsi */ + len = sizeof unit->sense; + if(len > sizeof r->sense-1) + len = sizeof r->sense-1; + memmove(r->sense, unit->sense, len); + unit->sense[2] = 0; + unit->sense[12] = 0; + unit->sense[13] = 0; + r->flags |= SDvalidsense; + return SDok; + } + return status; +} + +int +sdmodesense(SDreq *r, uint8_t *cmd, void *info, int ilen) +{ + int len; + uint8_t *data; + + /* + * Fake a vendor-specific request with page code 0, + * return the drive info. + */ + if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F) + return sdsetsense(r, SDcheck, 0x05, 0x24, 0); + len = (cmd[7]<<8)|cmd[8]; + if(len == 0) + return SDok; + if(len < 8+ilen) + return sdsetsense(r, SDcheck, 0x05, 0x1A, 0); + if(r->data == nil || r->dlen < len) + return sdsetsense(r, SDcheck, 0x05, 0x20, 1); + data = r->data; + memset(data, 0, 8); + data[0] = ilen>>8; + data[1] = ilen; + if(ilen) + memmove(data+8, info, ilen); + r->rlen = 8+ilen; + return sdsetsense(r, SDok, 0, 0, 0); +} + +int +sdfakescsi(SDreq *r, void *info, int ilen) +{ + uint8_t *cmd, *p; + uint64_t len; + SDunit *unit; + + cmd = r->cmd; + r->rlen = 0; + unit = r->unit; + + /* + * Rewrite read(6)/write(6) into read(10)/write(10). + */ + switch(cmd[0]){ + case 0x08: /* read */ + case 0x0A: /* write */ + cmd[9] = 0; + cmd[8] = cmd[4]; + cmd[7] = 0; + cmd[6] = 0; + cmd[5] = cmd[3]; + cmd[4] = cmd[2]; + cmd[3] = cmd[1] & 0x0F; + cmd[2] = 0; + cmd[1] &= 0xE0; + cmd[0] |= 0x20; + break; + } + + /* + * Map SCSI commands into ATA commands for discs. + * Fail any command with a LUN except INQUIRY which + * will return 'logical unit not supported'. + */ + if((cmd[1]>>5) && cmd[0] != 0x12) + return sdsetsense(r, SDcheck, 0x05, 0x25, 0); + + switch(cmd[0]){ + default: + return sdsetsense(r, SDcheck, 0x05, 0x20, 0); + + case 0x00: /* test unit ready */ + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x03: /* request sense */ + if(cmd[4] < sizeof unit->sense) + len = cmd[4]; + else + len = sizeof unit->sense; + if(r->data && r->dlen >= len){ + memmove(r->data, unit->sense, len); + r->rlen = len; + } + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x12: /* inquiry */ + if(cmd[4] < sizeof unit->inquiry) + len = cmd[4]; + else + len = sizeof unit->inquiry; + if(r->data && r->dlen >= len){ + memmove(r->data, unit->inquiry, len); + r->rlen = len; + } + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x1B: /* start/stop unit */ + /* + * nop for now, can use power management later. + */ + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x25: /* read capacity */ + if((cmd[1] & 0x01) || cmd[2] || cmd[3]) + return sdsetsense(r, SDcheck, 0x05, 0x24, 0); + if(r->data == nil || r->dlen < 8) + return sdsetsense(r, SDcheck, 0x05, 0x20, 1); + + /* + * Read capacity returns the LBA of the last sector. + */ + len = unit->sectors - 1; + p = r->data; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + len = 512; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + r->rlen = p - (uint8_t*)r->data; + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x9E: /* long read capacity */ + if((cmd[1] & 0x01) || cmd[2] || cmd[3]) + return sdsetsense(r, SDcheck, 0x05, 0x24, 0); + if(r->data == nil || r->dlen < 8) + return sdsetsense(r, SDcheck, 0x05, 0x20, 1); + /* + * Read capcity returns the LBA of the last sector. + */ + len = unit->sectors - 1; + p = r->data; + *p++ = len>>56; + *p++ = len>>48; + *p++ = len>>40; + *p++ = len>>32; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + len = 512; + *p++ = len>>24; + *p++ = len>>16; + *p++ = len>>8; + *p++ = len; + r->rlen = p - (uint8_t*)r->data; + return sdsetsense(r, SDok, 0, 0, 0); + + case 0x5A: /* mode sense */ + return sdmodesense(r, cmd, info, ilen); + + case 0x28: /* read */ + case 0x2A: /* write */ + case 0x88: /* read16 */ + case 0x8a: /* write16 */ + return SDnostatus; + } +} + +static int32_t +sdread(Chan *c, void *a, int32_t n, int64_t off) +{ + Proc *up = externup(); + char *p, *e, *buf; + SDpart *pp; + SDunit *unit; + SDev *sdev; + int32_t offset; + int i, l, mm, status; + + offset = off; + switch(TYPE(c->qid)){ + default: + error(Eperm); + case Qtopctl: + mm = 64*1024; /* room for register dumps */ + p = buf = malloc(mm); + if(p == nil) + error(Enomem); + e = p + mm; + qlock(&devslock); + for(i = 0; i < nelem(devs); i++){ + sdev = devs[i]; + if(sdev && sdev->ifc->rtopctl) + p = sdev->ifc->rtopctl(sdev, p, e); + } + qunlock(&devslock); + n = readstr(offset, a, n, buf); + free(buf); + return n; + + case Qtopdir: + case Qunitdir: + return devdirread(c, a, n, 0, 0, sdgen); + + case Qctl: + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + + unit = sdev->unit[UNIT(c->qid)]; + mm = 16*1024; /* room for register dumps */ + p = malloc(mm); + if(p == nil) + error(Enomem); + l = snprint(p, mm, "inquiry %.48s\n", + (char*)unit->inquiry+8); + qlock(&unit->ctl); + /* + * If there's a device specific routine it must + * provide all information pertaining to night geometry + * and the garscadden trains. + */ + if(unit->dev->ifc->rctl) + l += unit->dev->ifc->rctl(unit, p+l, mm-l); + if(unit->sectors == 0) + sdinitpart(unit); + if(unit->sectors){ + if(unit->dev->ifc->rctl == nil) + l += snprint(p+l, mm-l, + "geometry %llu %lu\n", + unit->sectors, unit->secsize); + pp = unit->part; + for(i = 0; i < unit->npart; i++){ + if(pp->valid) + l += snprint(p+l, mm-l, + "part %s %llu %llu\n", + pp->SDperm.name, pp->start, pp->end); + pp++; + } + } + qunlock(&unit->ctl); + decref(&sdev->r); + l = readstr(offset, a, n, p); + free(p); + return l; + + case Qraw: + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + + unit = sdev->unit[UNIT(c->qid)]; + qlock(&unit->raw); + if(waserror()){ + qunlock(&unit->raw); + decref(&sdev->r); + nexterror(); + } + if(unit->state == Rawdata){ + unit->state = Rawstatus; + i = sdrio(unit->req, a, n); + } + else if(unit->state == Rawstatus){ + status = unit->req->status; + unit->state = Rawcmd; + free(unit->req); + unit->req = nil; + i = readnum(0, a, n, status, NUMSIZE); + } else + i = 0; + qunlock(&unit->raw); + decref(&sdev->r); + poperror(); + return i; + + case Qpart: + return sdbio(c, 0, a, n, off); + } +} + +static void legacytopctl(Cmdbuf*); + +static int32_t +sdwrite(Chan* c, void* a, int32_t n, int64_t off) +{ + Proc *up = externup(); + char *f0; + int i; + uint64_t end, start; + Cmdbuf *cb; + SDifc *ifc; + SDreq *req; + SDunit *unit; + SDev *sdev; + + switch(TYPE(c->qid)){ + default: + error(Eperm); + case Qtopctl: + cb = parsecmd(a, n); + if(waserror()){ + free(cb); + nexterror(); + } + if(cb->nf == 0) + error("empty control message"); + f0 = cb->f[0]; + cb->f++; + cb->nf--; + if(strcmp(f0, "config") == 0){ + /* wormhole into ugly legacy interface */ + legacytopctl(cb); + poperror(); + free(cb); + break; + } + /* + * "ata arg..." invokes sdifc[i]->wtopctl(nil, cb), + * where sdifc[i]->SDperm.name=="ata" and cb contains the args. + */ + ifc = nil; + sdev = nil; + for(i=0; sdifc[i]; i++){ + if(strcmp(sdifc[i]->name, f0) == 0){ + ifc = sdifc[i]; + sdev = nil; + goto subtopctl; + } + } + /* + * "sd1 arg..." invokes sdifc[i]->wtopctl(sdev, cb), + * where sdifc[i] and sdev match controller letter "1", + * and cb contains the args. + */ + if(f0[0]=='s' && f0[1]=='d' && f0[2] && f0[3] == 0){ + if((sdev = sdgetdev(f0[2])) != nil){ + ifc = sdev->ifc; + goto subtopctl; + } + } + error("unknown interface"); + + subtopctl: + if(waserror()){ + if(sdev) + decref(&sdev->r); + nexterror(); + } + if(ifc->wtopctl) + ifc->wtopctl(sdev, cb); + else + error(Ebadctl); + poperror(); + poperror(); + if (sdev) + decref(&sdev->r); + free(cb); + break; + + case Qctl: + cb = parsecmd(a, n); + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + unit = sdev->unit[UNIT(c->qid)]; + + qlock(&unit->ctl); + if(waserror()){ + qunlock(&unit->ctl); + decref(&sdev->r); + free(cb); + nexterror(); + } + if(unit->vers != c->qid.vers) + error(Echange); + + if(cb->nf < 1) + error(Ebadctl); + if(strcmp(cb->f[0], "part") == 0){ + if(cb->nf != 4) + error(Ebadctl); + if(unit->sectors == 0 && !sdinitpart(unit)) + error(Eio); + start = strtoull(cb->f[2], 0, 0); + end = strtoull(cb->f[3], 0, 0); + sdaddpart(unit, cb->f[1], start, end); + } + else if(strcmp(cb->f[0], "delpart") == 0){ + if(cb->nf != 2 || unit->part == nil) + error(Ebadctl); + sddelpart(unit, cb->f[1]); + } + else if(unit->dev->ifc->wctl) + unit->dev->ifc->wctl(unit, cb); + else + error(Ebadctl); + qunlock(&unit->ctl); + decref(&sdev->r); + poperror(); + free(cb); + break; + + case Qraw: + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + unit = sdev->unit[UNIT(c->qid)]; + qlock(&unit->raw); + if(waserror()){ + qunlock(&unit->raw); + decref(&sdev->r); + nexterror(); + } + switch(unit->state){ + case Rawcmd: + if(n < 6 || n > sizeof(req->cmd)) + error(Ebadarg); + if((req = malloc(sizeof(SDreq))) == nil) + error(Enomem); + req->unit = unit; + memmove(req->cmd, a, n); + req->clen = n; + req->flags = SDnosense; + req->status = ~0; + + unit->req = req; + unit->state = Rawdata; + break; + + case Rawstatus: + unit->state = Rawcmd; + free(unit->req); + unit->req = nil; + error(Ebadusefd); + + case Rawdata: + unit->state = Rawstatus; + unit->req->write = 1; + n = sdrio(unit->req, a, n); + } + qunlock(&unit->raw); + decref(&sdev->r); + poperror(); + break; + case Qpart: + return sdbio(c, 1, a, n, off); + } + + return n; +} + +static int32_t +sdwstat(Chan* c, uint8_t* dp, int32_t n) +{ + Proc *up = externup(); + Dir *d; + SDpart *pp; + SDperm *perm; + SDunit *unit; + SDev *sdev; + + if(c->qid.type & QTDIR) + error(Eperm); + + sdev = sdgetdev(DEV(c->qid)); + if(sdev == nil) + error(Enonexist); + unit = sdev->unit[UNIT(c->qid)]; + qlock(&unit->ctl); + d = nil; + if(waserror()){ + free(d); + qunlock(&unit->ctl); + decref(&sdev->r); + nexterror(); + } + + switch(TYPE(c->qid)){ + default: + error(Eperm); + case Qctl: + perm = &unit->ctlperm; + break; + case Qraw: + perm = &unit->rawperm; + break; + case Qpart: + pp = &unit->part[PART(c->qid)]; + if(unit->vers+pp->vers != c->qid.vers) + error(Enonexist); + perm = &pp->SDperm; + break; + } + + if(strcmp(up->user, perm->user) && !iseve()) + error(Eperm); + + d = smalloc(sizeof(Dir)+n); + n = convM2D(dp, n, &d[0], (char*)&d[1]); + if(n == 0) + error(Eshortstat); + if(!emptystr(d[0].uid)) + kstrdup(&perm->user, d[0].uid); + if(d[0].mode != (uint32_t)~0UL) + perm->perm = (perm->perm & ~0777) | (d[0].mode & 0777); + + free(d); + qunlock(&unit->ctl); + decref(&sdev->r); + poperror(); + return n; +} + +static int +configure(char* spec, DevConf* cf) +{ + SDev *s, *sdev; + char *p; + int i; + + if(sdindex(*spec) < 0) + error("bad sd spec"); + + if((p = strchr(cf->type, '/')) != nil) + *p++ = '\0'; + + for(i = 0; sdifc[i] != nil; i++) + if(strcmp(sdifc[i]->name, cf->type) == 0) + break; + if(sdifc[i] == nil) + error("sd type not found"); + if(p) + *(p-1) = '/'; + + if(sdifc[i]->probe == nil) + error("sd type cannot probe"); + + sdev = sdifc[i]->probe(cf); + for(s=sdev; s; s=s->next) + s->idno = *spec; + sdadddevs(sdev); + return 0; +} + +static int +unconfigure(char* spec) +{ + int i; + SDev *sdev; + SDunit *unit; + + if((i = sdindex(*spec)) < 0) + error(Enonexist); + + qlock(&devslock); + if((sdev = devs[i]) == nil){ + qunlock(&devslock); + error(Enonexist); + } + if(sdev->r.ref){ + qunlock(&devslock); + error(Einuse); + } + devs[i] = nil; + qunlock(&devslock); + + /* make sure no interrupts arrive anymore before removing resources */ + if(sdev->enabled && sdev->ifc->disable) + sdev->ifc->disable(sdev); + + for(i = 0; i != sdev->nunit; i++){ + if(unit = sdev->unit[i]){ + free(unit->SDperm.name); + free(unit->SDperm.user); + free(unit); + } + } + + if(sdev->ifc->clear) + sdev->ifc->clear(sdev); + free(sdev); + return 0; +} + +static int +sdconfig(int on, char* spec, DevConf* cf) +{ + if(on) + return configure(spec, cf); + return unconfigure(spec); +} + +Dev sddevtab = { + .dc = 'S', + .name = "sd", + + .reset = sdreset, + .init = devinit, + .shutdown = devshutdown, + .attach = sdattach, + .walk = sdwalk, + .stat = sdstat, + .open = sdopen, + .create = devcreate, + .close = sdclose, + .read = sdread, + .bread = devbread, + .write = sdwrite, + .bwrite = devbwrite, + .remove = devremove, + .wstat = sdwstat, + .power = devpower, + .config = sdconfig, /* probe; only called for pcmcia-like devices */ +}; + +/* + * This is wrong for so many reasons. This code must go. + */ +typedef struct Confdata Confdata; +struct Confdata { + int on; + char* spec; + DevConf cf; +}; + +static void +parseswitch(Confdata* cd, char* option) +{ + if(!strcmp("on", option)) + cd->on = 1; + else if(!strcmp("off", option)) + cd->on = 0; + else + error(Ebadarg); +} + +static void +parsespec(Confdata* cd, char* option) +{ + if(strlen(option) > 1) + error(Ebadarg); + cd->spec = option; +} + +static Devport* +getnewport(DevConf* dc) +{ + Devport *p; + + p = (Devport *)malloc((dc->nports + 1) * sizeof(Devport)); + if(p == nil) + error(Enomem); + if(dc->nports > 0){ + memmove(p, dc->ports, dc->nports * sizeof(Devport)); + free(dc->ports); + } + dc->ports = p; + p = &dc->ports[dc->nports++]; + p->size = -1; + p->port = (uint32_t)-1; + return p; +} + +static void +parseport(Confdata* cd, char* option) +{ + char *e; + Devport *p; + + if(cd->cf.nports == 0 || cd->cf.ports[cd->cf.nports-1].port != (uint32_t)-1) + p = getnewport(&cd->cf); + else + p = &cd->cf.ports[cd->cf.nports-1]; + p->port = strtol(option, &e, 0); + if(e == nil || *e != '\0') + error(Ebadarg); +} + +static void +parsesize(Confdata* cd, char* option) +{ + char *e; + Devport *p; + + if(cd->cf.nports == 0 || cd->cf.ports[cd->cf.nports-1].size != -1) + p = getnewport(&cd->cf); + else + p = &cd->cf.ports[cd->cf.nports-1]; + p->size = (int)strtol(option, &e, 0); + if(e == nil || *e != '\0') + error(Ebadarg); +} + +static void +parseirq(Confdata* cd, char* option) +{ + char *e; + + cd->cf.intnum = strtoul(option, &e, 0); + if(e == nil || *e != '\0') + error(Ebadarg); +} + +static void +parsetype(Confdata* cd, char* option) +{ + cd->cf.type = option; +} + +static struct { + char *name; + void (*parse)(Confdata*, char*); +} options[] = { + "switch", parseswitch, + "spec", parsespec, + "port", parseport, + "size", parsesize, + "irq", parseirq, + "type", parsetype, +}; + +static void +legacytopctl(Cmdbuf *cb) +{ + char *opt; + int i, j; + Confdata cd; + + memset(&cd, 0, sizeof cd); + cd.on = -1; + for(i=0; i<cb->nf; i+=2){ + if(i+2 > cb->nf) + error(Ebadarg); + opt = cb->f[i]; + for(j=0; j<nelem(options); j++) + if(strcmp(opt, options[j].name) == 0){ + options[j].parse(&cd, cb->f[i+1]); + break; + } + if(j == nelem(options)) + error(Ebadarg); + } + /* this has been rewritten to accomodate sdaoe */ + if(cd.on < 0 || cd.spec == 0) + error(Ebadarg); + if(cd.on && cd.cf.type == nil) + error(Ebadarg); + sdconfig(cd.on, cd.spec, &cd.cf); +} diff --git a/kern/drivers/dev/sdiahci.c b/kern/drivers/dev/sdiahci.c new file mode 100644 index 0000000..13665da --- /dev/null +++ b/kern/drivers/dev/sdiahci.c @@ -0,0 +1,2461 @@ +/* + * This file is part of the UCB release of Plan 9. It is subject to the license + * terms in the LICENSE file found in the top-level directory of this + * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No + * part of the UCB release of Plan 9, including this file, may be copied, + * modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +/* + * ahci serial ata driver + * 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" + +enum { + Vatiamd = 0x1002, + Vintel = 0x8086, + Vmarvell= 0x1b4b, +}; + +#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] +#define Intel(x) ((x)->pci->vid == Vintel) + +enum { + NCtlr = 16, + NCtlrdrv= 32, + NDrive = NCtlr*NCtlrdrv, + + Read = 0, + Write, + + Nms = 256, /* ms. between drive checks */ + Mphywait= 2*1024/Nms - 1, + Midwait = 16*1024/Nms - 1, + Mcomrwait= 64*1024/Nms - 1, + + Obs = 0xa0, /* obsolete device bits */ + + /* + * if we get more than this many interrupts per tick for a drive, + * either the hardware is broken or we've got a bug in this driver. + */ + Maxintrspertick = 2000, /* was 1000 */ +}; + +/* pci space configuration */ +enum { + Pmap = 0x90, + Ppcs = 0x91, + Prev = 0xa8, +}; + +enum { + Tesb, + Tich, + Tsb600, + Tunk, +}; + +static char *tname[] = { + "63xxesb", + "ich", + "sb600", + "unknown", +}; + +enum { + Dnull, + Dmissing, + Dnew, + Dready, + Derror, + Dreset, + Doffline, + Dportreset, + Dlast, +}; + +static char *diskstates[Dlast] = { + "null", + "missing", + "new", + "ready", + "error", + "reset", + "offline", + "portreset", +}; + +enum { + DMautoneg, + DMsatai, + DMsataii, + DMsata3, +}; + +static char *modename[] = { /* used in control messages */ + "auto", + "satai", + "sataii", + "sata3", +}; +static char *descmode[] = { /* only printed */ + "auto", + "sata 1", + "sata 2", + "sata 3", +}; + +static char *flagname[] = { + "llba", + "smart", + "power", + "nop", + "atapi", + "atapi16", +}; + +typedef struct Asleep Asleep; +typedef struct Ctlr Ctlr; +typedef struct Drive Drive; + +struct Drive { + Lock Lock; + + Ctlr *ctlr; + SDunit *unit; + char name[10]; + Aport *port; + Aportm portm; + Aportc portc; /* redundant ptr to port and portm */ + + unsigned char mediachange; + unsigned char state; + unsigned char smartrs; + + uint64_t sectors; + uint32_t secsize; + uint32_t intick; /* start tick of current transfer */ + uint32_t lastseen; + int wait; + unsigned char mode; /* DMautoneg, satai or sataii */ + unsigned char active; + + char serial[20+1]; + char firmware[8+1]; + char model[40+1]; + + int infosz; + uint16_t *info; + uint16_t tinyinfo[2]; /* used iff malloc fails */ + + int driveno; /* ctlr*NCtlrdrv + unit */ + /* controller port # != driveno when not all ports are enabled */ + int portno; + + uint32_t lastintr0; + uint32_t intrs; +}; + +struct Ctlr { + Lock Lock; + + int type; + int enabled; + SDev *sdev; + Pcidev *pci; + void* vector; + + /* virtual register addresses */ + unsigned char *mmio; + uint32_t *lmmio; + Ahba *hba; + + /* phyical register address */ + unsigned char *physio; + + Drive *rawdrive; + Drive *drive[NCtlrdrv]; + int ndrive; + int mport; /* highest drive # (0-origin) on ich9 at least */ + + uint32_t lastintr0; + uint32_t intrs; /* not attributable to any drive */ +}; + +struct Asleep { + Aport *p; + int i; +}; + +extern SDifc sdiahciifc; + +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; + +// TODO: does this get initialized correctly? +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(uint32_t 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(unsigned char *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=%#lx; cmd=%#lx; ci=%#lx; is=%#lx\n", + s, p->task, p->cmd, p->ci, p->isr); +} + +static void +esleep(int ms) +{ + Proc *up = externup(); + if(waserror()) + return; + tsleep(&up->sleep, return0, 0, ms); + poperror(); +} + +static int +ahciclear(void *v) +{ + Asleep *s; + + s = v; + return (s->p->ci & s->i) == 0; +} + +static void +aesleep(Aportm *pm, Asleep *a, int ms) +{ + Proc *up = externup(); + if(waserror()) + return; + tsleep(&pm->Rendez, 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->pm, &as, ms); + if((p->task&1) == 0 && p->ci == 0) + return 0; + dreg("ahciwait timeout ", c->p); + return -1; +} + +/* fill in cfis boilerplate */ +static unsigned char * +cfissetup(Aportc *pc) +{ + unsigned char *cfis; + + cfis = pc->pm->ctab->cfis; + memset(cfis, 0, 0x20); + cfis[0] = 0x27; + cfis[1] = 0x80; + cfis[7] = Obs; + return cfis; +} + +/* initialise pc's list */ +static void +listsetup(Aportc *pc, int flags) +{ + Alist *list; + + list = pc->pm->list; + list->flags = flags | 5; + list->len = 0; + list->ctab = PCIWADDR(pc->pm->ctab); + list->ctabhi = 0; +} + +static int +nop(Aportc *pc) +{ + unsigned char *c; + + if((pc->pm->feat & Dnop) == 0) + return -1; + c = cfissetup(pc); + c[2] = 0; + listsetup(pc, Lwrite); + return ahciwait(pc, 3*1000); +} + +static int +setfeatures(Aportc *pc, unsigned char f) +{ + unsigned char *c; + + c = cfissetup(pc); + c[2] = 0xef; + c[3] = f; + listsetup(pc, Lwrite); + return ahciwait(pc, 3*1000); +} + +static int +setudmamode(Aportc *pc, unsigned char f) +{ + unsigned char *c; + + /* hack */ + if((pc->p->sig >> 16) == 0xeb14) + return 0; + c = cfissetup(pc); + c[2] = 0xef; + c[3] = 3; /* set transfer mode */ + c[12] = 0x40 | f; /* sector count */ + listsetup(pc, Lwrite); + return ahciwait(pc, 3*1000); +} + +static void +asleep(int ms) +{ + Proc *up = externup(); + if(up == nil) + delay(ms); + else + esleep(ms); +} + +static int +ahciportreset(Aportc *c) +{ + uint32_t *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) +{ + unsigned char *c; + + if((pc->pm->feat&Dsmart) == 0) + return -1; + c = cfissetup(pc); + c[2] = 0xb0; + c[3] = 0xd8 + n; /* able smart */ + c[5] = 0x4f; + c[6] = 0xc2; + listsetup(pc, Lwrite); + if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){ + dprint("ahci: smart fail %#lx\n", pc->p->task); +// preg(pc->m->fis.r, 20); + return -1; + } + if(n) + return 0; + return 1; +} + +static int +smartrs(Aportc *pc) +{ + unsigned char *c; + + c = cfissetup(pc); + c[2] = 0xb0; + c[3] = 0xda; /* return smart status */ + c[5] = 0x4f; + c[6] = 0xc2; + listsetup(pc, Lwrite); + + c = pc->pm->fis.r; + if(ahciwait(pc, 1000) == -1 || pc->p->task & (1|32)){ + dprint("ahci: smart fail %#lx\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) +{ + unsigned char *c; + + c = cfissetup(pc); + c[2] = pc->pm->feat & Dllba? 0xea: 0xe7; + listsetup(pc, Lwrite); + if(ahciwait(pc, 60000) == -1 || pc->p->task & (1|32)){ + dprint("ahciflushcache: fail %#lx\n", pc->p->task); +// preg(pc->m->fis.r, 20); + return -1; + } + return 0; +} + +static uint16_t +gbit16(void *a) +{ + unsigned char *i; + + i = a; + return i[1]<<8 | i[0]; +} + +static uint32_t +gbit32(void *a) +{ + uint32_t j; + unsigned char *i; + + i = a; + j = i[3] << 24; + j |= i[2] << 16; + j |= i[1] << 8; + j |= i[0]; + return j; +} + +static uint64_t +gbit64(void *a) +{ + unsigned char *i; + + i = a; + return (uint64_t)gbit32(i+4) << 32 | gbit32(a); +} + +static int +ahciidentify0(Aportc *pc, void *id, int atapi) +{ + unsigned char *c; + Aprdt *p; + static unsigned char tab[] = { 0xec, 0xa1, }; + + c = cfissetup(pc); + c[2] = tab[atapi]; + listsetup(pc, 1<<16); + + memset(id, 0, 0x100); /* magic */ + p = &pc->pm->ctab->prdt; + p->dba = PCIWADDR(id); + p->dbahi = 0; + p->count = 1<<31 | (0x200-2) | 1; + return ahciwait(pc, 3*1000); +} + +static int64_t +ahciidentify(Aportc *pc, uint16_t *id) +{ + int i, sig; + int64_t s; + Aportm *pm; + + pm = pc->pm; + pm->feat = 0; + pm->smart = 0; + i = 0; + sig = pc->p->sig >> 16; + if(sig == 0xeb14){ + pm->feat |= Datapi; + i = 1; + } + if(ahciidentify0(pc, id, i) == -1) + return -1; + + i = gbit16(id+83) | gbit16(id+86); + if(i & (1<<10)){ + pm->feat |= Dllba; + s = gbit64(id+100); + }else + s = gbit32(id+60); + + if(pm->feat&Datapi){ + i = gbit16(id+0); + if(i&1) + pm->feat |= Datapi16; + } + + i = gbit16(id+83); + if((i>>14) == 1) { + if(i & (1<<3)) + pm->feat |= Dpower; + i = gbit16(id+82); + if(i & 1) + pm->feat |= Dsmart; + if(i & (1<<14)) + pm->feat |= Dnop; + } + return s; +} + +#if 0 +static int +ahciquiet(Aport *a) +{ + uint32_t *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; +} +#endif + +#if 0 +static int +ahcicomreset(Aportc *pc) +{ + unsigned char *c; + + dprint("ahcicomreset\n"); + dreg("ahci: comreset ", pc->p); + if(ahciquiet(pc->p) == -1){ + dprint("ahciquiet failed\n"); + return -1; + } + dreg("comreset ", pc->p); + + c = cfissetup(pc); + c[1] = 0; + c[15] = 1<<2; /* srst */ + listsetup(pc, Lclear | Lreset); + if(ahciwait(pc, 500) == -1){ + dprint("ahcicomreset: first command failed\n"); + return -1; + } + microdelay(250); + dreg("comreset ", pc->p); + + c = cfissetup(pc); + c[1] = 0; + listsetup(pc, Lwrite); + if(ahciwait(pc, 150) == -1){ + dprint("ahcicomreset: second command failed\n"); + return -1; + } + dreg("comreset ", pc->p); + return 0; +} +#endif + +static int +ahciidle(Aport *port) +{ + uint32_t *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) +{ + return mallocalign(size, align, 0, 0); +} + +static void +setupfis(Afis *f) +{ + f->base = malign(0x100, 0x100); /* magic */ + f->d = f->base + 0; + f->p = f->base + 0x20; + f->r = f->base + 0x40; + f->u = f->base + 0x60; + f->devicebits = (uint32_t*)(f->base + 0x58); +} + +static void +ahciwakeup(Aport *p) +{ + uint16_t s; + + s = p->sstatus; + if((s & Intpm) != Intslumber && (s & Intpm) != Intpartpwr) + return; + if((s & Devdet) != Devpresent){ /* not (device, no phy) */ + iprint("ahci: slumbering drive unwakable %#x\n", s); + return; + } + p->sctl = 3*Aipm | 0*Aspd | Adet; + delay(1); + p->sctl &= ~7; +// iprint("ahci: wake %#x -> %#x\n", s, p->sstatus); +} + +static int +ahciconfigdrive(Drive *d) +{ + char *name; + Ahba *h; + Aport *p; + Aportm *pm; + + h = d->ctlr->hba; + p = d->portc.p; + pm = d->portc.pm; + if(pm->list == 0){ + setupfis(&pm->fis); + pm->list = malign(sizeof *pm->list, 1024); + pm->ctab = malign(sizeof *pm->ctab, 128); + } + + if (d->unit) + name = d->unit->SDperm.name; + else + name = nil; + if(p->sstatus & (Devphycomm|Devpresent) && h->cap & Hsss){ + /* device connected & staggered spin-up */ + dprint("ahci: configdrive: %s: spinning up ... [%#lx]\n", + name, p->sstatus); + p->cmd |= Apod|Asud; + asleep(1400); + } + + p->serror = SerrAll; + + p->list = PCIWADDR(pm->list); + p->listhi = 0; + p->fis = PCIWADDR(pm->fis.base); + p->fishi = 0; + p->cmd |= Afre|Ast; + + /* drive coming up in slumbering? */ + if((p->sstatus & Devdet) == Devpresent && + ((p->sstatus & Intpm) == Intslumber || + (p->sstatus & Intpm) == Intpartpwr)) + ahciwakeup(p); + + /* "disable power managment" sequence from book. */ + p->sctl = (3*Aipm) | (d->mode*Aspd) | (0*Adet); + p->cmd &= ~Aalpe; + + p->ie = IEM; + + return 0; +} + +static void +ahcienable(Ahba *h) +{ + h->ghc |= Hie; +} + +static void +ahcidisable(Ahba *h) +{ + h->ghc &= ~Hie; +} + +static int +countbits(uint32_t u) +{ + int n; + + n = 0; + for (; u != 0; u >>= 1) + if(u & 1) + n++; + return n; +} + +static int +ahciconf(Ctlr *ctlr) +{ + Ahba *h; + uint32_t u; + + h = ctlr->hba = (Ahba*)ctlr->mmio; + u = h->cap; + + if((u&Hsam) == 0) + h->ghc |= Hae; + + dprint("#S/sd%c: type %s port %#p: sss %ld ncs %ld coal %ld " + "%ld ports, 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) + 1, (u>>25) & 1, (u>>24) & 1, (u>>6) & 1); + return countbits(h->pi); +} + +#if 0 +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; +} +#endif + +static void +idmove(char *p, uint16_t *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) +{ + uint16_t *id; + int64_t osectors, s; + unsigned char oserial[21]; + SDunit *u; + + if(d->info == nil) { + d->infosz = 512 * sizeof(uint16_t); + d->info = malloc(d->infosz); + } + if(d->info == nil) { + d->info = d->tinyinfo; + d->infosz = sizeof d->tinyinfo; + } + 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) +{ + uint32_t cause, serr, s0, pr, ewake; + char *name; + Aport *p; + static uint32_t last; + + pr = 1; + ewake = 0; + p = d->port; + cause = p->isr; + serr = p->serror; + p->isr = cause; + name = "??"; + if(d->unit && d->unit->SDperm.name) + name = d->unit->SDperm.name; + + if(p->ci == 0){ + d->portm.flag |= Fdone; + wakeup(&d->portm.Rendez); + pr = 0; + }else if(cause & Adps) + pr = 0; + if(cause & Ifatal){ + ewake = 1; + dprint("ahci: updatedrive: %s: fatal\n", name); + } + if(cause & Adhrs){ + if(p->task & (1<<5|1)){ + dprint("ahci: %s: Adhrs cause %#lx serr %#lx task %#lx\n", + name, cause, serr, p->task); + d->portm.flag |= Ferror; + ewake = 1; + } + pr = 0; + } + if(p->task & 1 && last != cause) + dprint("%s: err ca %#lx serr %#lx task %#lx sstat %#lx\n", + name, cause, serr, p->task, p->sstatus); + if(pr) + dprint("%s: upd %#lx ta %#lx\n", name, cause, p->task); + + if(cause & (Aprcs|Aifs)){ + s0 = d->state; + switch(p->sstatus & Devdet){ + case 0: /* no device */ + d->state = Dmissing; + break; + case Devpresent: /* device but no phy comm. */ + if((p->sstatus & Intpm) == Intslumber || + (p->sstatus & Intpm) == Intpartpwr) + d->state = Dnew; /* slumbering */ + else + d->state = Derror; + break; + case Devpresent|Devphycomm: + /* power mgnt crap for surprise removal */ + p->ie |= Aprcs|Apcs; /* is this required? */ + d->state = Dreset; + break; + case Devphyoffline: + d->state = Doffline; + break; + } + dprint("%s: %s → %s [Apcrs] %#lx\n", name, + diskstates[s0], diskstates[d->state], p->sstatus); + /* print pulled message here. */ + if(s0 == Dready && d->state != Dready) + idprint("%s: pulled\n", name); /* wtf? */ + if(d->state != Dready) + d->portm.flag |= Ferror; + ewake = 1; + } + p->serror = serr; + if(ewake){ + clearci(p); + wakeup(&d->portm.Rendez); + } + last = cause; +} + +static void +pstatus(Drive *d, uint32_t s) +{ + /* + * s is masked with Devdet. + * + * 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 Devpresent: /* device but no phy. comm. */ + break; + case Devphycomm: /* should this be missing? need testcase. */ + dprint("ahci: pstatus 2\n"); + /* fallthrough */ + case Devpresent|Devphycomm: + d->wait = 0; + d->state = Dnew; + break; + case Devphyoffline: + d->state = Doffline; + break; + case Devphyoffline|Devphycomm: /* does this make sense? */ + d->state = Dnew; + break; + } +} + +static int +configdrive(Drive *d) +{ + if(ahciconfigdrive(d) == -1) + return -1; + ilock(&d->Lock); + pstatus(d, d->port->sstatus & Devdet); + iunlock(&d->Lock); + return 0; +} + +static void +setstate(Drive *d, int state) +{ + ilock(&d->Lock); + d->state = state; + iunlock(&d->Lock); +} + +static void +resetdisk(Drive *d) +{ + uint state, det, stat; + Aport *p; + + p = d->port; + det = p->sctl & 7; + stat = p->sstatus & Devdet; + state = (p->cmd>>28) & 0xf; + dprint("ahci: resetdisk: icc %#x det %d sdet %d\n", state, det, stat); + + ilock(&d->Lock); + state = d->state; + if(d->state != Dready || d->state != Dnew) + d->portm.flag |= Ferror; + clearci(p); /* satisfy sleep condition. */ + wakeup(&d->portm.Rendez); + if(stat != (Devpresent|Devphycomm)){ + /* device absent or phy not communicating */ + d->state = Dportreset; + iunlock(&d->Lock); + return; + } + d->state = Derror; + iunlock(&d->Lock); + + qlock(&d->portm.ql); + if(p->cmd&Ast && ahciswreset(&d->portc) == -1) + setstate(d, Dportreset); /* get a bigger stick. */ + else { + setstate(d, Dmissing); + configdrive(d); + } + dprint("ahci: %s: resetdisk: %s → %s\n", (d->unit? d->unit->SDperm.name: nil), + diskstates[state], diskstates[d->state]); + qunlock(&d->portm.ql); +} + +static int +newdrive(Drive *d) +{ + char *name; + Aportc *c; + Aportm *pm; + + c = &d->portc; + pm = &d->portm; + + name = d->unit->SDperm.name; + if(name == 0) + name = "??"; + + if(d->port->task == 0x80) + return -1; + qlock(&c->pm->ql); + 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(pm->feat & Dpower && setfeatures(c, 0x85) == -1){ + pm->feat &= ~Dpower; + if(ahcirecover(c) == -1) + goto lose; + } + setstate(d, Dready); + qunlock(&c->pm->ql); + + idprint("%s: %sLBA %,llu sectors: %s %s %s %s\n", d->unit->SDperm.name, + (pm->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->SDperm.name); + setstate(d, Dnull); + qunlock(&c->pm->ql); + return -1; +} + +static void +westerndigitalhung(Drive *d) +{ + if((d->portm.feat&Datapi) == 0 && d->active && + TK2MS(sys->ticks - d->intick) > 5000){ + dprint("%s: drive hung; resetting [%#lx] ci %#lx\n", + d->unit->SDperm.name, d->port->task, d->port->ci); + d->state = Dreset; + } +} + +static uint16_t olds[NCtlr*NCtlrdrv]; + +static int +doportreset(Drive *d) +{ + int i; + + i = -1; + qlock(&d->portm.ql); + if(ahciportreset(&d->portc) == -1) + dprint("ahci: doportreset: fails\n"); + else + i = 0; + qunlock(&d->portm.ql); + dprint("ahci: doportreset: portreset → %s [task %#lx]\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) +{ + uint16_t s; + char *name; + + if(d == nil) { + print("checkdrive: nil d\n"); + return; + } + ilock(&d->Lock); + if(d->unit == nil || d->port == nil) { + if(0) + print("checkdrive: nil d->%s\n", + d->unit == nil? "unit": "port"); + iunlock(&d->Lock); + return; + } + name = d->unit->SDperm.name; + s = d->port->sstatus; + if(s) + d->lastseen = sys->ticks; + if(s != olds[i]){ + dprint("%s: status: %06#x -> %06#x: %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 & (Intactive | Devdet)){ + case Devpresent: /* no device (pm), device but no phy. comm. */ + ahciwakeup(d->port); + /* fall through */ + case 0: /* no device */ + break; + default: + dprint("%s: unknown status %06#x\n", name, s); + /* fall through */ + case Intactive: /* 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->Lock); + resetdisk(d); + ilock(&d->Lock); + break; + case Intactive|Devphycomm|Devpresent: + if((++d->wait&Midwait) == 0){ + dprint("%s: slow reset %06#x task=%#lx; %d\n", + name, s, d->port->task, d->wait); + goto reset; + } + s = (unsigned char)d->port->task; + if(s == 0x7f || ((d->port->sig >> 16) != 0xeb14 && + (s & ~0x17) != (1<<6))) + break; + iunlock(&d->Lock); + newdrive(d); + ilock(&d->Lock); + break; + } + break; + case Doffline: + if(d->wait++ & Mcomrwait) + break; + /* fallthrough */ + case Derror: + case Dreset: + dprint("%s: reset [%s]: mode %d; status %06#x\n", + name, diskstates[d->state], d->mode, s); + iunlock(&d->Lock); + resetdisk(d); + ilock(&d->Lock); + break; + case Dportreset: +portreset: + if(d->wait++ & 0xff && (s & Intactive) == 0) + break; + /* device is active */ + dprint("%s: portreset [%s]: mode %d; status %06#x\n", + name, diskstates[d->state], d->mode, s); + d->portm.flag |= Ferror; + clearci(d->port); + wakeup(&d->portm.Rendez); + if((s & Devdet) == 0){ /* no device */ + d->state = Dmissing; + break; + } + iunlock(&d->Lock); + doportreset(d); + ilock(&d->Lock); + break; + } + statechange(d); + iunlock(&d->Lock); +} + +static void +satakproc(void *v) +{ + Proc *up = externup(); + int i; + for(;;){ + tsleep(&up->sleep, return0, 0, Nms); + for(i = 0; i < niadrive; i++) + if(iadrive[i] != nil) + checkdrive(iadrive[i], i); + } +} + +static void +isctlrjabbering(Ctlr *c, uint32_t cause) +{ + uint32_t now; + + now = TK2MS(sys->ticks); + if (now > c->lastintr0) { + c->intrs = 0; + c->lastintr0 = now; + } + if (++c->intrs > Maxintrspertick) { + iprint("sdiahci: %lu intrs per tick for no serviced " + "drive; cause %#lx mport %d\n", + c->intrs, cause, c->mport); + c->intrs = 0; + } +} + +static void +isdrivejabbering(Drive *d) +{ + uint32_t now; + + now = TK2MS(sys->ticks); + if (now > d->lastintr0) { + d->intrs = 0; + d->lastintr0 = now; + } + if (++d->intrs > Maxintrspertick) { + iprint("sdiahci: %lu interrupts per tick for %s\n", + d->intrs, d->unit->SDperm.name); + d->intrs = 0; + } +} + +static void +iainterrupt(Ureg *u, void *a) +{ + int i; + uint32_t cause, mask; + Ctlr *c; + Drive *d; + + c = a; + ilock(&c->Lock); + cause = c->hba->isr; + if (cause == 0) { + isctlrjabbering(c, cause); + // iprint("sdiahci: interrupt for no drive\n"); + iunlock(&c->Lock); + return; + } + for(i = 0; cause && i <= c->mport; i++){ + mask = 1 << i; + if((cause & mask) == 0) + continue; + d = c->rawdrive + i; + ilock(&d->Lock); + isdrivejabbering(d); + if(d->port->isr && c->hba->pi & mask) + updatedrive(d); + c->hba->isr = mask; + iunlock(&d->Lock); + + cause &= ~mask; + } + if (cause) { + isctlrjabbering(c, cause); + iprint("sdiachi: intr cause unserviced: %#lx\n", cause); + } + iunlock(&c->Lock); +} + +/* checkdrive, called from satakproc, will prod the drive while we wait */ +static void +awaitspinup(Drive *d) +{ + int ms; + uint16_t s; + char *name; + + ilock(&d->Lock); + if(d->unit == nil || d->port == nil) { + panic("awaitspinup: nil d->unit or d->port"); + iunlock(&d->Lock); + return; + } + name = (d->unit? d->unit->SDperm.name: nil); + s = d->port->sstatus; + if(!(s & Devpresent)) { /* never going to be ready */ + dprint("awaitspinup: %s absent, not waiting\n", name); + iunlock(&d->Lock); + return; + } + + for (ms = 20000; ms > 0; ms -= 50) + switch(d->state){ + case Dnull: + /* absent; done */ + iunlock(&d->Lock); + dprint("awaitspinup: %s in null state\n", name); + return; + case Dready: + case Dnew: + if(d->sectors || d->mediachange) { + /* ready to use; done */ + iunlock(&d->Lock); + dprint("awaitspinup: %s ready!\n", name); + return; + } + /* fall through */ + default: + case Dmissing: /* normal waiting states */ + case Dreset: + case Doffline: /* transitional states */ + case Derror: + case Dportreset: + iunlock(&d->Lock); + asleep(50); + ilock(&d->Lock); + break; + } + print("awaitspinup: %s didn't spin up after 20 seconds\n", name); + iunlock(&d->Lock); +} + +static int +iaverify(SDunit *u) +{ + Ctlr *c; + Drive *d; + + c = u->dev->ctlr; + d = c->drive[u->subno]; + ilock(&c->Lock); + ilock(&d->Lock); + d->unit = u; + iunlock(&d->Lock); + iunlock(&c->Lock); + checkdrive(d, d->driveno); /* c->d0 + d->driveno */ + + /* + * hang around until disks are spun up and thus available as + * nvram, dos file systems, etc. you wouldn't expect it, but + * the intel 330 ssd takes a while to `spin up'. + */ + awaitspinup(d); + return 1; +} + +static int +iaenable(SDev *s) +{ + char name[32]; + Ctlr *c; + static int once; + + c = s->ctlr; + ilock(&c->Lock); + if(!c->enabled) { + if(once == 0) { + once = 1; + kproc("ahci", 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); + c->vector = intrenable(c->pci->intl, iainterrupt, c, c->pci->tbdf, name); + /* supposed to squelch leftover interrupts here. */ + ahcienable(c->hba); + c->enabled = 1; + } + iunlock(&c->Lock); + return 1; +} + +static int +iadisable(SDev *s) +{ + char name[32]; + Ctlr *c; + + c = s->ctlr; + ilock(&c->Lock); + ahcidisable(c->hba); + snprint(name, sizeof name, "%s (%s)", s->name, s->ifc->name); + intrdisable(c->vector); + c->enabled = 0; + iunlock(&c->Lock); + 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->Lock); + 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->Lock); + return r; +} + +/* returns locked list! */ +static Alist* +ahcibuild(Drive *d, unsigned char *cmd, void *data, int n, int64_t lba) +{ + unsigned char *c, acmd, dir, llba; + Alist *l; + Actab *t; + Aportm *pm; + Aprdt *p; + static unsigned char tab[2][2] = { 0xc8, 0x25, 0xca, 0x35, }; + + pm = &d->portm; + dir = *cmd != 0x28; + llba = pm->feat&Dllba? 1: 0; + acmd = tab[dir][llba]; + qlock(&pm->ql); + l = pm->list; + t = pm->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] = Obs | 0x40; /* 0x40 == 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 */ + + *(uint32_t*)(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 *pm, SDreq *r, void *data, int n) +{ + int fill, len; + unsigned char *c; + Alist *l; + Actab *t; + Aprdt *p; + + qlock(&pm->ql); + l = pm->list; + t = pm->ctab; + c = t->cfis; + + fill = pm->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] = Obs; + + *(uint32_t*)(c + 8) = 0; + *(uint32_t*)(c + 12) = 0; + *(uint32_t*)(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) +{ + uint32_t s, i, delta; + + for(i = 0; i < 15000; i += 250){ + if(d->state == Dreset || d->state == Dportreset || + d->state == Dnew) + return 1; + delta = sys->ticks - d->lastseen; + if(d->state == Dnull || delta > 10*1000) + return -1; + ilock(&d->Lock); + s = d->port->sstatus; + iunlock(&d->Lock); + if((s & Intpm) == 0 && delta > 1500) + return -1; /* no detect */ + if(d->state == Dready && + (s & Devdet) == (Devphycomm|Devpresent)) + return 0; /* ready, present & phy. comm. */ + esleep(250); + } + print("%s: not responding; offline\n", d->unit->SDperm.name); + setstate(d, Doffline); + return -1; +} + +static int +lockready(Drive *d) +{ + int i; + + qlock(&d->portm.ql); + while ((i = waitready(d)) == 1) { /* could wait forever? */ + qunlock(&d->portm.ql); + esleep(1); + qlock(&d->portm.ql); + } + return i; +} + +static int +flushcache(Drive *d) +{ + int i; + + i = -1; + if(lockready(d) == 0) + i = ahciflushcache(&d->portc); + qunlock(&d->portm.ql); + return i; +} + +static int +iariopkt(SDreq *r, Drive *d) +{ + Proc *up = externup(); + int n, count, try, max, flag, task, wormwrite; + char *name; + unsigned char *cmd, *data; + Aport *p; + Asleep as; + + cmd = r->cmd; + name = d->unit->SDperm.name; + p = d->port; + + aprint("ahci: iariopkt: %04#x %04#x %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, d->infosz); + 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.ql); + return SDeio; + case 1: + qunlock(&d->portm.ql); + esleep(1); + goto retry; + } + /* d->portm qlock held here */ + + ilock(&d->Lock); + d->portm.flag = 0; + iunlock(&d->Lock); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = sys->ticks; + d->active++; + + while(waserror()) + ; + /* don't sleep here forever */ + tsleep(&d->portm.Rendez, ahciclear, &as, 3*1000); + poperror(); + if(!ahciclear(&as)) { + qunlock(&d->portm.ql); + print("%s: ahciclear not true after 3 seconds\n", name); + r->status = SDcheck; + return SDcheck; + } + + d->active--; + ilock(&d->Lock); + flag = d->portm.flag; + task = d->port->task; + iunlock(&d->Lock); + + 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.ql); + if(flag == 0){ + if(++try == 10){ + print("%s: bad disk\n", name); + r->status = SDcheck; + return SDcheck; + } + /* + * write retries cannot succeed on write-once media, + * so just accept any failure. + */ + wormwrite = 0; + switch(d->unit->inquiry[0] & SDinq0periphtype){ + case SDperworm: + case SDpercd: + switch(cmd[0]){ + case 0x0a: /* write (6?) */ + case 0x2a: /* write (10) */ + case 0x8a: /* int32_t write (16) */ + case 0x2e: /* write and verify (10) */ + wormwrite = 1; + break; + } + break; + } + if (!wormwrite) { + print("%s: retry\n", name); + goto retry; + } + } + if(flag & Ferror){ + if((task&Eidnf) == 0) + print("%s: i/o error task=%#x\n", name, task); + r->status = SDcheck; + return SDcheck; + } + + data += n; + + r->rlen = data - (unsigned char*)r->data; + r->status = SDok; + return SDok; +} + +static int +iario(SDreq *r) +{ + Proc *up = externup(); + int i, n, count, try, max, flag, task; + int64_t lba; + char *name; + unsigned char *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->SDperm.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, d->infosz)) != SDnostatus){ + r->status = i; + return i; + } + + if(*cmd != 0x28 && *cmd != 0x2a){ + print("%s: bad cmd %.2#x\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.ql); + return SDeio; + case 1: + qunlock(&d->portm.ql); + esleep(1); + goto retry; + } + /* d->portm qlock held here */ + ilock(&d->Lock); + d->portm.flag = 0; + iunlock(&d->Lock); + p->ci = 1; + + as.p = p; + as.i = 1; + d->intick = sys->ticks; + d->active++; + + while(waserror()) + ; + /* don't sleep here forever */ + tsleep(&d->portm.Rendez, ahciclear, &as, 3*1000); + poperror(); + if(!ahciclear(&as)) { + qunlock(&d->portm.ql); + print("%s: ahciclear not true after 3 seconds\n", name); + r->status = SDcheck; + return SDcheck; + } + + d->active--; + ilock(&d->Lock); + flag = d->portm.flag; + task = d->port->task; + iunlock(&d->Lock); + + if(task & (Efatal<<8) || + task & (ASbsy|ASdrq) && d->state == Dready){ + d->port->ci = 0; + ahcirecover(&d->portc); + task = d->port->task; + } + qunlock(&d->portm.ql); + if(flag == 0){ + if(++try == 10){ + print("%s: bad disk\n", name); + r->status = SDeio; + return SDeio; + } + print("%s: retry blk %lld\n", name, lba); + goto retry; + } + if(flag & Ferror){ + print("%s: i/o error task=%#x @%,lld\n", + name, task, lba); + r->status = SDeio; + return SDeio; + } + + count -= n; + lba += n; + data += n * unit->secsize; + } + r->rlen = data - (unsigned char*)r->data; + r->status = SDok; + return SDok; +} + +/* + * configure drives 0-5 as ahci sata (c.f. errata). + * what about 6 & 7, as claimed by marvell 0x9123? + */ +static int +iaahcimode(Pcidev *p) +{ + dprint("iaahcimode: %#x %#x %#x\n", pcicfgr8(p, 0x91), pcicfgr8(p, 92), + pcicfgr8(p, 93)); + pcicfgw16(p, 0x92, pcicfgr16(p, 0x92) | 0x3f); /* ports 0-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 and 6 ports; from ich9 datasheet */ + pcicfgw16(c->pci, 0x90, 1<<6 | 1<<5); +} + +static int +didtype(Pcidev *p) +{ + switch(p->vid){ + case Vintel: + if((p->did & 0xfffc) == 0x2680) + return Tesb; + /* + * 0x27c4 is the intel 82801 in compatibility (not sata) mode. + */ + if (p->did == 0x1e02 || /* c210 */ + p->did == 0x24d1 || /* 82801eb/er */ + (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 & 0xfff8) == 0x3b28) /* pchm */ + return Tich; + break; + case Vatiamd: + if(p->did == 0x4380 || p->did == 0x4390 || p->did == 0x4391){ + print("detected sb600 vid %#x did %#x\n", p->vid, p->did); + return Tsb600; + } + break; + case Vmarvell: + if (p->did == 0x9123) + print("ahci: marvell sata 3 controller has delusions " + "of something on unit 7\n"); + break; + } + if(p->ccrb == Pcibcstore && p->ccru == Pciscsata && p->ccrp == 1){ + print("ahci: Tunk: vid %#4.4x did %#4.4x\n", p->vid, p->did); + return Tunk; + } + return -1; +} + +static int +newctlr(Ctlr *ctlr, SDev *sdev, int nunit) +{ + int i, n; + Drive *drive; + + ctlr->ndrive = sdev->nunit = nunit; + ctlr->mport = ctlr->hba->cap & ((1<<5)-1); + + i = (ctlr->hba->cap >> 20) & ((1<<4)-1); /* iss */ + print("#S/sd%c: %s: %#p %s, %d ports, irq %d\n", sdev->idno, + Tname(ctlr), ctlr->physio, descmode[i], nunit, ctlr->pci->intl); + /* map the drives -- they don't all need to be enabled. */ + n = 0; + ctlr->rawdrive = malloc(NCtlrdrv * sizeof(Drive)); + if(ctlr->rawdrive == nil) { + print("ahci: out of memory\n"); + return -1; + } + for(i = 0; i < NCtlrdrv; i++) { + drive = ctlr->rawdrive + i; + drive->portno = i; + drive->driveno = -1; + drive->sectors = 0; + drive->serial[0] = ' '; + drive->ctlr = ctlr; + if((ctlr->hba->pi & (1<<i)) == 0) + continue; + drive->port = (Aport*)(ctlr->mmio + 0x80*i + 0x100); + drive->portc.p = drive->port; + drive->portc.pm = &drive->portm; + drive->driveno = n++; + ctlr->drive[drive->driveno] = drive; + iadrive[niadrive + drive->driveno] = drive; + } + for(i = 0; i < n; i++) + if(ahciidle(ctlr->drive[i]->port) == -1){ + dprint("ahci: %s: port %d wedged; abort\n", + Tname(ctlr), i); + return -1; + } + for(i = 0; i < n; i++){ + ctlr->drive[i]->mode = DMsatai; + configdrive(ctlr->drive[i]); + } + return n; +} + +static SDev* +iapnp(void) +{ + int n, nunit, type; + uintptr_t io; + Ctlr *c; + Pcidev *p; + SDev *head, *tail, *s; + static int done; + + if(done++) + return nil; + + memset(olds, 0xff, sizeof olds); + p = nil; + head = tail = nil; + 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->physio = (unsigned char *)io; + c->mmio = vmap(io, p->mem[Abar].size); + if(c->mmio == 0){ + print("ahci: %s: address %#lX in use did=%#x\n", + Tname(c), io, p->did); + continue; + } + c->lmmio = (uint32_t*)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; + } + n = newctlr(c, s, nunit); + if(n < 0) + continue; + 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, unsigned char f) +{ + unsigned char 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 %#lx cmd %#lx serr %#lx %s ci %#lx " + "is %#lx; sig %#lx sstatus %06#lx\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 %llu %lu\n", d->sectors, d->unit->secsize); + return p - op; +} + +static void +runflushcache(Drive *d) +{ + int32_t t0; + + t0 = sys->ticks; + if(flushcache(d) != 0) + error(Eio); + dprint("ahci: flush in %ld ms\n", sys->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->Lock); + d->mode = i; + iunlock(&d->Lock); +} + +static void +runsmartable(Drive *d, int i) +{ + Proc *up = externup(); + if(waserror()){ + qunlock(&d->portm.ql); + d->smartrs = 0; + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + d->smartrs = smart(&d->portc, i); + d->portm.smart = 0; + qunlock(&d->portm.ql); + 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); + setstate(d, i); +} + +/* + * force this driver to notice a change of medium if the hardware doesn't + * report it. + */ +static void +changemedia(SDunit *u) +{ + Ctlr *c; + Drive *d; + + c = u->dev->ctlr; + d = c->drive[u->subno]; + ilock(&d->Lock); + d->mediachange = 1; + u->sectors = 0; + iunlock(&d->Lock); +} + +static int +iawctl(SDunit *u, Cmdbuf *cmd) +{ + Proc *up = externup(); + char **f; + Ctlr *c; + Drive *d; + uint i; + + c = u->dev->ctlr; + d = c->drive[u->subno]; + f = cmd->f; + + if(strcmp(f[0], "change") == 0) + changemedia(u); + else 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 %#x\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.ql); + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + nop(&d->portc); + qunlock(&d->portm.ql); + 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.ql); + d->smartrs = 0; + nexterror(); + } + if(lockready(d) == -1) + error(Eio); + d->portm.smart = 2 + smartrs(&d->portc); + qunlock(&d->portm.ql); + 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) +{ + uint32_t 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, ctlr->physio); + 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 %#lx isr %#lx pi %#lx %s ver %#lx\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 *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, +}; diff --git a/kern/drivers/dev/sdscsi.c b/kern/drivers/dev/sdscsi.c new file mode 100644 index 0000000..380064c --- /dev/null +++ b/kern/drivers/dev/sdscsi.c @@ -0,0 +1,436 @@ +/* + * This file is part of the UCB release of Plan 9. It is subject to the license + * terms in the LICENSE file found in the top-level directory of this + * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No + * part of the UCB release of Plan 9, including this file, may be copied, + * modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" +#include "../port/error.h" + +#include "../port/sd.h" + +static int +scsitest(SDreq* r) +{ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[1] = r->lun<<5; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + + return r->unit->dev->ifc->rio(r); +} + +int +scsiverify(SDunit* unit) +{ + SDreq *r; + int i, status; + uint8_t *inquiry; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((inquiry = sdmalloc(sizeof(unit->inquiry))) == nil){ + free(r); + return 0; + } + r->unit = unit; + r->lun = 0; /* ??? */ + + memset(unit->inquiry, 0, sizeof(unit->inquiry)); + r->write = 0; + r->cmd[0] = 0x12; + r->cmd[1] = r->lun<<5; + r->cmd[4] = sizeof(unit->inquiry)-1; + r->clen = 6; + r->data = inquiry; + r->dlen = sizeof(unit->inquiry)-1; + r->flags = 0; + + r->status = ~0; + if(unit->dev->ifc->rio(r) != SDok){ + free(r); + return 0; + } + memmove(unit->inquiry, inquiry, r->dlen); + free(inquiry); + + SET(status); + for(i = 0; i < 3; i++){ + while((status = scsitest(r)) == SDbusy) + ; + if(status == SDok || status != SDcheck) + break; + if(!(r->flags & SDvalidsense)) + break; + if((r->sense[2] & 0x0F) != 0x02) + continue; + + /* + * Unit is 'not ready'. + * If it is in the process of becoming ready or needs + * an initialising command, set status so it will be spun-up + * below. + * If there's no medium, that's OK too, but don't + * try to spin it up. + */ + if(r->sense[12] == 0x04){ + if(r->sense[13] == 0x02 || r->sense[13] == 0x01){ + status = SDok; + break; + } + } + if(r->sense[12] == 0x3A) + break; + } + + if(status == SDok){ + /* + * Try to ensure a direct-access device is spinning. + * Don't wait for completion, ignore the result. + */ + if((unit->inquiry[0] & SDinq0periphtype) == SDperdisk){ + memset(r->cmd, 0, sizeof(r->cmd)); + r->write = 0; + r->cmd[0] = 0x1B; + r->cmd[1] = (r->lun<<5)|0x01; + r->cmd[4] = 1; + r->clen = 6; + r->data = nil; + r->dlen = 0; + r->flags = 0; + + r->status = ~0; + unit->dev->ifc->rio(r); + } + } + free(r); + + if(status == SDok || status == SDcheck) + return 1; + return 0; +} + +static int +scsirio(SDreq* r) +{ + Proc *up = externup(); + /* + * Perform an I/O request, returning + * -1 failure + * 0 ok + * 1 no medium present + * 2 retry + * The contents of r may be altered so the + * caller should re-initialise if necesary. + */ + r->status = ~0; + switch(r->unit->dev->ifc->rio(r)){ + default: + break; + case SDcheck: + if(!(r->flags & SDvalidsense)) + break; + switch(r->sense[2] & 0x0F){ + case 0x00: /* no sense */ + case 0x01: /* recovered error */ + return 2; + case 0x06: /* check condition */ + /* + * 0x28 - not ready to ready transition, + * medium may have changed. + * 0x29 - power on or some type of reset. + */ + if(r->sense[12] == 0x28 && r->sense[13] == 0) + return 2; + if(r->sense[12] == 0x29) + return 2; + break; + case 0x02: /* not ready */ + /* + * If no medium present, bail out. + * If unit is becoming ready, rather than not + * not ready, wait a little then poke it again. */ + if(r->sense[12] == 0x3A) + break; + if(r->sense[12] != 0x04 || r->sense[13] != 0x01) + break; + + while(waserror()) + ; + tsleep(&up->sleep, return0, 0, 500); + poperror(); + scsitest(r); + return 2; + default: + break; + } + break; + case SDok: + return 0; + } + return -1; +} + +int +scsionline(SDunit* unit) +{ + SDreq *r; + uint8_t *p; + int ok, retries; + + if((r = malloc(sizeof(SDreq))) == nil) + return 0; + if((p = sdmalloc(8)) == nil){ + free(r); + return 0; + } + + ok = 0; + + r->unit = unit; + r->lun = 0; /* ??? */ + for(retries = 0; retries < 10; retries++){ + /* + * Read-capacity is mandatory for DA, WORM, CD-ROM and + * MO. It may return 'not ready' if type DA is not + * spun up, type MO or type CD-ROM are not loaded or just + * plain slow getting their act together after a reset. + */ + r->write = 0; + memset(r->cmd, 0, sizeof(r->cmd)); + r->cmd[0] = 0x25; + r->cmd[1] = r->lun<<5; + r->clen = 10; + r->data = p; + r->dlen = 8; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + break; + case 0: + unit->sectors = (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; + unit->secsize = (p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]; + + /* + * Some ATAPI CD readers lie about the block size. + * Since we don't read audio via this interface + * it's okay to always fudge this. + */ + if(unit->secsize == 2352) + unit->secsize = 2048; + /* + * Devices with removable media may return 0 sectors + * when they have empty media (e.g. sata dvd writers); + * if so, keep the count zero. + * + * Read-capacity returns the LBA of the last sector, + * therefore the number of sectors must be incremented. + */ + if(unit->sectors != 0) + unit->sectors++; + ok = 1; + break; + case 1: + ok = 1; + break; + case 2: + continue; + } + break; + } + free(p); + free(r); + + if(ok) + return ok+retries; + else + return 0; +} + +int +scsiexec(SDunit* unit, int write, uint8_t* cmd, int clen, void* data, + int* dlen) +{ + SDreq *r; + int status; + + if((r = malloc(sizeof(SDreq))) == nil) + return SDmalloc; + r->unit = unit; + r->lun = cmd[1]>>5; /* ??? */ + r->write = write; + memmove(r->cmd, cmd, clen); + r->clen = clen; + r->data = data; + if(dlen) + r->dlen = *dlen; + r->flags = 0; + + r->status = ~0; + + /* + * Call the device-specific I/O routine. + * There should be no calls to 'error()' below this + * which percolate back up. + */ + switch(status = unit->dev->ifc->rio(r)){ + case SDok: + if(dlen) + *dlen = r->rlen; + /*FALLTHROUGH*/ + case SDcheck: + /*FALLTHROUGH*/ + default: + /* + * It's more complicated than this. There are conditions + * which are 'ok' but for which the returned status code + * is not 'SDok'. + * Also, not all conditions require a reqsense, might + * need to do a reqsense here and make it available to the + * caller somehow. + * + * Mañana. + */ + break; + } + sdfree(r); + + return status; +} + +static void +scsifmt10(SDreq *r, int write, int lun, uint32_t nb, uint64_t bno) +{ + uint8_t *c; + + c = r->cmd; + if(write == 0) + c[0] = 0x28; + else + c[0] = 0x2A; + c[1] = lun<<5; + c[2] = bno>>24; + c[3] = bno>>16; + c[4] = bno>>8; + c[5] = bno; + c[6] = 0; + c[7] = nb>>8; + c[8] = nb; + c[9] = 0; + + r->clen = 10; +} + +static void +scsifmt16(SDreq *r, int write, int lun, uint32_t nb, uint64_t bno) +{ + uint8_t *c; + + c = r->cmd; + if(write == 0) + c[0] = 0x88; + else + c[0] = 0x8A; + c[1] = lun<<5; /* so wrong */ + c[2] = bno>>56; + c[3] = bno>>48; + c[4] = bno>>40; + c[5] = bno>>32; + c[6] = bno>>24; + c[7] = bno>>16; + c[8] = bno>>8; + c[9] = bno; + c[10] = nb>>24; + c[11] = nb>>16; + c[12] = nb>>8; + c[13] = nb; + c[14] = 0; + c[15] = 0; + + r->clen = 16; +} + +int32_t +scsibio(SDunit* unit, int lun, int write, void* data, int32_t nb, + uint64_t bno) +{ + SDreq *r; + int32_t rlen; + + if((r = malloc(sizeof(SDreq))) == nil) + error(Enomem); + r->unit = unit; + r->lun = lun; +again: + r->write = write; + if(bno >= (1ULL<<32)) + scsifmt16(r, write, lun, nb, bno); + else + scsifmt10(r, write, lun, nb, bno); + r->data = data; + r->dlen = nb*unit->secsize; + r->flags = 0; + + r->status = ~0; + switch(scsirio(r)){ + default: + rlen = -1; + break; + case 0: + rlen = r->rlen; + break; + case 2: + rlen = -1; + if(!(r->flags & SDvalidsense)) + break; + switch(r->sense[2] & 0x0F){ + default: + break; + case 0x01: /* recovered error */ + print("%s: recovered error at sector %llu\n", + unit->SDperm.name, bno); + rlen = r->rlen; + break; + case 0x06: /* check condition */ + /* + * Check for a removeable media change. + * If so, mark it by zapping the geometry info + * to force an online request. + */ + if(r->sense[12] != 0x28 || r->sense[13] != 0) + break; + if(unit->inquiry[1] & SDinq1removable) + unit->sectors = 0; + break; + case 0x02: /* not ready */ + /* + * If unit is becoming ready, + * rather than not not ready, try again. + */ + if(r->sense[12] == 0x04 && r->sense[13] == 0x01) + goto again; + break; + } + break; + } + free(r); + + return rlen; +} + diff --git a/kern/include/ahci.h b/kern/include/ahci.h new file mode 100644 index 0000000..5a1566c --- /dev/null +++ b/kern/include/ahci.h @@ -0,0 +1,302 @@ +/* + * This file is part of the UCB release of Plan 9. It is subject to the license + * terms in the LICENSE file found in the top-level directory of this + * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No + * part of the UCB release of Plan 9, including this file, may be copied, + * modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +/* + * advanced host controller interface (sata) + * © 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: supported features */ +enum { + Hs64a = 1<<31, /* 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, /* aggressive 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, /* 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. */ + 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 { + uint32_t cap; + uint32_t ghc; + uint32_t isr; + uint32_t pi; /* ports implemented */ + uint32_t ver; + uint32_t ccc; /* coaleasing control */ + uint32_t cccports; + uint32_t emloc; + uint32_t 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 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 = 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, /* aggressive slumber & partial sleep */ + Aalpe = 1<<26, /* aggressive 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 dev (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 detection */ +}; + +#define sstatus scr0 +#define sctl scr2 +#define serror scr1 +#define sactive scr3 + +typedef struct { + uint32_t list; /* PxCLB must be 1kb aligned. */ + uint32_t listhi; + uint32_t fis; /* 256-byte aligned */ + uint32_t fishi; + uint32_t isr; + uint32_t ie; /* interrupt enable */ + uint32_t cmd; + uint32_t res1; + uint32_t task; + uint32_t sig; + uint32_t scr0; + uint32_t scr2; + uint32_t scr1; + uint32_t scr3; + uint32_t ci; /* command issue */ + uint32_t ntf; + unsigned char res2[8]; + uint32_t vendor; +} Aport; + +enum { + /* + * Aport sstatus bits (actually states): + * 11-8 interface power management + * 7-4 current interface speed (generation #) + * 3-0 device detection + */ + Intslumber = 0x600, + Intpartpwr = 0x200, + Intactive = 0x100, + Intpm = 0xf00, + + Devphyoffline = 4, + Devphycomm = 2, /* phy communication established */ + Devpresent = 1, + Devdet = Devpresent | Devphycomm | Devphyoffline, +}; + +/* in host's memory; not memory mapped */ +typedef struct { + unsigned char *base; + unsigned char *d; + unsigned char *p; + unsigned char *r; + unsigned char *u; + uint32_t *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 { + uint32_t flags; + uint32_t len; + uint32_t ctab; + uint32_t ctabhi; + unsigned char reserved[16]; +} Alist; + +typedef struct { + uint32_t dba; + uint32_t dbahi; + uint32_t pad; + uint32_t count; +} Aprdt; + +typedef struct { + unsigned char cfis[0x40]; + unsigned char atapi[0x10]; + unsigned char 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 ql; + Rendez Rendez; + unsigned char flag; + unsigned char feat; + unsigned char smart; + Afis fis; + Alist *list; + Actab *ctab; +} Aportm; + +typedef struct { + Aport *p; + Aportm *pm; +} Aportc; diff --git a/kern/include/sd.h b/kern/include/sd.h new file mode 100644 index 0000000..1afa3e3 --- /dev/null +++ b/kern/include/sd.h @@ -0,0 +1,203 @@ +/* + * This file is part of the UCB release of Plan 9. It is subject to the license + * terms in the LICENSE file found in the top-level directory of this + * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No + * part of the UCB release of Plan 9, including this file, may be copied, + * modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +/* + * Storage Device. + */ +typedef struct SDev SDev; +typedef struct SDifc SDifc; +typedef struct SDio SDio; +typedef struct SDpart SDpart; +typedef struct SDperm SDperm; +typedef struct SDreq SDreq; +typedef struct SDunit SDunit; + +struct SDperm { + char* name; + char* user; + uint32_t perm; +}; + +struct SDpart { + uint64_t start; + uint64_t end; + SDperm SDperm; + int valid; + uint32_t vers; +}; + +struct SDunit { + SDev* dev; + int subno; + unsigned char inquiry[255]; /* format follows SCSI spec */ + unsigned char sense[18]; /* format follows SCSI spec */ + SDperm SDperm; + + QLock ctl; + uint64_t sectors; + uint32_t secsize; + SDpart* part; /* nil or array of size npart */ + int npart; + uint32_t vers; + SDperm ctlperm; + + QLock raw; /* raw read or write in progress */ + uint32_t rawinuse; /* really just a test-and-set */ + int state; + SDreq* req; + SDperm rawperm; +}; + +/* + * Each controller is represented by a SDev. + */ +struct SDev { + Ref r; /* Number of callers using device */ + SDifc* ifc; /* pnp/legacy */ + void* ctlr; + int idno; + char name[8]; + SDev* next; + + QLock ql; /* enable/disable */ + int enabled; + int nunit; /* Number of units */ + QLock unitlock; /* `Loading' of units */ + int* unitflg; /* Unit flags */ + SDunit**unit; +}; + +struct SDifc { + char* name; + + SDev* (*pnp)(void); + SDev* (*legacy)(int, int); + int (*enable)(SDev*); + int (*disable)(SDev*); + + int (*verify)(SDunit*); + int (*online)(SDunit*); + int (*rio)(SDreq*); + int (*rctl)(SDunit*, char*, int); + int (*wctl)(SDunit*, Cmdbuf*); + + int32_t (*bio)(SDunit*, int, int, void*, int32_t, uint64_t); + SDev* (*probe)(DevConf*); + void (*clear)(SDev*); + char* (*rtopctl)(SDev*, char*, char*); + int (*wtopctl)(SDev*, Cmdbuf*); +}; + +struct SDreq { + SDunit* unit; + int lun; + int write; + unsigned char cmd[16]; + int clen; + void* data; + int dlen; + + int flags; + + int status; + int32_t rlen; + unsigned char sense[256]; +}; + +enum { + SDnosense = 0x00000001, + SDvalidsense = 0x00010000, + + SDinq0periphqual= 0xe0, + SDinq0periphtype= 0x1f, + SDinq1removable = 0x80, + + /* periphtype values */ + SDperdisk = 0, /* Direct access (disk) */ + SDpertape = 1, /* Sequential eg, tape */ + SDperpr = 2, /* Printer */ + SDperworm = 4, /* Worm */ + SDpercd = 5, /* CD-ROM */ + SDpermo = 7, /* rewriteable MO */ + SDperjuke = 8, /* medium-changer */ +}; + +enum { + SDretry = -5, /* internal to controllers */ + SDmalloc = -4, + SDeio = -3, + SDtimeout = -2, + SDnostatus = -1, + + SDok = 0, + + SDcheck = 0x02, /* check condition */ + SDbusy = 0x08, /* busy */ + + SDmaxio = 2048*1024, + SDnpart = 16, +}; + +/* + * Allow the default #defines for sdmalloc & sdfree to be overridden by + * system-specific versions. This can be used to avoid extra copying + * by making sure sd buffers are cache-aligned (some ARM systems) or + * page-aligned (xen) for DMA. + */ +#ifndef sdmalloc +#define sdmalloc(n) malloc(n) +#define sdfree(p) free(p) +#endif + +/* + * mmc/sd/sdio host controller interface + */ + +struct SDio { + char *name; + int (*init)(void); + void (*enable)(void); + int (*inquiry)(char*, int); + int (*cmd)(uint32_t, uint32_t, uint32_t*); + void (*iosetup)(int, void*, int, int); + void (*io)(int, unsigned char*, int); +}; + +extern SDio sdio; + +/* devsd.c */ +extern void sdadddevs(SDev*); +extern void sdaddconf(SDunit*); +extern void sdaddallconfs(void (*f)(SDunit*)); +extern void sdaddpart(SDunit*, char*, uint64_t, uint64_t); +extern int sdsetsense(SDreq*, int, int, int, int); +extern int sdmodesense(SDreq*, unsigned char*, void*, int); +extern int sdfakescsi(SDreq*, void*, int); + +/* sdscsi.c */ +extern int scsiverify(SDunit*); +extern int scsionline(SDunit*); +extern int32_t scsibio(SDunit*, int, int, void*, int32_t, uint64_t); +extern SDev* scsiid(SDev*, SDifc*); + +/* + * hardware info about a device + */ +typedef struct { + uint32_t port; + int size; +} Devport; + +struct DevConf +{ + uint32_t intnum; /* interrupt number */ + char *type; /* card type, malloced */ + int nports; /* Number of ports */ + Devport *ports; /* The ports themselves */ +}; -- 2.8.0.rc3.226.g39d4020 -- You received this message because you are subscribed to the Google Groups "Akaros" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. For more options, visit https://groups.google.com/d/optout.
