Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Now thread-safe.
 *
 * The codeqlock guarantees that once codes != nil, that pointer will never 
 * change nor become invalid.
 *
 * The QLock in the Scsi structure moderates access to the raw device.
 * We should probably export some of the already-locked routines, but
 * there hasn't been a need.
 */

#include <u.h>
#include <libc.h>
#include <disk.h>

enum {
        /* commands */
        Testrdy         = 0x00,
        Reqsense        = 0x03,
        Write10         = 0x2a,
        Writever10      = 0x2e,
        Readtoc         = 0x43,

        /* sense[2] (key) sense codes */
        Sensenone       = 0,
        Sensenotrdy     = 2,
        Sensebadreq     = 5,

        /* sense[12] (asc) sense codes */
        Lunnotrdy       = 0x04,
        Recovnoecc      = 0x17,
        Recovecc        = 0x18,
        Badcdb          = 0x24,
        Newmedium       = 0x28,
        Nomedium        = 0x3a,
};

int scsiverbose;

#define codefile "/sys/lib/scsicodes"

static char *codes;
static QLock codeqlock;

static void
getcodes(void)
{
        Dir *d;
        int n, fd;

        if(codes != nil)
                return;

        qlock(&codeqlock);
        if(codes != nil) {
                qunlock(&codeqlock);
                return;
        }

        if((d = dirstat(codefile)) == nil || (fd = open(codefile, OREAD)) < 0) {
                qunlock(&codeqlock);
                return;
        }

        codes = malloc(1+d->length+1);
        if(codes == nil) {
                close(fd);
                qunlock(&codeqlock);
                free(d);
                return;
        }

        codes[0] = '\n';        /* for searches */
        n = readn(fd, codes+1, d->length);
        close(fd);
        free(d);

        if(n < 0) {
                free(codes);
                codes = nil;
                qunlock(&codeqlock);
                return;
        }
        codes[n] = '\0';
        qunlock(&codeqlock);
}
        
char*
scsierror(int asc, int ascq)
{
        char *p, *q;
        static char search[32];
        static char buf[128];

        getcodes();

        if(codes) {
                snprint(search, sizeof search, "\n%.2ux%.2ux ", asc, ascq);
                if(p = strstr(codes, search)) {
                        p += 6;
                        if((q = strchr(p, '\n')) == nil)
                                q = p+strlen(p);
                        snprint(buf, sizeof buf, "%.*s", (int)(q-p), p);
                        return buf;
                }

                snprint(search, sizeof search, "\n%.2ux00", asc);
                if(p = strstr(codes, search)) {
                        p += 6;
                        if((q = strchr(p, '\n')) == nil)
                                q = p+strlen(p);
                        snprint(buf, sizeof buf, "(ascq #%.2ux) %.*s", ascq, (int)(q-p), p);
                        return buf;
                }
        }

        snprint(buf, sizeof buf, "scsi #%.2ux %.2ux", asc, ascq);
        return buf;
}


static int
_scsicmd(Scsi *s, uchar *cmd, int ccount, void *data, int dcount, int io, int dolock)
{
        uchar resp[16];
        int n;
        long status;

        if(dolock)
                qlock(s);
        if(write(s->rawfd, cmd, ccount) != ccount) {
                werrstr("cmd write: %r");
                if(dolock)
                        qunlock(s);
                return -1;
        }

        switch(io){
        case Sread:
                n = read(s->rawfd, data, dcount);
                /* read toc errors are frequent and not very interesting */
                if(n < 0 && (scsiverbose == 1 ||
                    scsiverbose == 2 && cmd[0] != Readtoc))
                        fprint(2, "dat read: %r: cmd 0x%2.2uX\n", cmd[0]);
                break;
        case Swrite:
                n = write(s->rawfd, data, dcount);
                if(n != dcount && scsiverbose)
                        fprint(2, "dat write: %r: cmd 0x%2.2uX\n", cmd[0]);
                break;
        default:
        case Snone:
                n = write(s->rawfd, resp, 0);
                if(n != 0 && scsiverbose)
                        fprint(2, "none write: %r: cmd 0x%2.2uX\n", cmd[0]);
                break;
        }

        memset(resp, 0, sizeof(resp));
        if(read(s->rawfd, resp, sizeof(resp)) < 0) {
                werrstr("resp read: %r\n");
                if(dolock)
                        qunlock(s);
                return -1;
        }
        if(dolock)
                qunlock(s);

        resp[sizeof(resp)-1] = '\0';
        status = atoi((char*)resp);
        if(status == 0)
                return n;

        werrstr("cmd %2.2uX: status %luX dcount %d n %d", cmd[0], status, dcount, n);
        return -1;
}

int
scsicmd(Scsi *s, uchar *cmd, int ccount, void *data, int dcount, int io)
{
        return _scsicmd(s, cmd, ccount, data, dcount, io, 1);
}

