blob: b0c894bc23e587ab067e7057519800614a110498 [file] [log] [blame]
/*
* 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",
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",
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);
}