plan9fox/sys/src/cmd/cwfs/juke.c

1333 lines
28 KiB
C

/*
* drive HP optical-disc jukeboxes (e.g. HP 1200EX).
* used to issue SCSI commands directly to the host adapter;
* now (via scsi.c) it issues them via scsi(2) using
* /dev/sdXX/raw to run the robotics, and uses normal i/o
* on /dev/sdXX/data to run the drives.
*/
#include "all.h"
#include "io.h"
enum {
SCSInone = SCSIread,
MAXDRIVE = 10,
MAXSIDE = 500, /* max. disc sides */
TWORM = MINUTE(10),
THYSTER = SECOND(10),
Sectorsz = 512, /* usual disk sector size */
Jukemagic = 0xbabfece2,
};
typedef struct Side Side;
struct Side
{
QLock; /* protects loading/unloading */
int elem; /* element number */
int drive; /* if loaded, where */
uchar status; /* Sunload, etc */
uchar rot; /* if backside */
int ord; /* ordinal number for labeling */
Timet time; /* time since last access, to unspin */
Timet stime; /* time since last spinup, for hysteresis */
long nblock; /* number of native blocks */
long block; /* bytes per native block */
long mult; /* multiplier to get plan9 blocks */
long max; /* max size in plan9 blocks */
};
typedef struct Juke Juke;
struct Juke
{
QLock; /* protects drive mechanism */
Side side[MAXSIDE];
int nside; /* # of storage elements (*2 if rev) */
int ndrive; /* # of transfer elements */
Device* juke; /* devworm of changer */
Device* drive[MAXDRIVE]; /* devworm for i/o */
uchar offline[MAXDRIVE]; /* drives removed from service */
int isfixedsize; /* flag: one size fits all? */
long fixedsize; /* the one size that fits all */
int probeok; /* wait for init to probe */
Scsi* robot; /* scsi(2) interface to robotics */
char* robotdir; /* /dev/sdXX name */
/*
* geometry returned by mode sense.
* a *0 number (such as mt0) is the `element number' of the
* first element of that type (e.g., mt, or motor transport).
* an n* number is the quantity of them.
*/
int mt0, nmt; /* motor transports (robot pickers) */
int se0, nse; /* storage elements (discs, slots) */
int ie0, nie; /* interchange elements (mailbox slots) */
int dt0, ndt; /* drives (data transfer?) */
int rot; /* if true, discs are double-sided */
ulong magic;
Juke* link;
};
static Juke* jukelist;
enum
{
Sempty = 0, /* does not exist */
Sunload, /* on the shelf */
Sstart, /* loaded and spinning */
};
static int bestdrive(Juke*, int);
static void element(Juke*, int);
static int mmove(Juke*, int, int, int, int);
static void shelves(void);
static int waitready(Juke *, Device*);
static int wormsense(Device*);
static Side* wormunit(Device*);
/* create a new label and try to write it */
static void
newlabel(Device *d, Off labelblk, char *labelbuf, unsigned vord)
{
Label *label = (Label *)labelbuf;
memset(labelbuf, 0, RBUFSIZE);
label->magic = Labmagic;
label->ord = vord;
strncpy(label->service, service, sizeof label->service);
if (!okay("write new label"))
print("NOT writing new label\n");
else if (wormwrite(d, labelblk, labelbuf))
/* wormwrite will have complained in detail */
fprint(2, "can't write new label on side %d\n", vord);
else if (chatty)
print("wrote new label on side %d\n", vord);
}
/* check for label in last block. call with v qlocked. */
Side*
wormlabel(Device *d, Side *v)
{
int vord;
Off labelblk = v->max - 1; /* last block */
char labelbuf[RBUFSIZE];
Label *label = (Label *)labelbuf;
Juke *w = d->private;
/* wormread calls wormunit, which locks v */
vord = v->ord;
qunlock(v);
memset(label, 0, sizeof *label);
if (wormread(d, labelblk, labelbuf)) {
/*
* wormread will have complained in detail about the error;
* no need to repeat most of that detail.
* probably an unwritten WORM-disc label; write a new one.
*/
fprint(2, "error reading label block of side %d\n", vord);
newlabel(d, labelblk, labelbuf, vord);
} else if (label->magic != Labmagic) {
swab8(&label->magic);
if (label->magic == Labmagic) {
fprint(2, "side %d's label magic byte-swapped; filsys should be configured with xD",
vord);
swab2(&label->ord);
/* could look for Devswab in Juke's filsys */
} else {
/*
* magic # is wrong in both byte orders, thus
* probably the label is empty on RW media,
* so create a new one and try to write it.
*/
fprint(2, "bad magic number in label of side %d\n", vord);
newlabel(d, labelblk, labelbuf, vord);
}
}
qlock(v);
if (v->ord != vord)
panic("wormlabel: side %d switched ordinal to %d underfoot",
vord, v->ord);
if (label->ord != vord) {
fprint(2, "labelled worm side %Z has wrong ordinal in label (%d, want %d)",
d, label->ord, vord);
qunlock(v);
cmd_wormreset(0, nil); /* put discs away */
panic("wrong ordinal in label");
}
if(chatty)
print("label %Z ordinal %d\n", d, v->ord);
qunlock(v);
/*
* wormunit should return without calling us again,
* since v is now known.
*/
if (w != d->private)
panic("wormlabel: w != %Z->private", d);
return wormunit(d);
}
/*
* mounts and spins up the device
* locks the structure
*/
static Side*
wormunit(Device *d) /* d is l0 or r2 (e.g.) */
{
int p, drive;
Device *dr; /* w0 or w1.2.0 (e.g.) */
Side *v;
Juke *w;
Dir *dir;
w = d->private;
if (w == nil)
panic("wormunit %Z nil juke", d);
if (w->magic != Jukemagic)
panic("bad magic in Juke for %Z", d);
p = d->wren.targ;
if(p < 0 || w && p >= w->nside) {
panic("wormunit: target %d out of range for %Z", p, d);
return 0;
}
/*
* if disk is unloaded, must load it
* into next (circular) logical unit
*/
v = &w->side[p];
qlock(v);
if(v->status == Sunload) {
for(;;) {
qlock(w);
drive = bestdrive(w, p);
if(drive >= 0)
break;
qunlock(w);
delay(100);
}
if(chatty)
print("\tload r%ld drive %Z\n", v-w->side, w->drive[drive]);
if(mmove(w, w->mt0, v->elem, w->dt0+drive, v->rot)) {
qunlock(w);
goto sbad;
}
v->drive = drive;
v->status = Sstart;
v->stime = toytime();
qunlock(w);
dr = w->drive[drive];
if (!waitready(w, dr))
goto sbad;
v->stime = toytime();
} else
dr = w->drive[v->drive];
if(v->status != Sstart) {
if(v->status == Sempty)
fprint(2, "worm: unit empty %Z\n", d);
else
fprint(2, "worm: not started %Z\n", d);
goto sbad;
}
v->time = toytime();
if(v->block) /* side is known already */
return v;
/*
* load and record information about side
*/
if (dr->wren.file)
dr->wren.sddata = dataof(dr->wren.file);
else {
if (dr->wren.sddir == nil) {
if (dr->type == Devwren)
dr->wren.sddir = sdof(dr);
if (dr->wren.sddir == nil)
panic("wormunit: %Z for %Z not a wren", dr, d);
}
dr->wren.sddata = smprint("%s/data", dr->wren.sddir);
}
if (dr->wren.fd == 0)
dr->wren.fd = open(dr->wren.sddata, ORDWR);
if (dr->wren.fd < 0) {
fprint(2, "wormunit: can't open %s for %Z: %r\n", dr->wren.sddata, d);
goto sbad;
}
v->block = inqsize(dr->wren.sddata);
if(v->block <= 0) {
if(chatty)
print("\twormunit %Z block size %ld, setting to %d\n",
d, v->block, Sectorsz);
v->block = Sectorsz;
}
dir = dirfstat(dr->wren.fd);
v->nblock = dir->length / v->block;
free(dir);
v->mult = (RBUFSIZE + v->block - 1) / v->block;
v->max = (v->nblock + 1) / v->mult;
if(chatty){
print("\tworm %Z: drive %Z (juke drive %d)\n",
d, w->drive[v->drive], v->drive);
print("\t\t%,ld %ld-byte sectors, ", v->nblock, v->block);
print("%,ld %d-byte blocks\n", v->max, RBUFSIZE);
print("\t\t%ld multiplier\n", v->mult);
}
if(d->type == Devlworm)
return wormlabel(d, v);
else
return v;
sbad:
qunlock(v);
return 0;
}
/* wait 10s for optical drive to spin up */
static int
waitready(Juke *w, Device *d)
{
int p, e, rv;
char *datanm;
if (w->magic != Jukemagic)
panic("waitready: bad magic in Juke (d->private) for %Z", d);
p = d->wren.targ;
if(p < 0 || p >= w->nside) {
fprint(2, "waitready: target %d out of range for %Z\n", p, d);
return 0;
}
if (d->type == Devwren && d->wren.file)
datanm = strdup(d->wren.file);
else {
if (d->wren.sddir)
free(d->wren.sddir);
if (d->type == Devwren)
d->wren.sddir = sdof(d);
if (d->wren.sddir == nil)
panic("waitready: d->wren.sddir not set for %Z", d);
datanm = smprint("%s/data", d->wren.sddir);
}
rv = 0;
for(e=0; e < 100; e++) {
if(e == 10 && chatty)
print("waitready: waiting for %s to exist\n", datanm);
if(access(datanm, AEXIST) >= 0){
rv = 1;
break;
}
delay(200);
}
if(rv == 0)
fprint(2, "waitready: %s for %Z didn't come ready\n", datanm, d);
free(datanm);
return rv;
}
static int
bestdrive(Juke *w, int side)
{
Side *v, *bv[MAXDRIVE];
int i, e, drive;
Timet t, t0;
loop:
/* build table of what platters on what drives */
for(i=0; i<w->ndt; i++)
bv[i] = 0;
v = &w->side[0];
for(i=0; i < w->nside; i++, v++)
if(v->status == Sstart) {
drive = v->drive;
if(drive >= 0 && drive < w->ndt)
bv[drive] = v;
}
/*
* find oldest drive, but must be
* at least THYSTER old.
*/
e = w->side[side].elem;
t0 = toytime() - THYSTER;
t = t0;
drive = -1;
for(i=0; i<w->ndt; i++) {
v = bv[i];
if(v == 0) { /* 2nd priority: empty drive */
if(w->offline[i])
continue;
if(w->drive[i] != devnone) {
drive = i;
t = 0;
}
continue;
}
if(v->elem == e) { /* 1st priority: other side */
drive = -1;
if(v->stime < t0)
drive = i;
break;
}
if(v->stime < t) { /* 3rd priority: by time */
drive = i;
t = v->stime;
}
}
if(drive >= 0) {
v = bv[drive];
if(v) {
qlock(v);
if(v->status != Sstart) {
qunlock(v);
goto loop;
}
if(chatty)
print("\tunload r%ld drive %Z\n",
v-w->side, w->drive[drive]);
if(mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot)) {
qunlock(v);
goto loop;
}
v->status = Sunload;
qunlock(v);
}
}
return drive;
}
Devsize
wormsize(Device *d)
{
Side *v;
Juke *w;
Devsize size;
w = d->private;
if (w->magic != Jukemagic)
fprint(2, "wormsize: bad magic in Juke (d->private) for %Z\n", d);
if(w->isfixedsize && w->fixedsize != 0)
size = w->fixedsize; /* fixed size is now known */
else {
if (w != d->private)
panic("wormsize: w != %Z->private", d);
v = wormunit(d);
if(v == nil)
return 0;
size = v->max;
qunlock(v);
/*
* set fixed size for whole Juke from
* size of first disc examined.
*/
if(w->isfixedsize)
w->fixedsize = size;
}
if(d->type == Devlworm)
return size-1; /* lie: last block is for label */
return size;
}
/*
* return a Devjuke or an mcat (normally of sides) from within d (or nil).
* if it's an mcat, the caller must walk it.
*/
static Device *
devtojuke(Device *d, Device *top)
{
while (d != nil)
switch(d->type) {
default:
fprint(2, "devtojuke: type of device %Z of %Z unknown\n", d, top);
return nil;
case Devjuke:
/* jackpot! d->private is a (Juke *) with nside, &c. */
/* FALL THROUGH */
case Devmcat:
case Devmlev:
case Devmirr:
/* squint hard & call an mlev or a mirr an mcat */
return d;
case Devworm:
case Devlworm:
/*
* d->private is a (Juke *) with nside, etc.,
* but we're not supposed to get here.
*/
if(chatty)
print("devtojuke: (l)worm %Z of %Z encountered\n", d, top);
/* FALL THROUGH */
case Devwren:
return nil;
case Devcw:
d = d->cw.w; /* usually juke */
break;
case Devro:
d = d->ro.parent; /* cw */
break;
case Devfworm:
d = d->fw.fw;
break;
case Devpart:
d = d->part.d;
break;
case Devswab:
d = d->swab.d;
break;
}
return d;
}
static int
devisside(Device *d)
{
return d->type == Devworm || d->type == Devlworm;
}
static Device *
findside(Device *juke, int side, Device *top)
{
int i = 0;
Device *mcat = juke->j.m, *x;
Juke *w = juke->private;
for (x = mcat->cat.first; x != nil; x = x->link) {
if (!devisside(x)) {
fprint(2, "wormsizeside: %Z of %Z of %Z type not (l)worm\n",
x, mcat, top);
return nil;
}
i = x->wren.targ;
if (i < 0 || i >= w->nside)
panic("wormsizeside: side %d in %Z out of range",
i, mcat);
if (i == side)
break;
}
if (x == nil)
return nil;
if (w->side[i].time == 0) {
fprint(2, "wormsizeside: side %d not in jukebox %Z\n", i, juke);
return nil;
}
return x;
}
typedef struct {
int sleft; /* sides still to visit to reach desired side */
int starget; /* side of topdev we want */
Device *topdev;
int sawjuke; /* passed by a jukebox */
int sized; /* flag: asked wormsize for size of starget */
} Visit;
/*
* walk the Device tree from d looking for Devjukes, counting sides.
* the main complication is mcats and the like with Devjukes in them.
* use Devjuke's d->private as Juke* and see sides.
*/
static Off
visitsides(Device *d, Device *parentj, Visit *vp)
{
Off size = 0;
Device *x;
Juke *w;
/*
* find the first juke or mcat.
* d==nil means we couldn't find one; typically harmless, due to a
* mirror of dissimilar devices.
*/
d = devtojuke(d, vp->topdev);
if (d == nil || vp->sleft < 0)
return 0;
if (d->type == Devjuke) { /* jackpot! d->private is a (Juke *) */
vp->sawjuke = 1;
w = d->private;
/*
* if there aren't enough sides in this jukebox to reach
* the desired one, subtract these sides and pass.
*/
if (vp->sleft >= w->nside) {
vp->sleft -= w->nside;
return 0;
}
/* else this is the right juke, paw through mcat of sides */
return visitsides(d->j.m, d, vp);
}
/*
* d will usually be an mcat of sides, but it could be an mcat of
* jukes, for example. in that case, we need to walk the mcat,
* recursing as needed, until we find the right juke, then stop at
* the right side within its mcat of sides, by comparing side
* numbers, not just by counting (to allow for unused slots).
*/
x = d->cat.first;
if (x == nil) {
fprint(2, "visitsides: %Z of %Z: empty mcat\n", d, vp->topdev);
return 0;
}
if (!devisside(x)) {
for (; x != nil && !vp->sized; x = x->link)
size = visitsides(x, parentj, vp);
return size;
}
/* the side we want is in this jukebox, thus this mcat (d) */
if (parentj == nil) {
fprint(2, "visitsides: no parent juke for sides mcat %Z\n", d);
vp->sleft = -1;
return 0;
}
if (d != parentj->j.m)
panic("visitsides: mcat mismatch %Z vs %Z", d, parentj->j.m);
x = findside(parentj, vp->sleft, vp->topdev);
if (x == nil) {
vp->sleft = -1;
return 0;
}
/* we've turned vp->starget into the right Device* */
vp->sleft = 0;
vp->sized = 1;
return wormsize(x);
}
/*
* d must be, or be within, a filesystem config that also contains
* the jukebox that `side' resides on.
* d is normally a Devcw, but could be Devwren, Devide, Devpart, Devfworm,
* etc. if called from chk.c Ctouch code. Note too that the worm part of
* the Devcw might be other than a Devjuke.
*/
Devsize
wormsizeside(Device *d, int side)
{
Devsize size;
Visit visit;
memset(&visit, 0, sizeof visit);
visit.starget = visit.sleft = side;
visit.topdev = d;
size = visitsides(d, nil, &visit);
if (visit.sawjuke && (visit.sleft != 0 || !visit.sized)) {
fprint(2, "wormsizeside: fewer than %d sides in %Z\n", side, d);
return 0;
}
return size;
}
/*
* returns starts (in blocks) of side #side and #(side+1) of dev in *stp.
* dev should be a Devcw.
*/
void
wormsidestarts(Device *dev, int side, Sidestarts *stp)
{
int s;
Devsize dstart;
for (dstart = s = 0; s < side; s++)
dstart += wormsizeside(dev, s);
stp->sstart = dstart;
stp->s1start = dstart + wormsizeside(dev, side);
}
int
wormread(Device *d, Off b, void *c)
{
int r = 0;
long max;
char name[128];
Side *v = wormunit(d);
Juke *w = d->private;
Device *dr;
if (v == nil)
panic("wormread: nil wormunit(%Z)", d);
dr = w->drive[v->drive];
if (dr->wren.fd < 0)
panic("wormread: unopened fd for %Z", d);
max = (d->type == Devlworm? v->max + 1: v->max);
if(b >= max) {
fprint(2, "wormread: block out of range %Z(%lld)\n", d, (Wideoff)b);
r = 0x071;
} else if (pread(dr->wren.fd, c, RBUFSIZE, (vlong)b*RBUFSIZE) != RBUFSIZE) {
fd2path(dr->wren.fd, name, sizeof name);
fprint(2, "wormread: error on %Z(%lld) on %s in %s: %r\n",
d, (Wideoff)b, name, dr->wren.sddir);
cons.nwormre++;
r = 1;
}
qunlock(v);
return r;
}
int
wormwrite(Device *d, Off b, void *c)
{
int r = 0;
long max;
char name[128];
Side *v = wormunit(d);
Juke *w = d->private;
Device *dr;
if (v == nil)
panic("wormwrite: nil wormunit(%Z)", d);
dr = w->drive[v->drive];
if (dr->wren.fd < 0)
panic("wormwrite: unopened fd for %Z", d);
max = (d->type == Devlworm? v->max + 1: v->max);
if(b >= max) {
fprint(2, "wormwrite: block out of range %Z(%lld)\n", d, (Wideoff)b);
r = 0x071;
} else if (pwrite(dr->wren.fd, c, RBUFSIZE, (vlong)b*RBUFSIZE) != RBUFSIZE) {
fd2path(dr->wren.fd, name, sizeof name);
fprint(2, "wormwrwite: error on %Z(%lld) on %s in %s: %r\n",
d, (Wideoff)b, name, dr->wren.sddir);
cons.nwormwe++;
r = 1;
}
qunlock(v);
return r;
}
static int
mmove(Juke *w, int trans, int from, int to, int rot)
{
int s;
uchar cmd[12], buf[4];
static int recur = 0;
memset(cmd, 0, sizeof cmd);
cmd[0] = 0xa5; /* move medium */
cmd[2] = trans>>8;
cmd[3] = trans;
cmd[4] = from>>8;
cmd[5] = from;
cmd[6] = to>>8;
cmd[7] = to;
if(rot)
cmd[10] = 1;
s = scsiio(w->juke, SCSInone, cmd, sizeof cmd, buf, 0); /* mmove */
if(s) {
print("scsio status #%x\n", s);
print("move medium t=%d fr=%d to=%d rot=%d\n",
trans, from, to, rot);
// panic("mmove");
if(recur == 0) {
recur = 1;
print("element from=%d\n", from);
element(w, from);
print("element to=%d\n", to);
element(w, to);
print("element trans=%d\n", trans);
element(w, trans);
recur = 0;
}
return 1;
}
return 0;
}
static void
geometry(Juke *w)
{
int s;
uchar cmd[6], buf[4+20];
memset(cmd, 0, sizeof cmd);
memset(buf, 0, sizeof buf);
cmd[0] = 0x1a; /* mode sense */
cmd[2] = 0x1d; /* element address assignment */
cmd[4] = sizeof buf; /* allocation length */
s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* mode sense elem addrs */
if(s)
panic("geometry #%x", s);
w->mt0 = (buf[4+2]<<8) | buf[4+3];
w->nmt = (buf[4+4]<<8) | buf[4+5];
w->se0 = (buf[4+6]<<8) | buf[4+7];
w->nse = (buf[4+8]<<8) | buf[4+9];
w->ie0 = (buf[4+10]<<8) | buf[4+11];
w->nie = (buf[4+12]<<8) | buf[4+13];
w->dt0 = (buf[4+14]<<8) | buf[4+15];
w->ndt = (buf[4+16]<<8) | buf[4+17];
memset(cmd, 0, 6);
memset(buf, 0, sizeof buf);
cmd[0] = 0x1a; /* mode sense */
cmd[2] = 0x1e; /* transport geometry */
cmd[4] = sizeof buf; /* allocation length */
s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* mode sense geometry */
if(s)
panic("geometry #%x", s);
w->rot = buf[4+2] & 1;
print("\tmt %d %d\n", w->mt0, w->nmt);
print("\tse %d %d\n", w->se0, w->nse);
print("\tie %d %d\n", w->ie0, w->nie);
print("\tdt %d %d\n", w->dt0, w->ndt);
print("\trot %d\n", w->rot);
prflush();
}
/*
* read element e's status from jukebox w, move any disc in drive back to its
* slot, and update and print software status.
*/
static void
element(Juke *w, int e)
{
uchar cmd[12], buf[8+8+88];
int s, t;
memset(cmd, 0, sizeof cmd);
memset(buf, 0, sizeof buf);
cmd[0] = 0xb8; /* read element status */
cmd[2] = e>>8; /* starting element */
cmd[3] = e;
cmd[5] = 1; /* number of elements */
cmd[9] = sizeof buf; /* allocation length */
s = scsiio(w->juke, SCSIread, cmd, sizeof cmd, buf, sizeof buf); /* read elem sts */
if(s) {
fprint(2, "scsiio #%x\n", s);
goto bad;
}
s = (buf[0]<<8) | buf[1];
if(s != e) {
fprint(2, "element = %d\n", s);
goto bad;
}
if(buf[3] != 1) {
fprint(2, "number reported = %d\n", buf[3]);
goto bad;
}
s = (buf[8+8+0]<<8) | buf[8+8+1];
if(s != e) {
fprint(2, "element1 = %d\n", s);
goto bad;
}
switch(buf[8+0]) { /* element type */
default:
fprint(2, "unknown element %d: %d\n", e, buf[8+0]);
goto bad;
case 1: /* transport */
s = e - w->mt0;
if(s < 0 || s >= w->nmt)
goto bad;
if(buf[8+8+2] & 1)
fprint(2, "transport %d full %d.%d\n", s,
(buf[8+8+10]<<8) | buf[8+8+11],
(buf[8+8+9]>>6) & 1);
break;
case 2: /* storage */
s = e - w->se0;
if(s < 0 || s >= w->nse)
goto bad;
w->side[s].status = Sempty;
if(buf[8+8+2] & 1)
w->side[s].status = Sunload;
if(w->rot)
w->side[w->nse+s].status = w->side[s].status;
break;
case 3: /* import/export */
s = e - w->ie0;
if(s < 0 || s >= w->nie)
goto bad;
if(chatty)
print("import/export %d #%.2x %d.%d\n", s,
buf[8+8+2],
(buf[8+8+10]<<8) | buf[8+8+11],
(buf[8+8+9]>>6) & 1);
break;
case 4: /* data transfer */
s = e - w->dt0;
if(s < 0 || s >= w->ndt)
goto bad;
if(chatty)
print("data transfer %d #%.2x %d.%d\n", s,
buf[8+8+2],
(buf[8+8+10]<<8) | buf[8+8+11],
(buf[8+8+9]>>6) & 1);
if(buf[8+8+2] & 1) {
t = ((buf[8+8+10]<<8) | buf[8+8+11]) - w->se0;
if (t < 0 || t >= w->nse || t >= MAXSIDE ||
s >= MAXDRIVE) {
fprint(2, "element: juke %Z lies; claims side %d is in drive %d\n",
w->juke, t, s); /* lying sack of ... */
/*
* at minimum, we've avoided corrupting our
* data structures. if we know that numbers
* like w->nside are valid here, we could use
* them in more stringent tests.
* perhaps should whack the jukebox upside the
* head here to knock some sense into it.
*/
goto bad;
}
if(chatty)
print("r%d in drive %d\n", t, s);
if(mmove(w, w->mt0, w->dt0+s, w->se0+t,(buf[8+8+9]>>6) & 1)){
fprint(2, "mmove initial unload\n");
goto bad;
}
w->side[t].status = Sunload;
if(w->rot)
w->side[w->nse+t].status = Sunload;
}
if(buf[8+8+2] & 4){
fprint(2, "drive w%d has exception #%.2x #%.2x\n", s,
buf[8+8+4], buf[8+8+5]);
goto bad;
}
break;
}
return;
bad:
/* panic("element") */ ;
}
/*
* read all elements' status from jukebox w, move any discs in drives back
* to their slots, and update and print software status.
*/
static void
positions(Juke *w)
{
int i, f;
/* mark empty shelves */
for(i=0; i<w->nse; i++)
element(w, w->se0+i);
for(i=0; i<w->nmt; i++)
element(w, w->mt0+i);
for(i=0; i<w->nie; i++)
element(w, w->ie0+i);
for(i=0; i<w->ndt; i++)
element(w, w->dt0+i);
f = 0;
for(i=0; i<w->nse; i++)
if(w->side[i].status == Sempty) {
if(f) {
print("r%d\n", i-1);
f = 0;
}
} else {
if(!f) {
print("\tshelves r%d-", i);
f = 1;
}
}
if(f)
print("r%d\n", i-1);
}
static void
jinit(Juke *w, Device *d, int o)
{
int p;
Device *dev = d;
switch(d->type) {
default:
fprint(2, "juke platter not (devmcat of) dev(l)worm: %Z\n", d);
panic("jinit: type");
case Devmcat:
/*
* we don't call mcatinit(d) here, so we have to set d->cat.ndev
* ourselves.
*/
for(d=d->cat.first; d; d=d->link)
jinit(w, d, o++);
dev->cat.ndev = o;
break;
case Devlworm:
p = d->wren.targ;
if(p < 0 || p >= w->nside)
panic("jinit partition %Z", d);
w->side[p].ord = o;
/* FALL THROUGH */
case Devworm:
if(d->private) {
fprint(2, "juke platter private pointer set %p\n",
d->private);
panic("jinit: private");
}
d->private = w;
break;
}
}
Side*
wormi(char *arg)
{
int i, j;
Juke *w;
Side *v;
i = number(arg, -1, 10) - 1;
w = jukelist;
if(i < 0 || i >= w->nside) {
fprint(2, "bad unit number %s (%d)\n", arg, i+1);
return 0;
}
j = i;
if(j >= w->nse)
j -= w->nse;
if(j < w->nside) {
v = &w->side[j];
qlock(v);
if(v->status == Sstart) {
if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
qunlock(v);
return 0;
}
v->status = Sunload;
}
qunlock(v);
}
j += w->nse;
if(j < w->nside) {
v = &w->side[j];
qlock(v);
if(v->status == Sstart) {
if(mmove(w, w->mt0, w->dt0+v->drive, v->elem, v->rot)) {
qunlock(v);
return 0;
}
v->status = Sunload;
}
qunlock(v);
}
v = &w->side[i];
qlock(v);
return v;
}
static void
cmd_wormoffline(int argc, char *argv[])
{
int u, i;
Juke *w;
if(argc <= 1) {
print("usage: wormoffline drive\n");
return;
}
u = number(argv[1], -1, 10);
w = jukelist;
if(u < 0 || u >= w->ndrive) {
fprint(2, "bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
return;
}
if(w->offline[u])
print("drive %d already offline\n", u);
w->offline[u] = 1;
for(i=0; i<w->ndrive; i++)
if(w->offline[i] == 0)
return;
print("that would take all drives offline\n");
w->offline[u] = 0;
}
static void
cmd_wormonline(int argc, char *argv[])
{
int u;
Juke *w;
if(argc <= 1) {
print("usage: wormonline drive\n");
return;
}
u = number(argv[1], -1, 10);
w = jukelist;
if(u < 0 || u >= w->ndrive) {
print("bad drive %s (0<=%d<%d)\n", argv[1], u, w->ndrive);
return;
}
if(w->offline[u] == 0)
print("drive %d already online\n", u);
w->offline[u] = 0;
}
void
cmd_wormreset(int, char *[])
{
Juke *w;
for(w=jukelist; w; w=w->link) {
qlock(w);
positions(w);
qunlock(w);
}
}
static void
cmd_wormeject(int argc, char *argv[])
{
Juke *w;
Side *v;
if(argc <= 1) {
print("usage: wormeject unit\n");
return;
}
v = wormi(argv[1]);
if(v == 0)
return;
w = jukelist;
mmove(w, w->mt0, v->elem, w->ie0, 0);
qunlock(v);
}
static void
cmd_wormingest(int argc, char *argv[])
{
Juke *w;
Side *v;
if(argc <= 1) {
print("usage: wormingest unit\n");
return;
}
v = wormi(argv[1]);
if(v == 0)
return;
w = jukelist;
mmove(w, w->mt0, w->ie0, v->elem, 0);
qunlock(v);
}
static void
newside(Side *v, int rot, int elem)
{
qlock(v);
qunlock(v);
// v->name = "shelf";
v->elem = elem;
v->rot = rot;
v->status = Sempty;
v->time = toytime();
}
/*
* query jukebox robotics for geometry;
* argument is the wren dev of the changer.
* result is actually Juke*, but that type is only known in this file.
*/
void *
querychanger(Device *xdev)
{
Juke *w;
Side *v;
int i;
if (xdev == nil)
panic("querychanger: nil Device");
if(xdev->type != Devwren) {
fprint(2, "juke changer not wren %Z\n", xdev);
goto bad;
}
for(w=jukelist; w; w=w->link)
if(xdev == w->juke)
return w;
/*
* allocate a juke structure
* no locking problems.
*/
w = malloc(sizeof(Juke));
w->magic = Jukemagic;
w->isfixedsize = FIXEDSIZE;
w->link = jukelist;
jukelist = w;
if(chatty)
print("alloc juke %Z\n", xdev);
qlock(w);
qunlock(w);
// w->name = "juke";
w->juke = xdev;
w->robotdir = sdof(xdev);
w->robot = openscsi(w->robotdir);
if (w->robot == nil)
panic("can't openscsi(%s): %r", w->robotdir);
newscsi(xdev, w->robot);
geometry(w);
/*
* pick up each side
*/
w->nside = w->nse;
if(w->rot)
w->nside += w->nside;
if(w->nside > MAXSIDE) {
fprint(2, "too many sides: %d max %d\n", w->nside, MAXSIDE);
goto bad;
}
for(i=0; i < w->nse; i++) {
v = &w->side[i];
newside(v, 0, w->se0 + i);
if(w->rot)
newside(v + w->nse, 1, w->se0 + i);
}
positions(w);
w->ndrive = w->ndt;
if(w->ndrive > MAXDRIVE) {
if(chatty)
print("ndrives truncated to %d\n", MAXDRIVE);
w->ndrive = MAXDRIVE;
}
/*
* pick up each drive
*/
for(i=0; i<w->ndrive; i++)
w->drive[i] = devnone;
return w;
bad:
panic("querychanger: %Z", xdev);
return nil;
}
void
jukeinit(Device *d)
{
Juke *w;
Device *xdev;
int i;
static int beenhere = 0;
/* j(w<changer>w<station0>...)(r<platters>) */
if (d == nil)
panic("jukeinit: nil Device");
xdev = d->j.j;
if(xdev == nil || xdev->type != Devmcat) {
fprint(2, "juke union not mcat\n");
goto bad;
}
/*
* pick up the changer device
*/
xdev = xdev->cat.first;
w = querychanger(xdev);
if (!beenhere) {
beenhere = 1;
cmd_install("wormreset",
"-- put drives back where jukebox thinks they belong",
cmd_wormreset);
cmd_install("wormeject", "unit -- shelf to outside",
cmd_wormeject);
cmd_install("wormingest", "unit -- outside to shelf",
cmd_wormingest);
cmd_install("wormoffline", "unit -- disable drive",
cmd_wormoffline);
cmd_install("wormonline", "unit -- enable drive",
cmd_wormonline);
}
/* walk through the worm drives */
i = 0;
while(xdev = xdev->link) {
if(xdev->type != Devwren) {
fprint(2, "drive not devwren: %Z\n", xdev);
goto bad;
}
if(w->drive[i]->type != Devnone &&
xdev != w->drive[i]) {
fprint(2, "double init drive %d %Z %Z\n",
i, w->drive[i], xdev);
goto bad;
}
if(i >= w->ndrive) {
fprint(2, "too many drives %Z\n", xdev);
goto bad;
}
w->drive[i++] = xdev;
}
if(i <= 0) {
fprint(2, "no drives\n");
goto bad;
}
/*
* put w pointer in each platter
*/
d->private = w;
jinit(w, d->j.m, 0);
w->probeok = 1;
return;
bad:
panic("juke init");
}
/*
* called periodically
*/
void
wormprobe(void)
{
int i, drive;
Timet t;
Side *v;
Juke *w;
t = toytime() - TWORM;
for(w=jukelist; w; w=w->link) {
if(w->probeok == 0 || !canqlock(w))
continue;
for(i=0; i<w->nside; i++) {
v = &w->side[i];
if(!canqlock(v))
continue;
if(v->status == Sstart && t > v->time) {
drive = v->drive;
if(chatty)
print("\ttime r%ld drive %Z\n",
v-w->side, w->drive[drive]);
mmove(w, w->mt0, w->dt0+drive, v->elem, v->rot);
v->status = Sunload;
}
qunlock(v);
}
qunlock(w);
}
}