static int
_scsiready(Scsi *s, int dolock)
{
        char err[ERRMAX];
        uchar cmd[6], resp[16];
        int status, i;

        if(dolock)
                qlock(s);
        werrstr("");
        for(i=0; i<3; i++) {
                memset(cmd, 0, sizeof(cmd));
                cmd[0] = Testrdy;       /* unit ready */
                if(write(s->rawfd, cmd, sizeof(cmd)) != sizeof(cmd)) {
                        if(scsiverbose)
                                fprint(2, "ur cmd write: %r\n");
                        werrstr("short unit-ready raw write");
                        continue;
                }
                write(s->rawfd, resp, 0);
                if(read(s->rawfd, resp, sizeof(resp)) < 0) {
                        if(scsiverbose)
                                fprint(2, "ur resp read: %r\n");
                        continue;
                }
                resp[sizeof(resp)-1] = '\0';
                status = atoi((char*)resp);
                if(status == 0 || status == 0x02) {
                        if(dolock)
                                qunlock(s);
                        return 0;
                }
                if(scsiverbose)
                        fprint(2, "target: bad status: %x\n", status);
        }
        rerrstr(err, sizeof err);
        if(err[0] == '\0')
                werrstr("unit did not become ready");
        if(dolock)
                qunlock(s);
        return -1;
}

int
scsiready(Scsi *s)
{
        return _scsiready(s, 1);
}

int
scsi(Scsi *s, uchar *cmd, int ccount, void *v, int dcount, int io)
{
        uchar req[6], sense[255], *data;
        int tries, code, key, n;
        char *p;

        data = v;
        SET(key, code);
        qlock(s);
        for(tries=0; tries<2; tries++) {
                n = _scsicmd(s, cmd, ccount, data, dcount, io, 0);
                if(n >= 0) {
                        qunlock(s);
                        return n;
                }

                /*
                 * request sense
                 */
                memset(req, 0, sizeof(req));
                req[0] = Reqsense;
                req[4] = sizeof(sense);
                memset(sense, 0xFF, sizeof(sense));
                if((n=_scsicmd(s, req, sizeof(req), sense, sizeof(sense), Sread, 0)) < 14)
                        if(scsiverbose)
                                fprint(2, "reqsense scsicmd %d: %r\n", n);
        
                if(_scsiready(s, 0) < 0)
                        if(scsiverbose)
                                fprint(2, "unit not ready\n");
        
                key = sense[2] & 0xf;
                code = sense[12];                       /* asc */
                if(code == Recovnoecc || code == Recovecc) { /* recovered errors */
                        qunlock(s);
                        return dcount;
                }

                /* retry various odd cases */
                if(code == Newmedium && cmd[0] == Readtoc) {
                        /* read toc and media changed */
                        s->nchange++;
                        s->changetime = time(0);
                } else if((cmd[0] == Write10 || cmd[0] == Writever10) &&
                    key == Sensenotrdy &&
                    code == Lunnotrdy && sense[13] == 0x08) {
                        /* long write in progress, per mmc-6 */
                        tries = 0;
                        sleep(1);
                } else if(cmd[0] == Write10 || cmd[0] == Writever10)
                        break;          /* don't retry worm writes */
        }

        /* drive not ready, or medium not present */
        if(cmd[0] == Readtoc && key == Sensenotrdy &&
            (code == Nomedium || code == Lunnotrdy)) {
                s->changetime = 0;
                qunlock(s);
                return -1;
        }
        qunlock(s);

        if(cmd[0] == Readtoc && key == Sensebadreq && code == Badcdb)
                return -1;                      /* blank media */

        p = scsierror(code, sense[13]);

        werrstr("cmd #%.2ux: %s", cmd[0], p);

        if(scsiverbose)
                fprint(2, "scsi cmd #%.2ux: %.2ux %.2ux %.2ux: %s\n",
                        cmd[0], key, code, sense[13], p);

//      if(key == Sensenone)
//              return dcount;
        return -1;
}

Scsi*
openscsi(char *dev)
{
        Scsi *s;
        int rawfd, ctlfd, l, n;
        char *name, *p, buf[512];

        l = strlen(dev)+1+3+1;
        name = malloc(l);
        if(name == nil)
                return nil;

        snprint(name, l, "%s/raw", dev);
        if((rawfd = open(name, ORDWR)) < 0) {
                free(name);
                return nil;
        }

        snprint(name, l, "%s/ctl", dev);
        if((ctlfd = open(name, ORDWR)) < 0) {
        Error:
                free(name);
                close(rawfd);
                return nil;
        }

        n = readn(ctlfd, buf, sizeof buf);
        close(ctlfd);
        if(n <= 0) {
                if(n == 0)
                        werrstr("eof on %s", name);
                goto Error;
        }

        if(strncmp(buf, "inquiry ", 8) != 0 || (p = strchr(buf, '\n')) == nil) {
                werrstr("inquiry mal-formatted in %s", name);
                goto Error;
        }
        *p = '\0';
        free(name);
        name = nil;

        if((p = strdup(buf+8)) == nil)
                goto Error;

        s = mallocz(sizeof(*s), 1);
        if(s == nil) {
        Error1:
                free(p);
                goto Error;
        }

        s->rawfd = rawfd;
        s->inquire = p;
        s->changetime = time(0);
        
        if(scsiready(s) < 0)
                goto Error1;

        return s;
}

void
closescsi(Scsi *s)
{
        close(s->rawfd);
        free(s->inquire);
        free(s);
}