|  | /* | 
|  | * 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 <assert.h> | 
|  | #include <cpio.h> | 
|  | #include <error.h> | 
|  | #include <kmalloc.h> | 
|  | #include <kref.h> | 
|  | #include <net/ip.h> | 
|  | #include <pmap.h> | 
|  | #include <slab.h> | 
|  | #include <smp.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <sd.h> | 
|  |  | 
|  | extern struct dev sddevtab; | 
|  | struct sdifc sdiahciifc; | 
|  |  | 
|  | /* In Plan 9, this array is auto-generated. That's almost certainly not | 
|  | * necessary; | 
|  | * we can use linker sets at some point, as we do elsewhere in Akaros. */ | 
|  | struct sdifc *sdifc[] = { | 
|  | &sdiahciifc, | 
|  | NULL, | 
|  | }; | 
|  |  | 
|  | static const char Echange[] = "media or partition has changed"; | 
|  |  | 
|  | static const char devletters[] = | 
|  | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; | 
|  |  | 
|  | static struct sdev *devs[sizeof(devletters) - 1]; | 
|  |  | 
|  | static qlock_t devslock = QLOCK_INITIALIZER(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(struct sdunit *unit, char *name, uint64_t start, uint64_t end) | 
|  | { | 
|  | struct sdpart *pp; | 
|  | int i, partno; | 
|  |  | 
|  | /* | 
|  | * Check name not already used | 
|  | * and look for a free slot. | 
|  | */ | 
|  | if (unit->part != NULL) { | 
|  | 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(EINVAL, "%s: '%s' is not valid", __func__, | 
|  | name); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | unit->part = kzmalloc(sizeof(struct sdpart) * SDnpart, 0); | 
|  | if (unit->part == NULL) | 
|  | error(ENOMEM, "%s: can't allocate %d bytes", __func__, | 
|  | sizeof(struct sdpart) * SDnpart); | 
|  | unit->npart = SDnpart; | 
|  | partno = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If no free slot found then increase the | 
|  | * array size (can't get here with unit->part == NULL). | 
|  | */ | 
|  | if (partno == -1) { | 
|  | if (unit->npart >= NPart) | 
|  | error(ENOMEM, "%s: no memory", __func__); | 
|  | pp = kzmalloc(sizeof(struct sdpart) * (unit->npart + SDnpart), | 
|  | 0); | 
|  | if (pp == NULL) | 
|  | error(ENOMEM, | 
|  | "%s: Can't allocate space for %d partitions", | 
|  | __func__, | 
|  | unit->npart + SDnpart); | 
|  | memmove(pp, unit->part, sizeof(struct sdpart) * unit->npart); | 
|  | kfree(unit->part); | 
|  | unit->part = pp; | 
|  | partno = unit->npart; | 
|  | unit->npart += SDnpart; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check size and extent are valid. | 
|  | */ | 
|  | if (start > end) | 
|  | error(EINVAL, "%s: start %d > end %d", __func__, start, end); | 
|  | if (end > unit->sectors) | 
|  | error(EINVAL, "%s: end %d > number of sectors %d", __func__, | 
|  | end, unit->sectors); | 
|  | pp = &unit->part[partno]; | 
|  | pp->start = start; | 
|  | pp->end = end; | 
|  | kstrdup(&pp->sdperm.name, name); | 
|  | kstrdup(&pp->sdperm.user, eve.name); | 
|  | pp->sdperm.perm = 0640; | 
|  | pp->valid = 1; | 
|  | } | 
|  |  | 
|  | static void sddelpart(struct sdunit *unit, char *name) | 
|  | { | 
|  | int i; | 
|  | struct 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(EINVAL, "%s: %d > npart %d", __func__, i, unit->npart); | 
|  |  | 
|  | /* TODO: Implement permission checking and raise errors as appropriate. | 
|  | */ | 
|  | // if (strcmp(current->user.name, pp->SDperm.user) && !iseve()) | 
|  | // error(Eperm); | 
|  |  | 
|  | pp->valid = 0; | 
|  | pp->vers++; | 
|  | } | 
|  |  | 
|  | static void sdincvers(struct 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(struct 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 | 
|  | snprintf(buf, sizeof(buf), "%spart", unit->sdperm.name); | 
|  | for (p = getconf(buf); p != NULL; p = q) { | 
|  | q = strchr(p, '/'); | 
|  | if (q) | 
|  | *q++ = '\0'; | 
|  | nf = tokenize(p, f, ARRAY_SIZE(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 == NULL) | 
|  | return -1; | 
|  | return p - devletters; | 
|  | } | 
|  |  | 
|  | static struct sdev *sdgetdev(int idno) | 
|  | { | 
|  | struct sdev *sdev; | 
|  | int i; | 
|  |  | 
|  | if ((i = sdindex(idno)) < 0) | 
|  | return NULL; | 
|  |  | 
|  | qlock(&devslock); | 
|  | sdev = devs[i]; | 
|  | if (sdev) | 
|  | kref_get(&sdev->r, 1); | 
|  | qunlock(&devslock); | 
|  | return sdev; | 
|  | } | 
|  |  | 
|  | static struct sdunit *sdgetunit(struct sdev *sdev, int subno) | 
|  | { | 
|  | struct 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 NULL; | 
|  | } | 
|  |  | 
|  | unit = sdev->unit[subno]; | 
|  | if (unit == NULL) { | 
|  | /* | 
|  | * Probe the unit only once. This decision | 
|  | * may be a little severe and reviewed later. | 
|  | */ | 
|  | if (sdev->unitflg[subno]) { | 
|  | qunlock(&sdev->unitlock); | 
|  | return NULL; | 
|  | } | 
|  | unit = kzmalloc(sizeof(struct sdunit), 0); | 
|  | if (unit == NULL) { | 
|  | qunlock(&sdev->unitlock); | 
|  | return NULL; | 
|  | } | 
|  | sdev->unitflg[subno] = 1; | 
|  |  | 
|  | snprintf(buf, sizeof(buf), "%s%d", sdev->name, subno); | 
|  | kstrdup(&unit->sdperm.name, buf); | 
|  | kstrdup(&unit->sdperm.user, eve.name); | 
|  | unit->sdperm.perm = 0555; | 
|  | unit->subno = subno; | 
|  | unit->dev = sdev; | 
|  | qlock_init(&unit->ctl); | 
|  |  | 
|  | 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); | 
|  | kfree(unit); | 
|  | return NULL; | 
|  | } | 
|  | sdev->unit[subno] = unit; | 
|  | } | 
|  | qunlock(&sdev->unitlock); | 
|  | return unit; | 
|  | } | 
|  |  | 
|  | static void sdreset(void) | 
|  | { | 
|  | int i; | 
|  | struct sdev *sdev; | 
|  |  | 
|  | /* | 
|  | * Probe all known controller types and register any devices found. | 
|  | */ | 
|  | for (i = 0; sdifc[i] != NULL; i++) { | 
|  | if (sdifc[i]->pnp == NULL) | 
|  | continue; | 
|  | sdev = sdifc[i]->pnp(); | 
|  | if (sdev == NULL) | 
|  | continue; | 
|  | sdadddevs(sdev); | 
|  | } | 
|  | } | 
|  |  | 
|  | void sdadddevs(struct sdev *sdev) | 
|  | { | 
|  | int i, j, id; | 
|  | struct sdev *next; | 
|  |  | 
|  | for (; sdev; sdev = next) { | 
|  | next = sdev->next; | 
|  |  | 
|  | sdev->unit = (struct sdunit **)kzmalloc( | 
|  | sdev->nunit * sizeof(struct sdunit *), 0); | 
|  | sdev->unitflg = (int *)kzmalloc(sdev->nunit * sizeof(int), 0); | 
|  | if (sdev->unit == NULL || sdev->unitflg == NULL) { | 
|  | printd("sdadddevs: out of memory\n"); | 
|  | giveup: | 
|  | kfree(sdev->unit); | 
|  | kfree(sdev->unitflg); | 
|  | if (sdev->ifc->clear) | 
|  | sdev->ifc->clear(sdev); | 
|  | kfree(sdev); | 
|  | continue; | 
|  | } | 
|  | id = sdindex(sdev->idno); | 
|  | if (id == -1) { | 
|  | printd("sdadddevs: bad id number %d (%C)\n", id, id); | 
|  | goto giveup; | 
|  | } | 
|  | qlock(&devslock); | 
|  | for (i = 0; i < ARRAY_SIZE(devs); i++) { | 
|  | j = (id + i) % ARRAY_SIZE(devs); | 
|  | if (devs[j] == NULL) { | 
|  | sdev->idno = devletters[j]; | 
|  | devs[j] = sdev; | 
|  | snprintf(sdev->name, sizeof(sdev->name), "sd%c", | 
|  | devletters[j]); | 
|  | break; | 
|  | } | 
|  | } | 
|  | qunlock(&devslock); | 
|  | if (i == ARRAY_SIZE(devs)) { | 
|  | printd("sdadddevs: out of device letters\n"); | 
|  | goto giveup; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void sdaddallconfs(void (*addconf)(struct sdunit *)) | 
|  | { | 
|  | int i, u; | 
|  | struct sdev *sdev; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(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(struct chan *c, int i, struct dir *dp) | 
|  | { | 
|  | struct qid q; | 
|  | uint64_t l; | 
|  | struct sdpart *pp; | 
|  | struct sdperm *perm; | 
|  | struct sdunit *unit; | 
|  | struct 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.name); | 
|  | 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.name); | 
|  | 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.name); | 
|  | devdir(c, q, pp->sdperm.name, l, pp->sdperm.user, | 
|  | pp->sdperm.perm, dp); | 
|  | rv = 1; | 
|  | break; | 
|  | } | 
|  |  | 
|  | kref_put(&sdev->r); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | static int sd1gen(struct chan *c, int i, struct dir *dp) | 
|  | { | 
|  | struct qid q; | 
|  |  | 
|  | switch (i) { | 
|  | case Qtopctl: | 
|  | mkqid(&q, QID(0, 0, 0, Qtopctl), 0, QTFILE); | 
|  | devdir(c, q, "sdctl", 0, eve.name, 0644, dp); /* no secrets */ | 
|  | return 1; | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static int sdgen(struct chan *c, char *d, struct dirtab *dir, int j, int s, | 
|  | struct dir *dp) | 
|  | { | 
|  | struct qid q = {}; | 
|  | int64_t l; | 
|  | int i, r; | 
|  | struct sdpart *pp; | 
|  | struct sdunit *unit; | 
|  | struct sdev *sdev; | 
|  |  | 
|  | switch (TYPE(c->qid)) { | 
|  | case Qtopdir: | 
|  | if (s == DEVDOTDOT) { | 
|  | mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR); | 
|  | snprintf(get_cur_genbuf(), GENBUF_SZ, "#%s", | 
|  | sddevtab.name); | 
|  | devdir(c, q, get_cur_genbuf(), 0, eve.name, 0555, dp); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (s + Qtopbase < Qunitdir) | 
|  | return sd1gen(c, s + Qtopbase, dp); | 
|  | s -= (Qunitdir - Qtopbase); | 
|  |  | 
|  | qlock(&devslock); | 
|  | for (i = 0; i < ARRAY_SIZE(devs); i++) { | 
|  | if (devs[i]) { | 
|  | if (s < devs[i]->nunit) | 
|  | break; | 
|  | s -= devs[i]->nunit; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (i == ARRAY_SIZE(devs)) { | 
|  | /* Run off the end of the list */ | 
|  | qunlock(&devslock); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | sdev = devs[i]; | 
|  | if (sdev == NULL) { | 
|  | qunlock(&devslock); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | kref_get(&sdev->r, 1); | 
|  | qunlock(&devslock); | 
|  |  | 
|  | unit = sdev->unit[s]; | 
|  | if (unit == NULL) | 
|  | unit = sdgetunit(sdev, s); | 
|  | if (unit == NULL) { | 
|  | kref_put(&sdev->r); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | mkqid(&q, QID(sdev->idno, s, 0, Qunitdir), 0, QTDIR); | 
|  | if (emptystr(unit->sdperm.user)) | 
|  | kstrdup(&unit->sdperm.user, eve.name); | 
|  | devdir(c, q, unit->sdperm.name, 0, unit->sdperm.user, | 
|  | unit->sdperm.perm, dp); | 
|  | kref_put(&sdev->r); | 
|  | return 1; | 
|  |  | 
|  | case Qunitdir: | 
|  | if (s == DEVDOTDOT) { | 
|  | mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR); | 
|  | snprintf(get_cur_genbuf(), GENBUF_SZ, "#%s", | 
|  | sddevtab.name); | 
|  | devdir(c, q, get_cur_genbuf(), 0, eve.name, 0555, dp); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) { | 
|  | devdir(c, c->qid, "unavailable", 0, eve.name, 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); | 
|  | kref_put(&sdev->r); | 
|  | return r; | 
|  | } | 
|  | i -= Qpart; | 
|  | if (unit->part == NULL || i >= unit->npart) { | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | break; | 
|  | } | 
|  | pp = &unit->part[i]; | 
|  | if (!pp->valid) { | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&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.name); | 
|  | devdir(c, q, pp->sdperm.name, l, pp->sdperm.user, | 
|  | pp->sdperm.perm, dp); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | return 1; | 
|  | case Qraw: | 
|  | case Qctl: | 
|  | case Qpart: | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) { | 
|  | devdir(c, q, "unavailable", 0, eve.name, 0, dp); | 
|  | return 1; | 
|  | } | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | qlock(&unit->ctl); | 
|  | r = sd2gen(c, TYPE(c->qid), dp); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | return r; | 
|  | case Qtopctl: | 
|  | return sd1gen(c, TYPE(c->qid), dp); | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static struct chan *sdattach(char *spec) | 
|  | { | 
|  | struct chan *c; | 
|  | char *p; | 
|  | struct sdev *sdev; | 
|  | int idno, subno; | 
|  |  | 
|  | if (*spec == '\0') { | 
|  | c = devattach(sddevtab.name, spec); | 
|  | mkqid(&c->qid, QID(0, 0, 0, Qtopdir), 0, QTDIR); | 
|  | return c; | 
|  | } | 
|  |  | 
|  | if (spec[0] != 's' || spec[1] != 'd') | 
|  | error(EINVAL, | 
|  | "First two characters of spec must be 'sd', not %c%c", | 
|  | spec[0], spec[1]); | 
|  | idno = spec[2]; | 
|  | subno = strtol(&spec[3], &p, 0); | 
|  | if (p == &spec[3]) | 
|  | error(EINVAL, "subno '%s' is not a number", &spec[3]); | 
|  |  | 
|  | sdev = sdgetdev(idno); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such unit %d", idno); | 
|  | if (sdgetunit(sdev, subno) == NULL) { | 
|  | kref_put(&sdev->r); | 
|  | error(ENOENT, "No such subno %d", subno); | 
|  | } | 
|  |  | 
|  | c = devattach(sddevtab.name, spec); | 
|  | mkqid(&c->qid, QID(sdev->idno, subno, 0, Qunitdir), 0, QTDIR); | 
|  | c->dev = (sdev->idno << UnitLOG) + subno; | 
|  | kref_put(&sdev->r); | 
|  | return c; | 
|  | } | 
|  |  | 
|  | static struct walkqid *sdwalk(struct chan *c, struct chan *nc, char **name, | 
|  | unsigned int nname) | 
|  | { | 
|  | return devwalk(c, nc, name, nname, NULL, 0, sdgen); | 
|  | } | 
|  |  | 
|  | static size_t sdstat(struct chan *c, uint8_t *db, size_t n) | 
|  | { | 
|  | return devstat(c, db, n, NULL, 0, sdgen); | 
|  | } | 
|  |  | 
|  | static struct chan *sdopen(struct chan *c, int omode) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | struct sdpart *pp; | 
|  | struct sdunit *unit; | 
|  | struct 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 == NULL) | 
|  | error(ENOENT, "No such device"); | 
|  |  | 
|  | 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 (test_and_set_bit(0, (unsigned long *)&unit->rawinuse) != | 
|  | 0) { | 
|  | c->flag &= ~COPEN; | 
|  | kref_put(&sdev->r); | 
|  | error(EBUSY, "In use"); | 
|  | } | 
|  | unit->state = Rawcmd; | 
|  | break; | 
|  | case Qpart: | 
|  | qlock(&unit->ctl); | 
|  | if (waserror()) { | 
|  | qunlock(&unit->ctl); | 
|  | c->flag &= ~COPEN; | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  | pp = &unit->part[PART(c->qid)]; | 
|  | c->qid.vers = unit->vers + pp->vers; | 
|  | qunlock(&unit->ctl); | 
|  | poperror(); | 
|  | break; | 
|  | } | 
|  | kref_put(&sdev->r); | 
|  | return c; | 
|  | } | 
|  |  | 
|  | static void sdclose(struct chan *c) | 
|  | { | 
|  | struct sdunit *unit; | 
|  | struct 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; | 
|  | kref_put(&sdev->r); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static size_t sdbio(struct chan *c, int write, char *a, size_t len, off64_t off) | 
|  | { | 
|  | ERRSTACK(2); | 
|  | int nchange; | 
|  | uint8_t *b; | 
|  | struct sdpart *pp; | 
|  | struct sdunit *unit; | 
|  | struct sdev *sdev; | 
|  | int64_t bno; | 
|  | size_t l, max, nb, offset; | 
|  |  | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) { | 
|  | kref_put(&sdev->r); | 
|  | error(ENOENT, "No such file or directory"); | 
|  | } | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | if (unit == NULL) | 
|  | error(ENOENT, "No such file or directory"); | 
|  |  | 
|  | nchange = 0; | 
|  | qlock(&unit->ctl); | 
|  | while (waserror()) { | 
|  | /* notification of media change; go around again */ | 
|  | /* Meta-comment: I'm leaving commented-out code in place, | 
|  | * which originally contained a strcmp of the error string to | 
|  | * a value, to remind us: plan 9 is a distributed system. It's | 
|  | * possible in principle to have the storage device on this | 
|  | * machine use an sdi{ata,ahci} on another machine, and it all | 
|  | * works. Nobody is going to do that, now, so get_errno() it is. | 
|  | * if (strcmp(up->errstr, Eio) == 0 ... */ | 
|  | if ((get_errno() == EIO) && (unit->sectors == 0) && | 
|  | (nchange++ == 0)) { | 
|  | sdinitpart(unit); | 
|  | poperror(); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* other errors; give up */ | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  | pp = &unit->part[PART(c->qid)]; | 
|  | if (unit->vers + pp->vers != c->qid.vers) | 
|  | error(EIO, "disk changed"); | 
|  |  | 
|  | /* | 
|  | * 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, "bno(%d) >= pp->end(%d) or nb(%d) == 0", bno, | 
|  | pp->end, nb); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | poperror(); | 
|  | return 0; | 
|  | } | 
|  | if (!(unit->inquiry[1] & SDinq1removable)) { | 
|  | qunlock(&unit->ctl); | 
|  | poperror(); | 
|  | } | 
|  |  | 
|  | b = kzmalloc(nb * unit->secsize, MEM_WAIT); | 
|  | if (b == NULL) | 
|  | error(ENOMEM, "%s: could not allocate %d bytes", __func__, | 
|  | nb * unit->secsize); | 
|  | if (waserror()) { | 
|  | kfree(b); | 
|  | if (!(unit->inquiry[1] & SDinq1removable)) | 
|  | kref_put(&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, "IO Error"); | 
|  | 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, "IO Error"); | 
|  | 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, "IO Error"); | 
|  | if (l < offset) | 
|  | len = 0; | 
|  | else if (len > l - offset) | 
|  | len = l - offset; | 
|  | memmove(a, b + offset, len); | 
|  | } | 
|  | kfree(b); | 
|  | poperror(); | 
|  |  | 
|  | if (unit->inquiry[1] & SDinq1removable) { | 
|  | qunlock(&unit->ctl); | 
|  | poperror(); | 
|  | } | 
|  |  | 
|  | kref_put(&sdev->r); | 
|  | return len; | 
|  | } | 
|  |  | 
|  | static size_t sdrio(struct sdreq *r, void *a, size_t n) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | void *data; | 
|  |  | 
|  | if (n >= SDmaxio || n < 0) | 
|  | error(EINVAL, "%d is < 0 or > SDmaxio", n); | 
|  |  | 
|  | data = NULL; | 
|  | if (n) { | 
|  | data = kzmalloc(n, MEM_WAIT); | 
|  | if (data == NULL) | 
|  | error(ENOMEM, "Alloc of %d bytes failed", n); | 
|  | if (r->write) | 
|  | memmove(data, a, n); | 
|  | } | 
|  | r->data = data; | 
|  | r->dlen = n; | 
|  |  | 
|  | if (waserror()) { | 
|  | kfree(data); | 
|  | r->data = NULL; | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | if (r->unit->dev->ifc->rio(r) != SDok) | 
|  | error(EIO, "IO Error"); | 
|  |  | 
|  | if (!r->write && r->rlen > 0) | 
|  | memmove(a, data, r->rlen); | 
|  | kfree(data); | 
|  | r->data = NULL; | 
|  | poperror(); | 
|  |  | 
|  | return r->rlen; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * SCSI simulation for non-SCSI devices | 
|  | */ | 
|  | int sdsetsense(struct sdreq *r, int status, int key, int asc, int ascq) | 
|  | { | 
|  | int len; | 
|  | struct 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(struct 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 == NULL || 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(struct sdreq *r, void *info, int ilen) | 
|  | { | 
|  | uint8_t *cmd, *p; | 
|  | uint64_t len; | 
|  | struct 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 == NULL || 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 == NULL || 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 size_t sdread(struct chan *c, void *a, size_t n, off64_t off) | 
|  | { | 
|  | ERRSTACK(1); | 
|  | char *p, *e, *buf; | 
|  | struct sdpart *pp; | 
|  | struct sdunit *unit; | 
|  | struct sdev *sdev; | 
|  | off64_t offset; | 
|  | int i, l, mm, status; | 
|  |  | 
|  | offset = off; | 
|  | switch (TYPE(c->qid)) { | 
|  | default: | 
|  | error(EPERM, "Permission denied"); | 
|  | case Qtopctl: | 
|  | mm = 64 * 1024; /* room for register dumps */ | 
|  | p = buf = kzmalloc(mm, 0); | 
|  | if (p == NULL) | 
|  | error(ENOMEM, "Alloc of %d bytes failed", mm); | 
|  | e = p + mm; | 
|  | qlock(&devslock); | 
|  | for (i = 0; i < ARRAY_SIZE(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); | 
|  | kfree(buf); | 
|  | return n; | 
|  |  | 
|  | case Qtopdir: | 
|  | case Qunitdir: | 
|  | return devdirread(c, a, n, 0, 0, sdgen); | 
|  |  | 
|  | case Qctl: | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such device"); | 
|  |  | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | mm = 16 * 1024; /* room for register dumps */ | 
|  | p = kzmalloc(mm, 0); | 
|  | if (p == NULL) | 
|  | error(ENOMEM, "Alloc of %d bytes failed", mm); | 
|  | l = snprintf(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 == NULL) | 
|  | l += snprintf(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 += snprintf(p + l, mm - l, | 
|  | "part %s %llu %llu\n", | 
|  | pp->sdperm.name, | 
|  | pp->start, pp->end); | 
|  | pp++; | 
|  | } | 
|  | } | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | l = readstr(offset, a, n, p); | 
|  | kfree(p); | 
|  | return l; | 
|  |  | 
|  | case Qraw: | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such file or directory"); | 
|  |  | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | qlock(&unit->raw); | 
|  | if (waserror()) { | 
|  | qunlock(&unit->raw); | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  | if (unit->state == Rawdata) { | 
|  | unit->state = Rawstatus; | 
|  | n = sdrio(unit->req, a, n); | 
|  | } else if (unit->state == Rawstatus) { | 
|  | status = unit->req->status; | 
|  | unit->state = Rawcmd; | 
|  | kfree(unit->req); | 
|  | unit->req = NULL; | 
|  | n = readnum(0, a, n, status, NUMSIZE); | 
|  | } else | 
|  | n = 0; | 
|  | qunlock(&unit->raw); | 
|  | kref_put(&sdev->r); | 
|  | poperror(); | 
|  | return n; | 
|  |  | 
|  | case Qpart: | 
|  | return sdbio(c, 0, a, n, off); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void legacytopctl(struct cmdbuf *); | 
|  |  | 
|  | static size_t sdwrite(struct chan *c, void *a, size_t n, off64_t off) | 
|  | { | 
|  | ERRSTACK(2); | 
|  | char *f0; | 
|  | int i; | 
|  | uint64_t end, start; | 
|  | struct cmdbuf *cb; | 
|  | struct sdifc *ifc; | 
|  | struct sdreq *req; | 
|  | struct sdunit *unit; | 
|  | struct sdev *sdev; | 
|  |  | 
|  | switch (TYPE(c->qid)) { | 
|  | default: | 
|  | error(EPERM, "Permission denied"); | 
|  | case Qtopctl: | 
|  | cb = parsecmd(a, n); | 
|  | if (waserror()) { | 
|  | kfree(cb); | 
|  | nexterror(); | 
|  | } | 
|  | if (cb->nf == 0) | 
|  | error(EINVAL, "empty control message"); | 
|  | f0 = cb->f[0]; | 
|  | cb->f++; | 
|  | cb->nf--; | 
|  | if (strcmp(f0, "config") == 0) { | 
|  | /* wormhole into ugly legacy interface */ | 
|  | legacytopctl(cb); | 
|  | poperror(); | 
|  | kfree(cb); | 
|  | break; | 
|  | } | 
|  | /* | 
|  | * "ata arg..." invokes sdifc[i]->wtopctl(NULL, cb), | 
|  | * where sdifc[i]->sdperm.name=="ata" and cb contains the args. | 
|  | */ | 
|  | ifc = NULL; | 
|  | sdev = NULL; | 
|  | for (i = 0; sdifc[i]; i++) { | 
|  | if (strcmp(sdifc[i]->name, f0) == 0) { | 
|  | ifc = sdifc[i]; | 
|  | sdev = NULL; | 
|  | 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) { | 
|  | sdev = sdgetdev(f0[2]); | 
|  | if (sdev != NULL) { | 
|  | ifc = sdev->ifc; | 
|  | goto subtopctl; | 
|  | } | 
|  | } | 
|  | error(EINVAL, "unknown interface"); | 
|  |  | 
|  | subtopctl: | 
|  | if (waserror()) { | 
|  | if (sdev) | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  | if (ifc->wtopctl) | 
|  | ifc->wtopctl(sdev, cb); | 
|  | else | 
|  | error(EINVAL, "Bad control"); | 
|  | poperror(); | 
|  | poperror(); | 
|  | if (sdev) | 
|  | kref_put(&sdev->r); | 
|  | kfree(cb); | 
|  | break; | 
|  |  | 
|  | case Qctl: | 
|  | cb = parsecmd(a, n); | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such file or directory"); | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  |  | 
|  | qlock(&unit->ctl); | 
|  | if (waserror()) { | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | kfree(cb); | 
|  | nexterror(); | 
|  | } | 
|  | if (unit->vers != c->qid.vers) | 
|  | error(EIO, "Unit changed"); | 
|  |  | 
|  | if (cb->nf < 1) | 
|  | error(EINVAL, "%s requires at least one argument", | 
|  | cb->f[0]); | 
|  | if (strcmp(cb->f[0], "part") == 0) { | 
|  | if (cb->nf != 4) | 
|  | error(EINVAL, | 
|  | "Part got %d arguments, requires 4", | 
|  | cb->nf); | 
|  | if (unit->sectors == 0) | 
|  | error(EINVAL, "unit->sectors was 0"); | 
|  | if (!sdinitpart(unit)) | 
|  | error(EIO, "sdinitpart failed"); | 
|  | start = strtoul(cb->f[2], 0, 0); | 
|  | end = strtoul(cb->f[3], 0, 0); | 
|  | sdaddpart(unit, cb->f[1], start, end); | 
|  | } else if (strcmp(cb->f[0], "delpart") == 0) { | 
|  | if (cb->nf != 2) | 
|  | error(EINVAL, | 
|  | "delpart got %d args, 2 required"); | 
|  | if (unit->part == NULL) | 
|  | error(EIO, "partition was NULL"); | 
|  | sddelpart(unit, cb->f[1]); | 
|  | } else if (unit->dev->ifc->wctl) | 
|  | unit->dev->ifc->wctl(unit, cb); | 
|  | else | 
|  | error(EINVAL, "Bad control %s", cb->f[0]); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | poperror(); | 
|  | kfree(cb); | 
|  | break; | 
|  |  | 
|  | case Qraw: | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such file or directory"); | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | qlock(&unit->raw); | 
|  | if (waserror()) { | 
|  | qunlock(&unit->raw); | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  | switch (unit->state) { | 
|  | case Rawcmd: | 
|  | if (n < 6 || n > sizeof(req->cmd)) | 
|  | error(EINVAL, "%d is < 6 or > %d", n, | 
|  | sizeof(req->cmd)); | 
|  | req = kzmalloc(sizeof(struct sdreq), 0); | 
|  | if (req == NULL) | 
|  | error(ENOMEM, "Can't allocate an sdreq"); | 
|  | 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; | 
|  | kfree(unit->req); | 
|  | unit->req = NULL; | 
|  | error(EINVAL, "Bad use of rawstatus"); | 
|  |  | 
|  | case Rawdata: | 
|  | unit->state = Rawstatus; | 
|  | unit->req->write = 1; | 
|  | n = sdrio(unit->req, a, n); | 
|  | } | 
|  | qunlock(&unit->raw); | 
|  | kref_put(&sdev->r); | 
|  | poperror(); | 
|  | break; | 
|  | case Qpart: | 
|  | return sdbio(c, 1, a, n, off); | 
|  | } | 
|  |  | 
|  | return n; | 
|  | } | 
|  |  | 
|  | static size_t sdwstat(struct chan *c, uint8_t *dp, size_t n) | 
|  | { | 
|  | ERRSTACK(2); | 
|  | struct dir *d; | 
|  | struct sdpart *pp; | 
|  | struct sdperm *perm; | 
|  | struct sdunit *unit; | 
|  | struct sdev *sdev; | 
|  |  | 
|  | if (c->qid.type & QTDIR) | 
|  | error(EPERM, "Not a directory"); | 
|  |  | 
|  | sdev = sdgetdev(DEV(c->qid)); | 
|  | if (sdev == NULL) | 
|  | error(ENOENT, "No such file or device"); | 
|  | unit = sdev->unit[UNIT(c->qid)]; | 
|  | qlock(&unit->ctl); | 
|  | d = NULL; | 
|  | if (waserror()) { | 
|  | kfree(d); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | nexterror(); | 
|  | } | 
|  |  | 
|  | switch (TYPE(c->qid)) { | 
|  | default: | 
|  | error(EPERM, "Permission denied"); | 
|  | 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(ENOENT, "No such file or directory"); | 
|  | perm = &pp->sdperm; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* TODO: Implement permissions checking and raise errors as appropriate. | 
|  | * */ | 
|  | // if (strcmp(current->user.name, perm->user) && !iseve()) | 
|  | // error(Eperm); | 
|  |  | 
|  | d = kzmalloc(sizeof(struct dir) + n, 0); | 
|  | n = convM2D(dp, n, &d[0], (char *)&d[1]); | 
|  | if (n == 0) | 
|  | error(EIO, "Short status"); | 
|  | if (!emptystr(d[0].uid)) | 
|  | kstrdup(&perm->user, d[0].uid); | 
|  | if (d[0].mode != -1) | 
|  | perm->perm = (perm->perm & ~0777) | (d[0].mode & 0777); | 
|  |  | 
|  | kfree(d); | 
|  | qunlock(&unit->ctl); | 
|  | kref_put(&sdev->r); | 
|  | poperror(); | 
|  | return n; | 
|  | } | 
|  |  | 
|  | static int configure(char *spec, struct devconf *cf) | 
|  | { | 
|  | struct sdev *s, *sdev; | 
|  | char *p; | 
|  | int i; | 
|  |  | 
|  | if (sdindex(*spec) < 0) | 
|  | error(EINVAL, "bad sd spec '%s'", spec); | 
|  |  | 
|  | p = strchr(cf->type, '/'); | 
|  | if (p != NULL) | 
|  | *p++ = '\0'; | 
|  |  | 
|  | for (i = 0; sdifc[i] != NULL; i++) | 
|  | if (strcmp(sdifc[i]->name, cf->type) == 0) | 
|  | break; | 
|  | if (sdifc[i] == NULL) | 
|  | error(ENOENT, "sd type not found"); | 
|  | if (p) | 
|  | *(p - 1) = '/'; | 
|  |  | 
|  | if (sdifc[i]->probe == NULL) | 
|  | error(EIO, "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; | 
|  | struct sdev *sdev; | 
|  | struct sdunit *unit; | 
|  |  | 
|  | if ((i = sdindex(*spec)) < 0) | 
|  | error(ENOENT, "No such file or directory '%s'", spec); | 
|  |  | 
|  | qlock(&devslock); | 
|  | sdev = devs[i]; | 
|  | if (sdev == NULL) { | 
|  | qunlock(&devslock); | 
|  | error(ENOENT, "No such file or directory at index %d", i); | 
|  | } | 
|  | if (kref_refcnt(&sdev->r)) { | 
|  | qunlock(&devslock); | 
|  | error(EBUSY, "%s is busy", spec); | 
|  | } | 
|  | devs[i] = NULL; | 
|  | 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++) { | 
|  | unit = sdev->unit[i]; | 
|  | if (unit) { | 
|  | kfree(unit->sdperm.name); | 
|  | kfree(unit->sdperm.user); | 
|  | kfree(unit); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (sdev->ifc->clear) | 
|  | sdev->ifc->clear(sdev); | 
|  | kfree(sdev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sdconfig(int on, char *spec, struct devconf *cf) | 
|  | { | 
|  | if (on) | 
|  | return configure(spec, cf); | 
|  | return unconfigure(spec); | 
|  | } | 
|  |  | 
|  | struct dev sddevtab __devtab = { | 
|  | .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, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * This is wrong for so many reasons.  This code must go. | 
|  | */ | 
|  | struct confdata { | 
|  | int on; | 
|  | char *spec; | 
|  | struct devconf cf; | 
|  | }; | 
|  |  | 
|  | static void parseswitch(struct confdata *cd, char *option) | 
|  | { | 
|  | if (!strcmp("on", option)) | 
|  | cd->on = 1; | 
|  | else if (!strcmp("off", option)) | 
|  | cd->on = 0; | 
|  | else | 
|  | error(EINVAL, "Got %s, must be on or off", option); | 
|  | } | 
|  |  | 
|  | static void parsespec(struct confdata *cd, char *option) | 
|  | { | 
|  | if (strlen(option) > 1) | 
|  | error(EINVAL, "spec is %d bytes, must be 1", strlen(option)); | 
|  | cd->spec = option; | 
|  | } | 
|  |  | 
|  | static struct devport *getnewport(struct devconf *dc) | 
|  | { | 
|  | struct devport *p; | 
|  |  | 
|  | p = (struct devport *)kzmalloc( | 
|  | (dc->nports + 1) * sizeof(struct devport), 0); | 
|  | if (p == NULL) | 
|  | error(ENOMEM, "Can't allocate %d bytes for %d ports", | 
|  | dc->nports, (dc->nports + 1) * sizeof(struct devport)); | 
|  | if (dc->nports > 0) { | 
|  | memmove(p, dc->ports, dc->nports * sizeof(struct devport)); | 
|  | kfree(dc->ports); | 
|  | } | 
|  | dc->ports = p; | 
|  | p = &dc->ports[dc->nports++]; | 
|  | p->size = -1; | 
|  | p->port = (uint32_t)-1; | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static void parseport(struct confdata *cd, char *option) | 
|  | { | 
|  | char *e; | 
|  | struct 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 == NULL || *e != '\0') | 
|  | error(EINVAL, "option %s is not a number", option); | 
|  | } | 
|  |  | 
|  | static void parsesize(struct confdata *cd, char *option) | 
|  | { | 
|  | char *e; | 
|  | struct 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 == NULL || *e != '\0') | 
|  | error(EINVAL, "%s is not a number", option); | 
|  | } | 
|  |  | 
|  | static void parseirq(struct confdata *cd, char *option) | 
|  | { | 
|  | char *e; | 
|  |  | 
|  | cd->cf.intnum = strtoul(option, &e, 0); | 
|  | if (e == NULL || *e != '\0') | 
|  | error(EINVAL, "%s is not a number", option); | 
|  | } | 
|  |  | 
|  | static void parsetype(struct confdata *cd, char *option) | 
|  | { | 
|  | cd->cf.type = option; | 
|  | } | 
|  |  | 
|  | static struct { | 
|  | char *name; | 
|  | void (*parse)(struct confdata *, char *unused_char_p_t); | 
|  | } options[] = { | 
|  | {"switch", parseswitch}, {"spec", parsespec}, {"port", parseport}, | 
|  | {"size", parsesize},     {"irq", parseirq},   {"type", parsetype}, | 
|  | }; | 
|  |  | 
|  | static void legacytopctl(struct cmdbuf *cb) | 
|  | { | 
|  | char *opt; | 
|  | int i, j; | 
|  | struct confdata cd; | 
|  |  | 
|  | memset(&cd, 0, sizeof(cd)); | 
|  | cd.on = -1; | 
|  | for (i = 0; i < cb->nf; i += 2) { | 
|  | if (i + 2 > cb->nf) | 
|  | error(EINVAL, "FIX ME. I don't know what this means"); | 
|  | opt = cb->f[i]; | 
|  | for (j = 0; j < ARRAY_SIZE(options); j++) | 
|  | if (strcmp(opt, options[j].name) == 0) { | 
|  | options[j].parse(&cd, cb->f[i + 1]); | 
|  | break; | 
|  | } | 
|  | if (j == ARRAY_SIZE(options)) | 
|  | error(EINVAL, "FIX ME"); | 
|  | } | 
|  | /* this has been rewritten to accommodate sdaoe */ | 
|  | if (cd.on < 0 || cd.spec == 0) | 
|  | error(EINVAL, "cd.on(%d) < 0 or cd.spec == 0", cd.on); | 
|  | if (cd.on && cd.cf.type == NULL) | 
|  | error(EINVAL, "cd.on non-zero and cd.cf.type == NULL"); | 
|  | sdconfig(cd.on, cd.spec, &cd.cf); | 
|  | } |