AHCI initial commit.
authorRonald G. Minnich <rminnich@gmail.com>
Tue, 9 Aug 2016 22:24:31 +0000 (15:24 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 10 Aug 2016 19:36:05 +0000 (15:36 -0400)
From github.com:harvey-os/harvey 5be5d9cc0a1da28339c61e974c31b1915d2a1b03

Change-Id: I5cd052ce7bde5f6eb6de9557dfe6151174d1ef5f
Signed-off-by: Ronald G. Minnich <rminnich@gmail.com>
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/drivers/dev/sd.c [new file with mode: 0644]
kern/drivers/dev/sdiahci.c [new file with mode: 0644]
kern/drivers/dev/sdscsi.c [new file with mode: 0644]
kern/include/ahci.h [new file with mode: 0644]
kern/include/sd.h [new file with mode: 0644]

diff --git a/kern/drivers/dev/sd.c b/kern/drivers/dev/sd.c
new file mode 100644 (file)
index 0000000..5ca4f5c
--- /dev/null
@@ -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 (file)
index 0000000..13665da
--- /dev/null
@@ -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 (file)
index 0000000..380064c
--- /dev/null
@@ -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 (file)
index 0000000..5a1566c
--- /dev/null
@@ -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 (file)
index 0000000..1afa3e3
--- /dev/null
@@ -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 */
+};