424 lines
8.7 KiB
C
424 lines
8.7 KiB
C
/*
|
|
* interface to scsi devices via scsi(2) to sd(3),
|
|
* which does not implement LUNs.
|
|
*/
|
|
#include "all.h"
|
|
#include "io.h"
|
|
|
|
enum {
|
|
Ninquiry = 255,
|
|
Nsense = 255,
|
|
|
|
CMDtest = 0x00,
|
|
CMDreqsense = 0x03,
|
|
CMDread6 = 0x08,
|
|
CMDwrite6 = 0x0A,
|
|
CMDinquiry = 0x12,
|
|
CMDstart = 0x1B,
|
|
CMDread10 = 0x28,
|
|
CMDwrite10 = 0x2A,
|
|
};
|
|
|
|
typedef struct {
|
|
Target target[NTarget];
|
|
} Ctlr;
|
|
static Ctlr scsictlr[MaxScsi];
|
|
|
|
extern int scsiverbose;
|
|
|
|
void
|
|
scsiinit(void)
|
|
{
|
|
Ctlr *ctlr;
|
|
int ctlrno, targetno;
|
|
Target *tp;
|
|
|
|
scsiverbose = 1;
|
|
for(ctlrno = 0; ctlrno < MaxScsi; ctlrno++){
|
|
ctlr = &scsictlr[ctlrno];
|
|
memset(ctlr, 0, sizeof(Ctlr));
|
|
for(targetno = 0; targetno < NTarget; targetno++){
|
|
tp = &ctlr->target[targetno];
|
|
|
|
qlock(tp);
|
|
qunlock(tp);
|
|
sprint(tp->id, "scsictlr#%d.%d", ctlrno, targetno);
|
|
|
|
tp->ctlrno = ctlrno;
|
|
tp->targetno = targetno;
|
|
tp->inquiry = malloc(Ninquiry);
|
|
tp->sense = malloc(Nsense);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uchar lastcmd[16];
|
|
static int lastcmdsz;
|
|
|
|
static int
|
|
sense2stcode(uchar *sense)
|
|
{
|
|
switch(sense[2] & 0x0F){
|
|
case 6: /* unit attention */
|
|
/*
|
|
* 0x28 - not ready to ready transition,
|
|
* medium may have changed.
|
|
* 0x29 - power on, RESET or BUS DEVICE RESET occurred.
|
|
*/
|
|
if(sense[12] != 0x28 && sense[12] != 0x29)
|
|
return STcheck;
|
|
/*FALLTHROUGH*/
|
|
case 0: /* no sense */
|
|
case 1: /* recovered error */
|
|
return STok;
|
|
case 8: /* blank data */
|
|
return STblank;
|
|
case 2: /* not ready */
|
|
if(sense[12] == 0x3A) /* medium not present */
|
|
return STcheck;
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
/*
|
|
* If unit is becoming ready, rather than not ready,
|
|
* then wait a little then poke it again; should this
|
|
* be here or in the caller?
|
|
*/
|
|
if((sense[12] == 0x04 && sense[13] == 0x01)) {
|
|
// delay(500);
|
|
// scsitest(tp, lun);
|
|
fprint(2, "sense2stcode: unit becoming ready\n");
|
|
return STcheck; /* not exactly right */
|
|
}
|
|
return STcheck;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* issue the SCSI command via scsi(2). lun must already be in cmd[1].
|
|
*/
|
|
static int
|
|
doscsi(Target* tp, int rw, uchar* cmd, int cbytes, void* data, int* dbytes)
|
|
{
|
|
int lun, db = 0;
|
|
uchar reqcmd[6], reqdata[Nsense], dummy[1];
|
|
Scsi *sc;
|
|
|
|
sc = tp->sc;
|
|
if (sc == nil)
|
|
panic("doscsi: nil tp->sc");
|
|
lun = cmd[1] >> 5; /* save lun in case we need it for reqsense */
|
|
|
|
/* cope with zero arguments */
|
|
if (dbytes != nil)
|
|
db = *dbytes;
|
|
if (data == nil)
|
|
data = dummy;
|
|
|
|
if (scsi(sc, cmd, cbytes, data, db, rw) >= 0)
|
|
return STok;
|
|
|
|
/* cmd failed, get whatever sense data we can */
|
|
memset(reqcmd, 0, sizeof reqcmd);
|
|
reqcmd[0] = CMDreqsense;
|
|
reqcmd[1] = lun<<5;
|
|
reqcmd[4] = Nsense;
|
|
memset(reqdata, 0, sizeof reqdata);
|
|
if (scsicmd(sc, reqcmd, sizeof reqcmd, reqdata, sizeof reqdata,
|
|
Sread) < 0)
|
|
return STharderr;
|
|
|
|
/* translate sense data to ST* codes */
|
|
return sense2stcode(reqdata);
|
|
}
|
|
|
|
static int
|
|
scsiexec(Target* tp, int rw, uchar* cmd, int cbytes, void* data, int* dbytes)
|
|
{
|
|
int s;
|
|
|
|
/*
|
|
* issue the SCSI command. lun must already be in cmd[1].
|
|
*/
|
|
s = doscsi(tp, rw, cmd, cbytes, data, dbytes);
|
|
switch(s){
|
|
|
|
case STcheck:
|
|
memmove(lastcmd, cmd, cbytes);
|
|
lastcmdsz = cbytes;
|
|
/*FALLTHROUGH*/
|
|
|
|
default:
|
|
/*
|
|
* It's more complicated than this. There are conditions which
|
|
* are 'ok' but for which the returned status code is not 'STok'.
|
|
* Also, not all conditions require a reqsense, there may be a
|
|
* need to do a reqsense here when necessary and making it
|
|
* available to the caller somehow.
|
|
*
|
|
* Later.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static int
|
|
scsitest(Target* tp, char lun)
|
|
{
|
|
uchar cmd[6];
|
|
|
|
memset(cmd, 0, sizeof cmd);
|
|
cmd[0] = CMDtest;
|
|
cmd[1] = lun<<5;
|
|
return scsiexec(tp, SCSIread, cmd, sizeof cmd, 0, 0);
|
|
|
|
}
|
|
|
|
static int
|
|
scsistart(Target* tp, char lun, int start)
|
|
{
|
|
uchar cmd[6];
|
|
|
|
memset(cmd, 0, sizeof cmd);
|
|
cmd[0] = CMDstart;
|
|
cmd[1] = lun<<5;
|
|
if(start)
|
|
cmd[4] = 1;
|
|
return scsiexec(tp, SCSIread, cmd, sizeof cmd, 0, 0);
|
|
}
|
|
|
|
static int
|
|
scsiinquiry(Target* tp, char lun, int* nbytes)
|
|
{
|
|
uchar cmd[6];
|
|
|
|
memset(cmd, 0, sizeof cmd);
|
|
cmd[0] = CMDinquiry;
|
|
cmd[1] = lun<<5;
|
|
*nbytes = Ninquiry;
|
|
cmd[4] = *nbytes;
|
|
return scsiexec(tp, SCSIread, cmd, sizeof cmd, tp->inquiry, nbytes);
|
|
}
|
|
|
|
static char *key[] =
|
|
{
|
|
"no sense",
|
|
"recovered error",
|
|
"not ready",
|
|
"medium error",
|
|
"hardware error",
|
|
"illegal request",
|
|
"unit attention",
|
|
"data protect",
|
|
"blank check",
|
|
"vendor specific",
|
|
"copy aborted",
|
|
"aborted command",
|
|
"equal",
|
|
"volume overflow",
|
|
"miscompare",
|
|
"reserved"
|
|
};
|
|
|
|
static int
|
|
scsireqsense(Target* tp, char lun, int* nbytes, int quiet)
|
|
{
|
|
char *s;
|
|
int n, status, try;
|
|
uchar cmd[6], *sense;
|
|
|
|
sense = tp->sense;
|
|
for(try = 0; try < 20; try++) {
|
|
memset(cmd, 0, sizeof cmd);
|
|
cmd[0] = CMDreqsense;
|
|
cmd[1] = lun<<5;
|
|
cmd[4] = Ninquiry;
|
|
memset(sense, 0, Ninquiry);
|
|
|
|
*nbytes = Ninquiry;
|
|
status = scsiexec(tp, SCSIread, cmd, sizeof cmd, sense, nbytes);
|
|
if(status != STok)
|
|
return status;
|
|
*nbytes = sense[0x07]+8;
|
|
|
|
switch(sense[2] & 0x0F){
|
|
case 6: /* unit attention */
|
|
/*
|
|
* 0x28 - not ready to ready transition,
|
|
* medium may have changed.
|
|
* 0x29 - power on, RESET or BUS DEVICE RESET occurred.
|
|
*/
|
|
if(sense[12] != 0x28 && sense[12] != 0x29)
|
|
goto buggery;
|
|
/*FALLTHROUGH*/
|
|
case 0: /* no sense */
|
|
case 1: /* recovered error */
|
|
return STok;
|
|
case 8: /* blank data */
|
|
return STblank;
|
|
case 2: /* not ready */
|
|
if(sense[12] == 0x3A) /* medium not present */
|
|
goto buggery;
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
/*
|
|
* If unit is becoming ready, rather than not ready,
|
|
* then wait a little then poke it again; should this
|
|
* be here or in the caller?
|
|
*/
|
|
if((sense[12] == 0x04 && sense[13] == 0x01)){
|
|
delay(500);
|
|
scsitest(tp, lun);
|
|
break;
|
|
}
|
|
goto buggery;
|
|
}
|
|
}
|
|
|
|
buggery:
|
|
if(quiet == 0){
|
|
s = key[sense[2]&0x0F];
|
|
print("%s: reqsense: '%s' code #%2.2ux #%2.2ux\n",
|
|
tp->id, s, sense[12], sense[13]);
|
|
print("%s: byte 2: #%2.2ux, bytes 15-17: #%2.2ux #%2.2ux #%2.2ux\n",
|
|
tp->id, sense[2], sense[15], sense[16], sense[17]);
|
|
print("lastcmd (%d): ", lastcmdsz);
|
|
for(n = 0; n < lastcmdsz; n++)
|
|
print(" #%2.2ux", lastcmd[n]);
|
|
print("\n");
|
|
}
|
|
|
|
return STcheck;
|
|
}
|
|
|
|
static Target*
|
|
scsitarget(Device* d)
|
|
{
|
|
int ctlrno, targetno;
|
|
|
|
ctlrno = d->wren.ctrl;
|
|
if(ctlrno < 0 || ctlrno >= MaxScsi /* || scsictlr[ctlrno].io == nil */)
|
|
return 0;
|
|
targetno = d->wren.targ;
|
|
if(targetno < 0 || targetno >= NTarget)
|
|
return 0;
|
|
return &scsictlr[ctlrno].target[targetno];
|
|
}
|
|
|
|
static void
|
|
scsiprobe(Device* d)
|
|
{
|
|
Target *tp;
|
|
int nbytes, s;
|
|
uchar *sense;
|
|
int acount;
|
|
|
|
if((tp = scsitarget(d)) == 0)
|
|
panic("scsiprobe: device = %Z", d);
|
|
|
|
acount = 0;
|
|
again:
|
|
s = scsitest(tp, d->wren.lun);
|
|
if(s < STok){
|
|
print("%s: test, status %d\n", tp->id, s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Determine if the drive exists and is not ready or
|
|
* is simply not responding.
|
|
* If the status is OK but the drive came back with a 'power on' or
|
|
* 'reset' status, try the test again to make sure the drive is really
|
|
* ready.
|
|
* If the drive is not ready and requires intervention, try to spin it
|
|
* up.
|
|
*/
|
|
s = scsireqsense(tp, d->wren.lun, &nbytes, acount);
|
|
sense = tp->sense;
|
|
switch(s){
|
|
case STok:
|
|
if ((sense[2] & 0x0F) == 0x06 &&
|
|
(sense[12] == 0x28 || sense[12] == 0x29))
|
|
if(acount == 0){
|
|
acount = 1;
|
|
goto again;
|
|
}
|
|
break;
|
|
case STcheck:
|
|
if((sense[2] & 0x0F) == 0x02){
|
|
if(sense[12] == 0x3A)
|
|
break;
|
|
if(sense[12] == 0x04 && sense[13] == 0x02){
|
|
print("%s: starting...\n", tp->id);
|
|
if(scsistart(tp, d->wren.lun, 1) == STok)
|
|
break;
|
|
s = scsireqsense(tp, d->wren.lun, &nbytes, 0);
|
|
}
|
|
}
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
print("%s: unavailable, status %d\n", tp->id, s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Inquire to find out what the device is.
|
|
* Hardware drivers may need some of the info.
|
|
*/
|
|
s = scsiinquiry(tp, d->wren.lun, &nbytes);
|
|
if(s != STok) {
|
|
print("%s: inquiry failed, status %d\n", tp->id, s);
|
|
return;
|
|
}
|
|
print("%s: %s\n", tp->id, (char*)tp->inquiry+8);
|
|
tp->ok = 1;
|
|
}
|
|
|
|
int
|
|
scsiio(Device* d, int rw, uchar* cmd, int cbytes, void* data, int dbytes)
|
|
{
|
|
Target *tp;
|
|
int e, nbytes, s;
|
|
|
|
if((tp = scsitarget(d)) == 0)
|
|
panic("scsiio: device = %Z", d);
|
|
|
|
qlock(tp);
|
|
if(tp->ok == 0)
|
|
scsiprobe(d);
|
|
qunlock(tp);
|
|
|
|
s = STinit;
|
|
for(e = 0; e < 10; e++){
|
|
for(;;){
|
|
nbytes = dbytes;
|
|
s = scsiexec(tp, rw, cmd, cbytes, data, &nbytes);
|
|
if(s == STok)
|
|
break;
|
|
s = scsireqsense(tp, d->wren.lun, &nbytes, 0);
|
|
if(s == STblank && rw == SCSIread) {
|
|
memset(data, 0, dbytes);
|
|
return STok;
|
|
}
|
|
if(s != STok)
|
|
break;
|
|
}
|
|
if(s == STok)
|
|
break;
|
|
}
|
|
if(e)
|
|
print("%s: retry %d cmd #%x\n", tp->id, e, cmd[0]);
|
|
return s;
|
|
}
|
|
|
|
void
|
|
newscsi(Device *d, Scsi *sc)
|
|
{
|
|
Target *tp;
|
|
|
|
if((tp = scsitarget(d)) == nil)
|
|
panic("newscsi: device = %Z", d);
|
|
tp->sc = sc; /* connect Target to Scsi */
|
|
}
|