/*
* Storage Device.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"
#include "../port/sd.h"
extern Dev sddevtab;
extern SDifc* sdifc[];
static char devletters[] = "0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static SDev *devs[sizeof devletters-1];
static QLock devslock;
enum {
Rawcmd,
Rawdata,
Rawstatus,
};
enum {
Qtopdir = 1, /* top level directory */
Qtopbase,
Qtopctl = Qtopbase,
Qunitdir, /* directory per unit */
Qunitbase,
Qctl = Qunitbase,
Qraw,
Qpart,
TypeLOG = 4,
NType = (1<<TypeLOG),
TypeMASK = (NType-1),
TypeSHIFT = 0,
PartLOG = 8,
NPart = (1<<PartLOG),
PartMASK = (NPart-1),
PartSHIFT = TypeLOG,
UnitLOG = 8,
NUnit = (1<<UnitLOG),
UnitMASK = (NUnit-1),
UnitSHIFT = (PartLOG+TypeLOG),
DevLOG = 8,
NDev = (1 << DevLOG),
DevMASK = (NDev-1),
DevSHIFT = (UnitLOG+PartLOG+TypeLOG),
Ncmd = 20,
};
#define TYPE(q) ((((ulong)(q).path)>>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)<<DevSHIFT)|((u)<<UnitSHIFT)|\
((p)<<PartSHIFT)|((t)<<TypeSHIFT))
void
sdaddpart(SDunit* unit, char* name, uvlong start, uvlong end)
{
SDpart *pp;
int i, partno;
/*
* Check name not already used
* and look for a free slot.
*/
if(unit->part != nil){
partno = -1;
for(i = 0; i < unit->npart; i++){
pp = &unit->part[i];
if(!pp->valid){
if(partno == -1)
partno = i;
break;
}
if(strcmp(name, pp->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->name, name);
kstrdup(&pp->user, eve);
pp->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->name) == 0)
break;
pp++;
}
if(i >= unit->npart)
error(Ebadctl);
if(strcmp(up->user, pp->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);
}
/* 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.
*/
snprint(buf, sizeof buf, "%spart", unit->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();
}
}
}
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->name, buf);
kstrdup(&unit->user, eve);
unit->perm = 0555;
unit->subno = subno;
unit->dev = sdev;
if(sdev->enabled == 0 && sdev->ifc->enable)
sdev->ifc->enable(sdev);
sdev->enabled = 1;
/*
* No need to lock anything here as this is only
* called before the unit is made available in the
* sdunit[] array.
*/
if(unit->dev->ifc->verify(unit) == 0){
qunlock(&sdev->unitlock);
free(unit);
return nil;
}
sdev->unit[subno] = unit;
}
qunlock(&sdev->unitlock);
return unit;
}
static void
sdreset(void)
{
int i;
SDev *sdev;
/*
* Probe all known controller types and register any devices found.
*/
for(i = 0; sdifc[i] != nil; i++){
if(sdifc[i]->pnp == nil || (sdev = sdifc[i]->pnp()) == nil)
continue;
sdadddevs(sdev);
}
}
void
sdadddevs(SDev *sdev)
{
int i, j, id;
SDev *next;
for(; sdev; sdev=next){
next = sdev->next;
sdev->unit = (SDunit**)malloc(sdev->nunit * sizeof(SDunit*));
sdev->unitflg = (int*)malloc(sdev->nunit * sizeof(int));
if(sdev->unit == nil || sdev->unitflg == nil){
print("sdadddevs: out of memory\n");
giveup:
free(sdev->unit);
free(sdev->unitflg);
if(sdev->ifc->clear)
sdev->ifc->clear(sdev);
free(sdev);
continue;
}
id = sdindex(sdev->idno);
if(id == -1){
print("sdadddevs: bad id number %d (%C)\n", id, id);
goto giveup;
}
qlock(&devslock);
for(i=0; i<nelem(devs); i++){
if(devs[j = (id+i)%nelem(devs)] == nil){
sdev->idno = devletters[j];
devs[j] = sdev;
snprint(sdev->name, sizeof sdev->name, "sd%c", devletters[j]);
break;
}
}
qunlock(&devslock);
if(i == nelem(devs)){
print("sdadddevs: out of device letters\n");
goto giveup;
}
}
}
// void
// sdrmdevs(SDev *sdev)
// {
// char buf[2];
//
// snprint(buf, sizeof buf, "%c", sdev->idno);
// unconfigure(buf);
// }
void
sdaddallconfs(void (*addconf)(SDunit *))
{
int i, u;
SDev *sdev;
for(i = 0; i < nelem(devs); i++) /* each controller */
for(sdev = devs[i]; sdev; sdev = sdev->next)
for(u = 0; u < sdev->nunit; u++) /* each drive */
(*addconf)(sdev->unit[u]);
}
static int
sd2gen(Chan* c, int i, Dir* dp)
{
Qid q;
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 = 0644; /* nothing secret in ctl */
}
devdir(c, q, "ctl", 0, perm->user, perm->perm, dp);
rv = 1;
break;
case Qraw:
mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qraw),
unit->vers, QTFILE);
perm = &unit->rawperm;
if(emptystr(perm->user)){
kstrdup(&perm->user, eve);
perm->perm = DMEXCL|0600;
}
devdir(c, q, "raw", 0, perm->user, perm->perm, dp);
rv = 1;
break;
case Qpart:
pp = &unit->part[PART(c->qid)];
l = (pp->end - pp->start) * unit->secsize;
mkqid(&q, QID(DEV(c->qid), UNIT(c->qid), PART(c->qid), Qpart),
unit->vers+pp->vers, QTFILE);
if(emptystr(pp->user))
kstrdup(&pp->user, eve);
devdir(c, q, pp->name, l, pp->user, pp->perm, dp);
rv = 1;
break;
}
decref(&sdev->r);
return rv;
}
static int
sd1gen(Chan* c, int i, Dir* dp)
{
Qid q;
switch(i){
case Qtopctl:
mkqid(&q, QID(0, 0, 0, Qtopctl), 0, QTFILE);
devdir(c, q, "sdctl", 0, eve, 0644, dp); /* no secrets */
return 1;
}
return -1;
}
static int
sdgen(Chan* c, char*, Dirtab*, int, 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);
snprint(up->genbuf, sizeof up->genbuf, "#%C",
sddevtab.dc);
devdir(c, q, up->genbuf, 0, eve, 0555, dp);
return 1;
}
if(s+Qtopbase < Qunitdir)
return sd1gen(c, s+Qtopbase, dp);
s -= (Qunitdir-Qtopbase);
qlock(&devslock);
for(i=0; i<nelem(devs); i++){
if(devs[i]){
if(s < devs[i]->nunit)
break;
s -= devs[i]->nunit;
}
}
if(i == nelem(devs)){
/* Run off the end of the list */
qunlock(&devslock);
return -1;
}
if((sdev = devs[i]) == nil){
qunlock(&devslock);
return 0;
}
incref(&sdev->r);
qunlock(&devslock);
if((unit = sdev->unit[s]) == nil)
if((unit = sdgetunit(sdev, s)) == nil){
decref(&sdev->r);
return 0;
}
mkqid(&q, QID(sdev->idno, s, 0, Qunitdir), 0, QTDIR);
if(emptystr(unit->user))
kstrdup(&unit->user, eve);
devdir(c, q, unit->name, 0, unit->user, unit->perm, dp);
decref(&sdev->r);
return 1;
case Qunitdir:
if(s == DEVDOTDOT){
mkqid(&q, QID(0, 0, 0, Qtopdir), 0, QTDIR);
snprint(up->genbuf, sizeof 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->user))
kstrdup(&pp->user, eve);
devdir(c, q, pp->name, l, pp->user, pp->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] & SDinq1removable)){
qunlock(&unit->ctl);
poperror();
}
b = sdmalloc(nb*unit->secsize);
if(b == nil)
error(Enomem);
if(waserror()){
sdfree(b);
if(!(unit->inquiry[1] & SDinq1removable))
decref(&sdev->r); /* gadverdamme! */
nexterror();
}
offset = off%unit->secsize;
if(offset+len > nb*unit->secsize)
len = nb*unit->secsize - offset;
if(write){
if(offset || (len%unit->secsize)){
l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
if(l < 0)
error(Eio);
if(l < (nb*unit->secsize)){
nb = l/unit->secsize;
l = nb*unit->secsize - offset;
if(len > l)
len = l;
}
}
memmove(b+offset, a, len);
l = unit->dev->ifc->bio(unit, 0, 1, b, nb, bno);
if(l < 0)
error(Eio);
if(l < offset)
len = 0;
else if(len > l - offset)
len = l - offset;
}
else{
l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
if(l < 0)
error(Eio);
if(l < offset)
len = 0;
else if(len > l - offset)
len = l - offset;
memmove(a, b+offset, len);
}
sdfree(b);
poperror();
if(unit->inquiry[1] & SDinq1removable){
qunlock(&unit->ctl);
poperror();
}
decref(&sdev->r);
return len;
}
static 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);
if(p == nil)
error(Enomem);
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);
if(p == nil)
error(Enomem);
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->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->SDperm;
break;
}
if(strcmp(up->user, perm->user) && !iseve())
error(Eperm);
d = smalloc(sizeof(Dir)+n);
n = convM2D(dp, n, &d[0], (char*)&d[1]);
if(n == 0)
error(Eshortstat);
if(!emptystr(d[0].uid))
kstrdup(&perm->user, d[0].uid);
if(d[0].mode != ~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->name);
free(unit->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, /* probe; only called for pcmcia-like devices */
};
/*
* This is wrong for so many reasons. This code must go.
*/
typedef struct Confdata Confdata;
struct Confdata {
int on;
char* spec;
DevConf cf;
};
static void
parseswitch(Confdata* cd, char* option)
{
if(!strcmp("on", option))
cd->on = 1;
else if(!strcmp("off", option))
cd->on = 0;
else
error(Ebadarg);
}
static void
parsespec(Confdata* cd, char* option)
{
if(strlen(option) > 1)
error(Ebadarg);
cd->spec = option;
}
static Devport*
getnewport(DevConf* dc)
{
Devport *p;
p = (Devport *)malloc((dc->nports + 1) * sizeof(Devport));
if(p == nil)
error(Enomem);
if(dc->nports > 0){
memmove(p, dc->ports, dc->nports * sizeof(Devport));
free(dc->ports);
}
dc->ports = p;
p = &dc->ports[dc->nports++];
p->size = -1;
p->port = (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; i<cb->nf; i+=2){
if(i+2 > cb->nf)
error(Ebadarg);
opt = cb->f[i];
for(j=0; j<nelem(options); j++)
if(strcmp(opt, options[j].name) == 0){
options[j].parse(&cd, cb->f[i+1]);
break;
}
if(j == nelem(options))
error(Ebadarg);
}
/* this has been rewritten to accomodate sdaoe */
if(cd.on < 0 || cd.spec == 0)
error(Ebadarg);
if(cd.on && cd.cf.type == nil)
error(Ebadarg);
sdconfig(cd.on, cd.spec, &cd.cf);
}
|