9vx/OSX: add devsd, add device size code to devfs-posix - vx32 - Local 9vx git repository for patches.
Log
Files
Refs
---
commit 7414e9996dc38431669b1da77013891e3f8940d6
parent b73f901985b9aaada2c11d977276a2d064555048
Author: Russ Cox 
Date:   Sat, 28 Jun 2008 11:40:18 -0400

9vx/OSX: add devsd, add device size code to devfs-posix

Diffstat:
  src/9vx/Makefrag                    |       3 +++
  src/9vx/a/devsd.c                   |    1645 +++++++++++++++++++++++++++++++
  src/9vx/a/sd.h                      |     137 +++++++++++++++++++++++++++++++
  src/9vx/a/sdscsi.c                  |     424 ++++++++++++++++++++++++++++++
  src/9vx/devfs-posix.c               |      62 +++++++++++++++++++++++++++++++
  src/9vx/devtab.c                    |       2 ++
  src/9vx/sdloop.c                    |     274 +++++++++++++++++++++++++++++++

7 files changed, 2547 insertions(+), 0 deletions(-)
---
diff --git a/src/9vx/Makefrag b/src/9vx/Makefrag
@@ -45,6 +45,7 @@ PLAN9_OBJS = \
                 main.o \
                 mmu.o \
                 sched.o \
+                sdloop.o \
                 stub.o \
                 term.o \
                 time.o \
@@ -74,6 +75,7 @@ PLAN9_A_OBJS = \
                 devproc.o \
                 devpipe.o \
                 devroot.o \
+                devsd.o \
                 devsrv.o \
                 devssl.o \
                 devtls.o \
@@ -93,6 +95,7 @@ PLAN9_A_OBJS = \
                 proc.o \
                 qio.o \
                 qlock.o \
+                sdscsi.o \
                 segment.o \
                 strecpy.o \
                 sysfile.o \
