Merged to master at 185e3d72f127..5e3ed80f093b (from, to] You can see the entire diff with 'git diff' or at https://github.com/brho/akaros/compare/185e3d72f127...5e3ed80f093b
On 2016-08-09 at 15:24 "Ronald G. Minnich" <[email protected]> wrote: > From github.com:harvey-os/harvey > 5be5d9cc0a1da28339c61e974c31b1915d2a1b03 > > 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 */ +}; -- 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.
