1333 lines
28 KiB
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);
|
|
}
|
|
}
|