diff --git a/src/9vx/a/devsd.c b/src/9vx/a/devsd.c
@@ -0,0 +1,1645 @@
+/*
+ * Storage Device.
+ */
+#include "u.h"
+#include "lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "error.h"
+
+#include "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<>TypeSHIFT) & TypeMASK)
+#define PART(q)                ((((ulong)(q).path)>>PartSHIFT) & PartMASK)
+#define UNIT(q)                ((((ulong)(q).path)>>UnitSHIFT) & UnitMASK)
+#define DEV(q)                ((((ulong)(q).path)>>DevSHIFT) & DevMASK)
+#define QID(d,u, p, t)        (((d)<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->perm.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->perm.name, name);
+        kstrdup(&pp->perm.user, eve);
+        pp->perm.perm = 0640;
+        pp->valid = 1;
+}
+
+static void
+sddelpart(SDunit* unit, char* name)
+{
+        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->perm.name) == 0)
+                        break;
+                pp++;
+        }
+        if(i >= unit->npart)
+                error(Ebadctl);
+        if(strcmp(up->user, pp->perm.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)
+{
+        int nf;
+        uvlong start, end;
+        char *f[4], *p, *q, buf[10];
+
+        if(unit->sectors > 0){
+                unit->sectors = unit->secsize = 0;
+                sdincvers(unit);
+        }
+
+        if(unit->inquiry[0] & 0xC0)
+                return 0;
+        switch(unit->inquiry[0] & 0x1F){
+        case 0x00:                        /* DA */
+        case 0x04:                        /* WORM */
+        case 0x05:                        /* CD-ROM */
+        case 0x07:                        /* MO */
+                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);
+#if 0
+                /*
+                 * 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.
+                 */
+                snprint(buf, sizeof buf, "%spart", unit->perm.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->perm.name, buf);
+                kstrdup(&unit->perm.user, eve);
+                unit->perm.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; iidno = 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);
+// }
+
+static int
+sd2gen(Chan* c, int i, Dir* dp)
+{
+        Qid q;
+        uvlong 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 = 0640;
+                }
+                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->perm.user))
+                        kstrdup(&pp->perm.user, eve);
+                devdir(c, q, pp->perm.name, l, pp->perm.user, pp->perm.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, 0640, dp);
+                return 1;
+        }
+        return -1;
+}
+
+static int
+sdgen(Chan* c, char *name, Dirtab *dt, int j, int s, Dir* dp)
+{
+        Qid q;
+        uvlong 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; inunit)
+                                        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->perm.user))
+                        kstrdup(&unit->perm.user, eve);
+                devdir(c, q, unit->perm.name, 0, unit->perm.user, unit->perm.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) * unit->secsize;
+                mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), i, Qpart),
+                        unit->vers+pp->vers, QTFILE);
+                if(emptystr(pp->perm.user))
+                        kstrdup(&pp->perm.user, eve);
+                devdir(c, q, pp->perm.name, l, pp->perm.user, pp->perm.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->dev = (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 int
+sdstat(Chan* c, uchar* db, int n)
+{
+        return devstat(c, db, n, nil, 0, sdgen);
+}
+
+static Chan*
+sdopen(Chan* c, int omode)
+{
+        SDpart *pp;
+        SDunit *unit;
+        SDev *sdev;
+        uchar 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 long
+sdbio(Chan* c, int write, char* a, long len, uvlong off)
+{
+        int nchange;
+        long l;
+        uchar *b;
+        SDpart *pp;
+        SDunit *unit;
+        SDev *sdev;
+        ulong max, nb, offset;
+        uvlong bno;
+
+        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] & 0x80)){
+                qunlock(&unit->ctl);
+                poperror();
+        }
+
+        b = sdmalloc(nb*unit->secsize);
+        if(b == nil)
+                error(Enomem);
+        if(waserror()){
+                sdfree(b);
+                if(!(unit->inquiry[1] & 0x80))
+                        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] & 0x80){
+                qunlock(&unit->ctl);
+                poperror();
+        }
+
+        decref(&sdev->r);
+        return len;
+}
+
+static long
+sdrio(SDreq* r, void* a, long n)
+{
+        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, uchar *cmd, void *info, int ilen)
+{
+        int len;
+        uchar *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)
+{
+        uchar *cmd, *p;
+        uvlong 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 - (uchar*)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 - (uchar*)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 long
+sdread(Chan *c, void *a, long n, vlong off)
+{
+        char *p, *e, *buf;
+        SDpart *pp;
+        SDunit *unit;
+        SDev *sdev;
+        ulong offset;
+        int i, l, m, status;
+
+        offset = off;
+        switch(TYPE(c->qid)){
+        default:
+                error(Eperm);
+        case Qtopctl:
+                m = 64*1024;        /* room for register dumps */
+                p = buf = malloc(m);
+                assert(p);
+                e = p + m;
+                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(off, 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)];
+                m = 16*1024;        /* room for register dumps */
+                p = malloc(m);
+                l = snprint(p, m, "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, m-l);
+                if(unit->sectors == 0)
+                        sdinitpart(unit);
+                if(unit->sectors){
+                        if(unit->dev->ifc->rctl == nil)
+                                l += snprint(p+l, m-l,
+                                        "geometry %llud %lud\n",
+                                        unit->sectors, unit->secsize);
+                        pp = unit->part;
+                        for(i = 0; i < unit->npart; i++){
+                                if(pp->valid)
+                                        l += snprint(p+l, m-l,
+                                                "part %s %llud %llud\n",
+                                                pp->perm.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 long
+sdwrite(Chan* c, void* a, long n, vlong off)
+{
+        char *f0;
+        int i;
+        uvlong 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]->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 int
+sdwstat(Chan* c, uchar* dp, int n)
+{
+        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->perm;
+                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 != ~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->perm.name);
+                        free(unit->perm.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 = {
+        'S',
+        "sd",
+
+        sdreset,
+        devinit,
+        devshutdown,
+        sdattach,
+        sdwalk,
+        sdstat,
+        sdopen,
+        devcreate,
+        sdclose,
+        sdread,
+        devbread,
+        sdwrite,
+        devbwrite,
+        devremove,
+        sdwstat,
+        devpower,
+        sdconfig,
+};
+
+/*
+ * This is wrong for so many reasons.  This code must go.
+ */
+ttypedef 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(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 = (ulong)-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 != (ulong)-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; inf; i+=2){
+                if(i+2 > cb->nf)
+                        error(Ebadarg);
+                opt = cb->f[i];
+                for(j=0; jf[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/src/9vx/a/sd.h b/src/9vx/a/sd.h
@@ -0,0 +1,137 @@
+/*
+ * Storage Device.
+ */
+ttypedef struct SDev SDev;
+ttypedef struct SDifc SDifc;
+ttypedef struct SDpart SDpart;
+ttypedef struct SDperm SDperm;
+ttypedef struct SDreq SDreq;
+ttypedef struct SDunit SDunit;
+
+struct SDperm {
+        char*        name;
+        char*        user;
+        ulong        perm;
+};
+
+struct SDpart {
+        uvlong        start;
+        uvlong        end;
+        SDperm        perm;
+        int        valid;
+        ulong        vers;
+};
+
+struct SDunit {
+        SDev*        dev;
+        int        subno;
+        uchar        inquiry[255];                /* format follows SCSI spec */
+        uchar        sense[18];                /* format follows SCSI spec */
+        SDperm        perm;
+
+        QLock        ctl;
+        uvlong        sectors;
+        ulong        secsize;
+        SDpart*        part;                        /* nil or array of size npart */
+        int        npart;
+        ulong        vers;
+        SDperm        ctlperm;
+
+        QLock        raw;                        /* raw read or write in progress */
+        ulong        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        lk;                        /* 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*);
+
+        long        (*bio)(SDunit*, int, int, void*, long, uvlong);
+        SDev*        (*probe)(DevConf*);
+        void        (*clear)(SDev*);
+        char*        (*rtopctl)(SDev*, char*, char*);
+        int        (*wtopctl)(SDev*, Cmdbuf*);
+};
+
+struct SDreq {
+        SDunit*        unit;
+        int        lun;
+        int        write;
+        uchar        cmd[16];
+        int        clen;
+        void*        data;
+        int        dlen;
+
+        int        flags;
+
+        int        status;
+        long        rlen;
+        uchar        sense[256];
+};
+
+enum {
+        SDnosense        = 0x00000001,
+        SDvalidsense        = 0x00010000,
+};
+
+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,
+};
+
+#define sdmalloc(n)        malloc(n)
+#define sdfree(p)        free(p)
+
+/* devsd.c */
+extern void sdadddevs(SDev*);
+extern int sdsetsense(SDreq*, int, int, int, int);
+extern int sdmodesense(SDreq*, uchar*, void*, int);
+extern int sdfakescsi(SDreq*, void*, int);
+
+/* sdscsi.c */
+extern int scsiverify(SDunit*);
+extern int scsionline(SDunit*);
+extern long scsibio(SDunit*, int, int, void*, long, uvlong);
+extern SDev* scsiid(SDev*, SDifc*);
diff --git a/src/9vx/a/sdscsi.c b/src/9vx/a/sdscsi.c
@@ -0,0 +1,424 @@
+#include "u.h"
+#include "lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "error.h"
+
+#include "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;
+        uchar *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);
+
+        status = 0;
+        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] & 0x1F) == 0){
+                        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)
+{
+        /*
+         * 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;
+        uchar *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, uchar* 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, ulong nb, uvlong bno)
+{
+        uchar *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, ulong nb, uvlong bno)
+{
+        uchar *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;
+}
+
+long
+scsibio(SDunit* unit, int lun, int write, void* data, long nb, uvlong bno)
+{
+        SDreq *r;
+        long 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 %llud\n",
+                                unit->perm.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] & 0x80)
+                                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/src/9vx/devfs-posix.c b/src/9vx/devfs-posix.c
@@ -4,6 +4,16 @@
 #include                /* going to regret this - getgrgid is a stack smasher */
 #include        
 #include        
+#if defined(__FreeBSD__)
+#include 
+#include 
+#include 
+#endif
+#if defined(__linux__)
+#include 
+#include 
+#include 
+#endif
 #include        "lib.h"
 #include        "mem.h"
 #include        "dat.h"
@@ -24,6 +34,8 @@ static char *gidtoname(int);
 static int nametouid(char*);
 static int nametogid(char*);
 
+static vlong disksize(int, struct stat*);
+
 ttypedef struct UnixFd UnixFd;
 struct UnixFd
 {
@@ -247,6 +259,7 @@ fswalk(Chan *c, Chan *nc, char **name, int nname)
 static int
 fsdirstat(char *path, int dev, Dir *d)
 {
+        int fd;
         struct stat st;
         
         if(stat(path, &st) < 0 && lstat(path, &st) < 0)
@@ -261,6 +274,10 @@ fsdirstat(char *path, int dev, Dir *d)
         d->atime = st.st_atime;
         d->mtime = st.st_mtime;
         d->length = st.st_size;
+        if(S_ISBLK(st.st_mode) && (fd = open(path, O_RDONLY)) >= 0){
+                d->length = disksize(fd, &st);
+                close(fd);
+        }
         d->type = FsChar;
         d->dev = dev;
         return 0;
@@ -844,3 +861,48 @@ nametogid(char *name)
                 return -1;
         return u->id;
 }
+
+#if defined(__linux__)
+
+static vlong
+disksize(int fd, struct stat *st)
+{
+        uvlong u64;
+        long l;
+        struct hd_geometry geo;
+        
+        memset(&geo, 0, sizeof geo);
+        l = 0;
+        u64 = 0;
+#ifdef BLKGETSIZE64
+        if(ioctl(fd, BLKGETSIZE64, &u64) >= 0)
+                return u64;
+#endif
+        if(ioctl(fd, BLKGETSIZE, &l) >= 0)
+                return l*512;
+        if(ioctl(fd, HDIO_GETGEO, &geo) >= 0)
+                return (vlong)geo.heads*geo.sectors*geo.cylinders*512;
+        return 0;
+}
+
+#elif defined(__FreeBSD__) && defined(DIOCGMEDIASIZE)
+
+static vlong
+disksize(int fd, struct stat *st)
+{
+        off_t mediasize;
+        
+        if(ioctl(fd, DIOCGMEDIASIZE, &mediasize) >= 0)
+                return mediasize;
+        return 0;
+}
+
+#else
+
+static vlong
+disksize(int fd, struct stat *st)
+{
+        return 0;
+}
+
+#endif
diff --git a/src/9vx/devtab.c b/src/9vx/devtab.c
@@ -21,6 +21,7 @@ extern Dev srvdevtab;
 extern Dev procdevtab;
 extern Dev mntloopdevtab;
 extern Dev dupdevtab;
+extern Dev sddevtab;
 
 Dev *devtab[] = {
         &rootdevtab,        /* must be first */
@@ -39,6 +40,7 @@ Dev *devtab[] = {
         &srvdevtab,
         &ssldevtab,
         &tlsdevtab,
+        &sddevtab,
         0
 };
 
diff --git a/src/9vx/sdloop.c b/src/9vx/sdloop.c
@@ -0,0 +1,274 @@
+#include "u.h"
+#include "lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "error.h"
+
+#include "sd.h"
+
+void        loopdev(char*, int);
+
+ttypedef struct Ctlr Ctlr;
+struct Ctlr{
+        Ctlr        *next;
+        Ctlr        *prev;
+        
+        QLock        lk;
+        SDev        *sdev;
+
+        Chan        *c;
+        int                mode;
+        uvlong        qidpath;
+};
+
+static        Lock        ctlrlock;
+static        Ctlr        *ctlrhead;
+static        Ctlr        *ctlrtail;
+
+SDifc sdloopifc;
+
+static SDev*
+looppnp(void)
+{
+        return nil;
+}
+
+/*
+ * Cannot error.
+ * Check that unit is available.
+ * Return 1 if so, 0 if not.
+ */
+static int
+loopverify(SDunit *u)
+{        
+        return 1;
+}
+
+/*
+ * Cannot error.
+ * Check that unit is online.
+ * If media changed, return 2.
+ * If ready, return 1.
+ * If not ready, return 0.
+ */
+static int
+looponline(SDunit *unit)
+{
+        uchar buf[sizeof(Dir)+100];
+        Chan *c;
+        SDev *sdev;
+        Ctlr *ctlr;
+        Dir dir;
+        long n;
+        
+        if(waserror())
+                return 0;
+
+        sdev = unit->dev;
+        ctlr = sdev->ctlr;
+        c = ctlr->c;
+        n = devtab[c->type]->stat(c, buf, sizeof buf);
+        if(convM2D(buf, n, &dir, nil) == 0)
+                error("internal error: stat error in looponline");
+        if(ctlr->qidpath != dir.qid.path){
+                unit->sectors = dir.length/512;
+                unit->secsize = 512;
+                ctlr->qidpath = dir.qid.path;
+                poperror();
+                return 2;
+        }
+        poperror();
+        return 1;
+}
+
+static int
+looprio(SDreq *r)
+{
+        SDev *sdev;
+        SDunit *unit;
+        Ctlr *ctlr;
+        uchar *cmd;
+        uvlong lba;
+        long count, n;
+        Chan *c;
+        int status;
+
+        unit = r->unit;
+        sdev = unit->dev;
+        ctlr = sdev->ctlr;
+        cmd = r->cmd;
+
+#if 0        
+        if((status = sdfakescsi(r, ctlr->info, sizeof ctlr->info)) != SDnostatus){
+                /* XXX check for SDcheck here */
+                r->status = status;
+                return status;
+        }
+#endif
+
+        switch(cmd[0]){
+        case 0x28:        /* read */
+        case 0x2A:        /* write */
+                break;
+        default:
+                print("%s: bad cmd 0x%.2ux\n", unit->perm.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*512)
+                count = r->dlen/512;
+
+        c = ctlr->c;
+        if(cmd[0] == 0x28)
+                n = devtab[c->type]->read(c, r->data, count*512, lba*512);
+        else
+                n = devtab[c->type]->write(c, r->data, count*512, lba*512);
+        r->rlen = n;
+        return SDok;
+}
+
+static int
+looprctl(SDunit *unit, char *p, int l)
+{
+        Ctlr *ctlr;
+        char *e, *op;
+        
+        ctlr = unit->dev->ctlr;
+        e = p+l;
+        op = p;
+        
+        p = seprint(p, e, "loop %s %s\n", ctlr->mode == ORDWR ? "rw" : "ro", chanpath(ctlr->c));
+        return p - op;
+}
+
+static int
+loopwctl(SDunit *u, Cmdbuf *cmd)
+{
+        cmderror(cmd, Ebadarg);
+        return 0;
+}
+
+static void
+loopclear1(Ctlr *ctlr)
+{
+        lock(&ctlrlock);
+        if(ctlr->prev)
+                ctlr->prev->next = ctlr->next;
+        else
+                ctlrhead = ctlr;
+        if(ctlr->next)
+                ctlr->next->prev = ctlr->prev;
+        else
+                ctlrtail = ctlr->prev;
+        unlock(&ctlrlock);
+        
+        cclose(ctlr->c);
+        free(ctlr);
+}
+
+static void
+loopclear(SDev *sdev)
+{
+        loopclear1(sdev->ctlr);
+}
+
+static int
+loopwtopctl(SDev *sdev, Cmdbuf *cb)
+{
+        int mode;
+
+        mode = 0;
+        if(cb->nf != 2)
+                cmderror(cb, Ebadarg);
+        if(strcmp(cb->f[0], "rw") == 0)
+                mode = ORDWR;
+        else if(strcmp(cb->f[0], "ro") == 0)
+                mode = OREAD;
+        else
+                cmderror(cb, Ebadarg);
+        
+        loopdev(cb->f[1], mode);
+        return 0;
+}
+
+void
+loopdev(char *name, int mode)
+{
+        Chan *c;
+        Ctlr *volatile ctlr;
+        SDev *volatile sdev;
+
+        c = namec(name, Aopen, mode, 0);
+        ctlr = nil;
+        sdev = nil;
+        if(waserror()){
+                cclose(c);
+                if(ctlr)
+                        free(ctlr);
+                if(sdev)
+                        free(sdev);
+                nexterror();
+        }
+
+        ctlr = smalloc(sizeof *ctlr);
+        sdev = smalloc(sizeof *sdev);
+        sdev->ifc = &sdloopifc;
+        sdev->ctlr = ctlr;
+        sdev->nunit = 1;
+        sdev->idno = '0';
+        ctlr->sdev = sdev;
+        ctlr->c = c;
+        ctlr->mode = mode;
+        poperror();
+
+        lock(&ctlrlock);
+        ctlr->next = nil;
+        ctlr->prev = ctlrtail;
+        ctlrtail = ctlr;
+        if(ctlr->prev)
+                ctlr->prev->next = ctlr;
+        else
+                ctlrhead = ctlr;
+        unlock(&ctlrlock);
+        
+        sdadddevs(sdev);
+}
+
+
+SDifc sdloopifc = {
+        "loop",
+
+        looppnp,
+        nil,                /* legacy */
+        nil,                /* enable */
+        nil,                /* disable */
+
+        loopverify,
+        looponline,
+        looprio,
+        looprctl,
+        loopwctl,
+
+        scsibio,
+        nil,        /* probe */
+        loopclear,        /* clear */
+        nil,
+        loopwtopctl,
+};
+
+SDifc *sdifc[] = 
+{
+        &sdloopifc,
+        nil
+};
+
+
+