/*
* 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)
               fprint(2, "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)
                       fprint(2, "\tload   r%zd 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)
                       fprint(2, "\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){
               fprint(2, "\tworm %Z: drive %Z (juke drive %d)\n",
                       d, w->drive[v->drive], v->drive);
               fprint(2, "\t\t%,ld %ld-byte sectors, ", v->nblock, v->block);
               fprint(2, "%,ld %d-byte blocks\n", v->max, RBUFSIZE);
               fprint(2, "\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)
                       fprint(2, "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)
                               fprint(2, "\tunload r%zd 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)
                               fprint(2, "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) {
               fprint(2, "scsio status #%x\n", s);
               fprint(2, "move medium t=%d fr=%d to=%d rot=%d\n",
                       trans, from, to, rot);
//              panic("mmove");
               if(recur == 0) {
                       recur = 1;
                       fprint(2, "element from=%d\n", from);
                       element(w, from);
                       fprint(2, "element to=%d\n", to);
                       element(w, to);
                       fprint(2, "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;

       fprint(2, "\tmt %d %d\n", w->mt0, w->nmt);
       fprint(2, "\tse %d %d\n", w->se0, w->nse);
       fprint(2, "\tie %d %d\n", w->ie0, w->nie);
       fprint(2, "\tdt %d %d\n", w->dt0, w->ndt);
       fprint(2, "\trot %d\n", w->rot);
}

/*
* 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)
                       fprint(2, "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)
                       fprint(2, "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)
                               fprint(2, "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 = ialloc(sizeof(Juke), 0);
       w->magic = Jukemagic;
       w->isfixedsize = FIXEDSIZE;
       w->link = jukelist;
       jukelist = w;

       if(chatty)
               fprint(2, "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)
                       fprint(2, "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)
                                       fprint(2, "\ttime   r%zd 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);
       }
}