Coherent4.2.10/conf/haisd/src/haisd.c
/* (-lgl
* Coherent 386 release 4.2
* Copyright (c) 1982, 1994 by Mark Williams Company.
* All rights reserved. May not be copied without permission.
* For copying permission and licensing info, write licensing@mwc.com
-lgl) */
/***********************************************************************
* Module: haisd.c
*
* Unix device driver functions for accessing SCSI hard drives as
* block devices. Conforms to Mark Williams Coherent definition of
* the Unix Device Driver interface.
*
* Copyright (c) 1993, Christopher Sean Hilton. All rights reserved.
*
* Last Modified: Wed Jul 13 16:49:30 1994 by [chris]
*
* This code assumes BSIZE == (1 << 9).
*
* $Id$
*
* $Log$
*/
#include <stddef.h>
#include <sys/coherent.h>
#include <sys/buf.h>
#include <sys/cmn_err.h>
#include <sys/fdisk.h>
#include <sys/sched.h>
#include <sys/stat.h>
#include <sys/haiscsi.h>
#include <errno.h>
#define INQBUFSZ 64
#define RETRYLIMIT 7
#define LOCAL static
extern unsigned lbolt; /* System timer */
extern size_t haisd_maxreq; /* Max requests for look ahead */
typedef enum {
SD_INIT = -1, /* Driver hasn't been initialized */
SD_IDLE = 0, /* Driver is Idle */
SD_STARTSCMD, /* Start SCSI command */
SD_SCMDWAIT, /* Wait for SCSI command to finish */
SD_SCMDFINISH, /* SCSI Command has finished */
SD_RECOVER, /* Recover from failed command */
SD_RSTARTSCMD, /* Start a command for recovery */
SD_RSCMDWAIT, /* Wait for a recovery command */
SD_RSCMDFINISH /* Finish up a recovery command */
} sdstate_t;
typedef struct partlim_s *partlim_p;
typedef struct partlim_s {
unsigned long p_base; /* base of the partition (blocks) */
unsigned long p_size; /* size of the partition (blocks) */
} partlim_t;
#if __ALLOW_STATS
#define HDGETPERF (HDIOC|13) /* Get performance stats */
#define HDCLRPERF (HDIOC|14) /* Clear Performance stats */
#define HDGETPERFCLR (HDIOC|15) /* Get perf stats and clear */
typedef struct hdstats_s {
unsigned hd_reqcnt; /* Number of request */
unsigned hd_blkcnt; /* Number of blocks read */
unsigned hd_time; /* Time to complete these request */
unsigned hd_errcnt; /* Number of errors generated */
} hdstats_t;
#endif
typedef struct sdctrl_s *sdctrl_p;
typedef struct sdctrl_s {
sdstate_t c_state; /* Driver state */
haft_t * c_haft; /* Host adapter function table */
buf_t *c_actf, /* First buffer in driver work queue */
*c_actl; /* Last buffer in driver work queue */
int c_tries; /* Number of tries on this command */
int c_lastclose; /* Last close on this device */
int c_rmb; /* Removable media flag */
daddr_t c_blkno; /* Block number of last I/O op */
size_t c_blkcnt; /* Count of blocks on last I/O op */
size_t c_reqcnt; /* Number of requests served by this buffer */
unsigned c_starttime; /* Start time on current request */
size_t c_sglistsize; /* Scatter/gather list size */
haisgsegm_t c_sglist[16]; /* Scatter/gather list elements */
partlim_t c_plim[5]; /* Partition table entries */
srb_t c_srb; /* SCSI request block for all commands */
#if __ALLOW_STATS
hdstats_t c_stats; /* Statistics */
#endif
} sdctrl_t;
static sdctrl_p sddevs[MAXDEVS] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
/*
* Make a table of all the command that we will use. This is more
* self documenting and allows us to have a last operation string
* so we can print out better error messages when things don't work.
*/
typedef struct scmd_s *scmd_p;
typedef struct scmd_s {
unsigned char sc_opcode; /* Opcode */
char *sc_description; /* String description */
} scmd_t;
scmd_t TEST_UNIT_READY = { 0x00, "Test unit ready" },
REZERO_UNIT = { 0x01, "Rezero unit" },
REQUEST_SENSE = { 0x03, "Request Sense" },
INQUIRY = { 0x12, "Inquiry" },
MODE_SENSE = { 0x1a, "Mode Sense" },
READ_CAPACITY = { 0x25, "Read Capacity" },
G1READ = { 0x28, "Read" },
G1WRITE = { 0x2a, "Write" },
G1SEEK = { 0x2b, "Seek" };
static char *lastscsicmd;
#define set_opcode(o, cmd) \
{ \
lastscsicmd = (cmd).sc_description; \
o = (cmd).sc_opcode; \
}
/*
* And now a table of error strings for printing out sense conditions.
*/
static char *sensestr[] = {
"False check condition (Firmware fault)",
"Recovered error",
"Device not ready error",
"Medium error",
"Hardware error",
"Illegal request condition (Driver/Firmware fault)",
"Unit attention condition",
"Data protect error",
"Blank check error",
"Vendor unique error",
"Copy aborted ",
"Aborted command",
"Equal",
"Volume overflow",
"Miscompare",
"Reserved"
};
/*
* Function prototypes.
*/
LOCAL int sdload __PROTO((haft_t *, register int));
LOCAL void sdunload __PROTO((register int));
LOCAL int sdopen __PROTO((dev_t, ...));
LOCAL void sdclose __PROTO((dev_t));
LOCAL void sdblock __PROTO((register buf_t *));
LOCAL void sdread __PROTO((dev_t, IO *));
LOCAL void sdwrite __PROTO((dev_t, IO *));
LOCAL void sdioctl __PROTO((dev_t, int, char *));
LOCAL void sdmachine __PROTO((register sdctrl_p));
/*
* Kernel functions
*/
int busyWait __PROTO((int (*)(void), unsigned));
int busyWait2 __PROTO((int (*)(void), unsigned));
int fdisk __PROTO((dev_t, struct fdisk_s []));
unsigned kucopy __PROTO((void *, void *, unsigned));
unsigned ukcopy __PROTO((void *, void *, unsigned));
static dca_t dca = {
sdopen, /* Open */
sdclose, /* Close */
sdblock, /* Block */
sdread, /* Read */
sdwrite, /* Write */
sdioctl, /* Ioctl */
sdload, /* Load */
sdunload, /* Unload */
NULL /* Poll */
};
dca_p sddca = &dca;
#define partindex(d) ((((d) & (SPECIAL | PARTMASK)) == SPECIAL) \
? 0 : ((d) & PARTMASK) + 1)
#define blkdev_base(p, d) (p[partindex(d)]. p_base)
#define blkdev_size(p, d) (p[partindex(d)]. p_size)
#define set_state(ctrl_p, newstate) ((ctrl_p)->c_state = (newstate))
#define DEBUG_QTRACE 0
#if DEBUG_QTRACE
#define qtrace(x, y, c) _chirp((c), 2 * (80 * (y) + (x)))
#else
#define qtrace(x, y, c)
#endif
/***********************************************************************
* srbpending()
*
* Repeatedly check the status in sp_srb for pending. Return 0 if
* still pending, one if not. This function is intended to be used
* with busyWait. Use as follows:
*
* set p_srb to point to your srb.
* set up your srb and start the command.
* call busyWait with the timeout that you would like and srbpending.
*/
static srb_p p_srb = NULL;
#if __USE_PROTO__
LOCAL int srbpending(void)
#else
LOCAL int
srbpending()
#endif
{
return (p_srb->status != ST_PENDING);
} /* srbpending() */
/***********************************************************************
* timedscsi()
*
* For lack of a better name... This does the command within an srb
* or times out within the given number of clock ticks. This does
* not use sleep so it's safe to use anywhere however it does
* use busyWait so it's probably best left for short commands or the
* load/init routine.
*/
#if __USE_PROTO__
LOCAL int timedscsi(haft_t * haft, register srb_p r, unsigned clockticks)
#else
LOCAL int timedscsi(haft, r, clockticks)
haft_t * haft;
srb_p r;
unsigned clockticks;
#endif
{
int rval;
int s;
s = sphi();
p_srb = r;
if (!startscsi(haft, r)) {
cmn_err(CE_WARN,
"(%d,0x%x) haisd: could not start scsi command",
major(r->dev), minor(r->dev));
rval = -1;
} else
rval = busyWait(srbpending, clockticks);
p_srb = NULL;
spl(s);
return rval;
} /* timedscsi() */
#if __USE_PROTO__
LOCAL void timedreqsense(haft_t * haft, register srb_p r, unsigned clockticks)
#else
LOCAL void
timedreqsense(haft, r, clockticks)
haft_t * haft;
srb_p r;
unsigned clockticks;
#endif
{
r->buf. space = KRNL_ADDR | WATCH_REQACK;
r->buf. ba_virt = (caddr_t) r->sensebuf;
r->buf. size = sizeof(r->sensebuf);
r->cleanup = NULL;
memset(&(r->cdb), 0, sizeof(cdb_t));
set_opcode(r->cdb. g0. opcode, REQUEST_SENSE);
r->cdb. g0. xfr_len = sizeof(r->sensebuf);
timedscsi(haft, r, clockticks);
} /* timedreqsense() */
#if __ALLOW_STATS
/***********************************************************************
* sdclrstat()
*
* Clear out the statistics buffer.
*/
#if __USE_PROTO__
LOCAL void sdclrstat(hdstats_t *st)
#else
LOCAL void
sdclrstat(st)
hdstats_t *st;
#endif
{
int s;
s = sphi();
if (st)
memset(st, 0, sizeof(hdstats_t));
spl(s);
}
#endif
/***********************************************************************
* sdload()
*
* Start up a DASD device at (id).
*
* 1) Make sure that it's a disk drive and that we can support it.
* 2) Get its size and blocksize to make sure that we can use it.
* 3) Set up a control structure for it.
*/
#if __USE_PROTO__
LOCAL int sdload(haft_t * haft, register int id)
#else
LOCAL int
sdload(haft, id)
haft_t * haft;
register int id;
#endif
{
register sdctrl_p c;
register srb_p r;
int tries;
char inqbuf[INQBUFSZ];
unsigned long diskcap[2];
int i;
#if HDB
cmn_err (CE_CONT, "haisd loading\n");
#endif
if (!haft) {
cmn_err(CE_WARN,
"haisd: Empty host adapter function table - load failed.");
return 0;
}
c = kalloc(sizeof(sdctrl_t));
if (!c) {
cmn_err(CE_WARN, "out of memory in sdload(): ");
return 0;
}
memset(c, 0, sizeof(sdctrl_t));
c->c_state = SD_INIT;
c->c_haft = haft;
/* Request Sense to clear reset condition. */
r = &(c->c_srb);
r->dev = makedev(SCSIMAJOR, SPECIAL | (r->target << 4));
r->target = id;
r->lun = 0;
r->timeout = 0;
r->cleanup = NULL;
r->xferdir = DMAREAD;
timedreqsense(c->c_haft, r, 300);
if (r->status != ST_GOOD) {
cmn_err(CE_WARN, "Request sense failed: status (0x%x)\n", r->status);
kfree(c);
return 0;
}
#if HDB
cmn_err (CE_CONT, "request sense succeeded\n");
#endif
/* Inquiry to make sure that this is a disk drive */
r->buf.space = KRNL_ADDR | WATCH_REQACK;
r->buf.ba_virt= (caddr_t) inqbuf;
r->buf.size = sizeof(inqbuf);
memset(&(r->cdb), 0, sizeof(cdb_t));
set_opcode(r->cdb. g0. opcode, INQUIRY);
r->cdb.g0.xfr_len = sizeof(inqbuf);
timedscsi(c->c_haft, r, 300);
if (r->status != ST_GOOD) {
cmn_err(CE_WARN, "Inquiry failed status: (0x%x)\n", r->status);
kfree(c);
return 0;
}
#if HDB
cmn_err (CE_CONT, "inquiry succeeded\n");
#endif
if (inqbuf[0] != 0) {
cmn_err(CE_WARN,
"Device type byte: (0x%x) - not direct access device\n", inqbuf[0]);
kfree(c);
return 0;
}
#if HDB
cmn_err (CE_CONT, "DASD detected\n");
#endif
/* Get Capacity to set up the drive for use */
for (tries = 3; tries > 0; --tries) {
r->buf.space = KRNL_ADDR | WATCH_REQACK;
r->buf.ba_virt = (caddr_t) diskcap;
r->buf.size = sizeof(diskcap);
diskcap[0] = diskcap[1] = 0;
memset(&r->cdb, 0, sizeof(cdb_t));
set_opcode(r->cdb. g1. opcode, READ_CAPACITY);
timedscsi(c->c_haft, r, 500);
if (r->status == ST_GOOD)
break;
else if (r->status == ST_CHKCOND) {
timedreqsense(c->c_haft, r, 300);
}
else {
resetdevice(c->c_haft, r->target);
busyWait(NULL, 500);
}
}
if (r->status != ST_GOOD) {
cmn_err(CE_WARN, "Get Capacity Failed: 0x%x\n", r->status);
kfree(c);
return 0;
}
#if HDB
cmn_err (CE_CONT, "got capacity\n");
#endif
flip(diskcap[0]);
flip(diskcap[1]);
if (diskcap[1] != BSIZE) {
cmn_err(CE_WARN,
"Invalid Block Size %ld Reformat with %d Bytes/Block\n",
diskcap[1],
BSIZE);
kfree(c);
return 0;
}
inqbuf[36] = '\0';
/* Suppress weird control characters in inqbuf. */
for (i = 8; i < 36; i++)
if (inqbuf [i] < 0x20)
inqbuf [i] = ' ';
cmn_err(CE_CONT,
"%d: %s %d MB\n",
id,
(inqbuf + 8),
(diskcap[0] + bit(10)) >> 11);
c->c_state = SD_IDLE;
c->c_plim[0].p_base = 0;
c->c_plim[0].p_size = diskcap[0];
c->c_actf = c->c_actl = NULL;
c->c_rmb = ((inqbuf[1] & 0x80) == 0x80);
c->c_sglistsize = sizeof(c->c_sglist) / sizeof(haisgsegm_t);
sddevs[id] = c;
sddevs[id]->c_state = SD_IDLE;
return 1;
}
/***********************************************************************
* sdunload()
*
* Unload routine. Right now unused so ifdefed out.
*/
#if __USE_PROTO__
LOCAL void sdunload(register int id)
#else
LOCAL void
sdunload(id)
register int id;
#endif
{
if (sddevs[id]) {
kfree(sddevs[id]);
sddevs[id] = NULL;
}
} /* sdunload() */
/***********************************************************************
* loadptable()
*
* Read the master boot record from the Fixed disk and set the block
* limits on the individual partition devices. Wouldn't it be nice if
* there were more than four partition slots available?!
*/
#if __USE_PROTO__
LOCAL int loadptable(register dev_t dev)
#else
LOCAL int
loadptable(dev)
register dev_t dev;
#endif
{
struct fdisk_s fp[4];
register sdctrl_p c;
register int i;
if (!partindex(dev))
return 0;
if (fdisk(makedev(major(dev), (minor(dev) & ~PARTMASK) | SPECIAL), fp)) {
for (c = sddevs[tid(dev)], i = 1; i < 5; ++i) {
c->c_plim[i].p_base = fp[i-1].p_base;
c->c_plim[i].p_size = fp[i-1].p_size;
}
return 1;
}
else {
cmn_err(CE_WARN, "Partition table load failed\n");
return -1;
}
} /* loadptable() */
/***********************************************************************
* checkmedia() -- Check for new media in the drive.
*
* Use the Test Unit Read SCSI Command to check for new or any media
* in the drive.
*/
#if __USE_PROTO__
LOCAL int checkmedia(register sdctrl_p c)
#else
LOCAL int
checkmedia(c)
sdctrl_p c;
#endif
{
srb_p r = &(c->c_srb);
r->buf. space = PHYS_ADDR | WATCH_REQACK;
r->buf. ba_phys = NULL;
r->buf. size = 0;
r->xferdir = 0;
r->timeout = 2;
memset(&(r->cdb. g0), 0, sizeof(cdb_t)); /* Test Unit Ready */
set_opcode(r->cdb. g0. opcode, TEST_UNIT_READY);
memset(r->sensebuf, 0, sizeof(r->sensebuf));
doscsi(c->c_haft, r, 4, pritape, slpriSigCatch, "sdchkmda");
return (r->status == ST_GOOD);
} /* checkmedia() */
/***********************************************************************
* sdopen()
*
* Open Entry point for SCSI DASD devices.
*/
#if __USE_PROTO__
LOCAL int sdopen(dev_t dev, ...)
#else
LOCAL int
sdopen(dev /*, mode */)
dev_t dev;
/* int mode; */
#endif
{
register sdctrl_p c;
c = sddevs[tid(dev)];
if (!c) {
set_user_error(ENXIO);
return -1;
}
if (loadptable(dev) == -1) {
set_user_error (ENXIO);
return -1;
}
if (c->c_rmb && !checkmedia(c)) {
cmn_err(CE_NOTE,
"(%d,%d) <Door open - Check media>",
major(c->c_srb. dev),
minor(c->c_srb. dev));
set_user_error (ENXIO);
return -1;
}
++c->c_lastclose;
return 0;
} /* sdopen() */
/***********************************************************************
* sdclose()
*
* Close the SCSI DASD device at dev.
*/
#if __USE_PROTO__
LOCAL void sdclose(dev_t dev)
#else
LOCAL void
sdclose(dev)
dev_t dev;
#endif
{
register sdctrl_p c;
c = sddevs[tid(dev)];
if (!c)
set_user_error (ENXIO);
else if (c->c_lastclose)
--c->c_lastclose;
} /* sdclose() */
#if __USE_PROTO__
LOCAL void sdcleanup(register srb_p r)
#else
LOCAL void
sdcleanup(r)
register srb_p r;
#endif
{
sdmachine(sddevs[r->target]);
}
#if __USE_PROTO__
LOCAL void sddone(register sdctrl_p c, unsigned xfrsize, int errno)
#else
LOCAL void
sddone(c, xfrsize, errno)
register sdctrl_p c;
unsigned xfrsize;
int errno;
#endif
{
register buf_t *bp;
int s;
/*
* This can be put in a loop to move the actual bytes read out of
* a look ahead buffer and into the buffer cache to support multi-
* sector transfers. The trick would be to put a while or for loop
* around the whole thing. The caller would have to make two calls
* to this function, one before recovery is attempted with errno
* cleared and another after recovery is attempted with errno set.
* Calling this function will return all the buffers that finished
* the kernel.
*/
bp = c->c_actf;
if (!bp)
return;
bp->b_resid -= xfrsize;
if (bp->b_resid > 0 && xfrsize > 0 && !errno)
return;
if (errno)
bioerror(bp, errno);
s = sphi();
c->c_actf = bp->b_actf;
bp->b_actf = bp->b_actl = NULL;
spl(s);
bdone(bp);
}
#if __USE_PROTO__
LOCAL srb_p sdmksrb(sdctrl_p c, srb_p r)
#else
LOCAL srb_p sdmksrb(c, r)
sdctrl_p c;
srb_p r;
#endif
{
buf_t *bp;
size_t blkno;
size_t maxblk;
size_t seccnt;
size_t reqcnt;
size_t blkcnt;
int segcount;
size_t segindex;
g1cmd_p g1;
/*
* Search through the request queue for the first block that needs
* work to be done. 99.44% of the time this will be the first block
* in the chain but safe programming dicates that we cover for an
* error somewhere else and minimize the resulting damage at every
* stage.
*/
while ((bp = c->c_actf) != NULL) {
/*
* Get the starting block number and generate the maximum
* I/O size. The maximum possible I/O size is the number
* of blocks from the starting block to the end of the device.
*/
blkno = bp->b_bno + (bp->b_count - bp->b_resid) / BSIZE;
maxblk = blkdev_size(c->c_plim, bp->b_dev) - blkno;
/*
* Check to be sure that there is something to do here. In
* particular the last I/O request could have been shortened
* because it straddled the End of the device. In that case
* the block should be returned to the kernel as is.
*/
if (maxblk > 0)
break;
else
sddone(c, 0, (bp->b_req == BREAD) ? 0 : ENXIO);
} /* while */
/*
* We could have come out of that with no work to do. If so then
* return the sentinel value.
*/
if (!bp)
return NULL;
/*
* Now look ahead through the work list to see if we can optimize
* our communications with the host adapter. e.g. Send one command
* to serve several kernel request. Most of the time we probably
* won't be able to.
*/
seccnt = (bp->b_resid / BSIZE);
if (seccnt > maxblk)
seccnt = maxblk;
blkcnt = seccnt;
reqcnt = 1;
memset(c->c_sglist, 0, sizeof(c->c_sglist));
c->c_sglist[0]. sgs_segstart = NULL;
c->c_sglist[0]. sgs_segsize = 0;
segindex = 0;
segcount = addbuftosgl(bp->b_paddr + bp->b_count - bp->b_resid,
(seccnt * BSIZE),
c->c_sglist,
c->c_sglistsize);
if (segcount != -1) {
segindex += segcount;
while (reqcnt < haisd_maxreq &&
blkcnt < maxblk &&
bp->b_actf != NULL &&
bp->b_actf->b_bno == blkno + blkcnt &&
bp->b_dev == bp->b_actf->b_dev &&
bp->b_req == bp->b_actf->b_req) {
bp = bp->b_actf;
if (bp->b_resid != bp->b_count)
break;
seccnt = (bp->b_resid / BSIZE);
if (seccnt + blkcnt > maxblk)
seccnt = maxblk - blkcnt;
segcount = addbuftosgl(bp->b_paddr,
(seccnt * BSIZE),
c->c_sglist + segindex,
c->c_sglistsize - segindex);
if (segcount == -1)
break;
segindex += segcount;
blkcnt += seccnt;
++reqcnt;
} /* while */
} /* if */
else {
cmn_err(CE_CONT, "[Couldn't start Scatter/Gather list]\n");
}
bp = c->c_actf;
c->c_blkno = blkno + blkdev_base(c->c_plim, bp->b_dev);
c->c_blkcnt = blkcnt;
c->c_reqcnt = reqcnt;
r->dev = bp->b_dev;
r->target = tid(r->dev);
r->lun = lun(r->dev);
if (reqcnt == 1) {
r->buf.space = SYSGLBL_ADDR;
r->buf.ba_phys = bp->b_paddr + bp->b_count - bp->b_resid;
r->buf.size = bp->b_resid;
} /* if */
else {
r->buf.space = SGLIST_ADDR;
r->buf.ba_sglist = c->c_sglist;
r->buf.size = segindex + 1;
} /* else */
r->cleanup = &sdcleanup;
g1 = &(r->cdb. g1);
memset(g1, 0, sizeof(g1cmd_t));
if (bp->b_req == BREAD) {
r->xferdir = DMAREAD;
set_opcode(g1->opcode, G1READ);
}
else {
r->xferdir = DMAWRITE;
set_opcode(g1->opcode, G1WRITE);
}
g1->lba = c->c_blkno;
flip(g1->lba);
g1->xfr_len = c->c_blkcnt;
flip(g1->xfr_len);
return r;
} /* sdmksrb() */
#if __USE_PROTO__
LOCAL void sdmscheckout(sdctrl_p c, size_t xfrsize)
#else
LOCAL void
sdmscheckout(c, xfrsize)
sdctrl_p c;
size_t xfrsize;
#endif
{
buf_t *bp;
size_t bufsize;
/*
* Setup the variables. The data in the cache is valid for
* (cachesize) bytes.
*/
bp = c->c_actf;
while (xfrsize > 0) {
if (!bp)
cmn_err(CE_WARN, "haisd: Internal error: ran out of buffers.");
/*
* Get number of bytes to transfer, always the minimum of the
* Number of bytes in the cache or the number of bytes requested.
*/
bufsize = bp->b_resid;
if (bufsize > xfrsize)
bufsize = xfrsize;
/*
* Update the block and give it back to the kernel if it's done.
*/
sddone(c, bufsize, 0);
/*
* Update the cachestart and buffer pointers and go back
* for another pass.
*/
bp = c->c_actf;
xfrsize -= bufsize;
} /* while */
} /* sdmscheckout() */
/*
* int sderror()
*
* Print a message and return 1 if the last SCSI command had a hard error.
* Return a 0 if no error or if drive corrected the error.
*
* Side effects: if xfrsz is non-null set it to the number of blocks that
* where effected on the last I/O instruction.
*/
#if __USE_PROTO__
LOCAL int sderror(register sdctrl_p c, size_t *xfrsz)
#else
LOCAL int
sderror(c, xfrsz)
register sdctrl_p c;
size_t *xfrsz;
#endif
{
register srb_p r = &(c->c_srb);
extsense_p e;
unsigned long info;
/*
* First check the status in the srb. If this is set to ST_GOOD then
* Look no further.
*/
if (r->status == ST_GOOD) {
if (xfrsz)
*xfrsz = c->c_blkcnt;
return 0;
}
/*
* When Status is not good assume the worst and handle corrected
* Errors as a special case.
*/
if (xfrsz)
*xfrsz = 0;
/*
* When status is ST_CHKCOND do a Request Sense command to get more
* information.
*/
if (r->status == ST_CHKCOND) {
e = (extsense_p) r->sensebuf; /* Assume reqsense will work. */
timedreqsense(c->c_haft, r, 300);
/*
* If the host adapter is hung then the request sense will
* fail too so we need to check the status again here. If the
* request sense worked then we've got all we need in the sense
* buffer so now we just have to decode it.
*/
if (r->status == ST_GOOD && (e->errorcode & 0xf0) == 0x70) {
/*
* Check to see if the info bytes are valid. They usually
* will be for I/O commands. I'm not sure on the recovery
* commands. If they are good then print out an informative
* Message.
*/
if (e->errorcode & 0x80) {
info = e->info;
flip(info);
cmn_err(CE_WARN,
"haisd: (%d, 0x%x) %s on %s at block %ld.",
major(r->dev),
minor(r->dev),
sensestr[e->sensekey & 0x0f],
lastscsicmd,
info - blkdev_base(c->c_plim, r->dev));
/*
* Set xfrsz from info. This still may be wrong (recovered
* errors or false check conditions)
*/
if (xfrsz)
*xfrsz = info - c->c_blkno;
} else {
cmn_err(CE_WARN,
"haisd: (%d, 0x%x) %s on %s.",
major(r->dev),
minor(r->dev),
sensestr[e->sensekey & 0x0f],
lastscsicmd);
}
/*
* If this is a recovered error or a false check condition.
* just assume we got it all and just return success. The Seagate
* Manual says that the info bytes will be set to the "first"
* offending block if multiple blocks offend. This could
* be a problem if other drives do this differently or if multiple
* blocks get recovered errors.
*/
if ((e->sensekey & 0x0f) <= 1) {
if (xfrsz)
*xfrsz = c->c_blkcnt;
return 0;
} else
return 1;
} else {
cmn_err(CE_WARN,
"haisd: (%d, 0x%x) %s failed: status check = 0x%x",
major(r->dev), minor(r->dev), lastscsicmd, r->status);
}
} else
cmn_err(CE_WARN,
"haisd: (%d, 0x%x) %s failed: status 0x%x.",
major(r->dev), minor(r->dev), lastscsicmd, r->status);
return 1;
}
#if __USE_PROTO__
LOCAL int sdrecover(register sdctrl_p c)
#else
LOCAL int
sdrecover(c)
register sdctrl_p c;
#endif
{
register srb_p r = &(c->c_srb);
g0cmd_p g0 = &(r->cdb. g0);
g1cmd_p g1 = &(r->cdb. g1);
memset(&(r->cdb), 0, sizeof(r->cdb));
switch (++(c->c_tries)) {
case 1: /* Test unit ready. */
set_opcode(g0->opcode, TEST_UNIT_READY);
break;
case 3: /* Seek to (target - 1). */
set_opcode(g1->opcode, G1SEEK);
g1->lba = c->c_blkno - 1;
flip(g1->lba);
break;
case 5: /* Seek to (target + 1). */
set_opcode(g1->opcode, G1SEEK);
g1->lba = c->c_blkno + 1;
flip(g1->lba);
break;
case 2: /* Rezero unit. */
case 4: /* Rezero unit. */
case 6: /* Rezero unit. */
set_opcode(g0->opcode, REZERO_UNIT);
break;
default:
cmn_err(CE_WARN,
"haisd: (%d, 0x%x) %s failed at block %ld resetting device.",
major(r->dev), minor(r->dev), lastscsicmd,
c->c_blkno - blkdev_base(c->c_plim, r->dev));
cmn_err(CE_CONT,
"...haisd: Expect unit attention on next operation.\n");
resetdevice(c->c_haft, r->target);
return 0;
}
return 1;
}
#if __USE_PROTO__
LOCAL void sdmachine(register sdctrl_p c)
#else
LOCAL void
sdmachine(c)
register sdctrl_p c;
#endif
{
int done;
int erf;
size_t xfrsz;
unsigned reqtime;
for (done = 0 ; !done ; ) {
switch (c->c_state) {
case SD_INIT:
cmn_err(CE_PANIC, "haisd: Improper state in sdmachine().");
break;
case SD_IDLE:
if (!c->c_actf)
done = 1;
else if (sdmksrb(c, &(c->c_srb)))
set_state(c, SD_STARTSCMD);
break;
case SD_STARTSCMD:
set_state(c, SD_SCMDWAIT);
c->c_starttime = lbolt;
startscsi(c->c_haft, &(c->c_srb));
set_state(c, SD_SCMDFINISH);
break;
case SD_SCMDWAIT:
return;
case SD_SCMDFINISH:
/*
* Status is still pending so the transfer hasn't finished
* yet. We can leave here and pick up when the transfer completes.
*/
if (c->c_srb. status == ST_PENDING)
done = 1;
/*
* Transfer is complete. Check for an error and get the number of
* blocks that actually transfered. Handle partial transfers under
* the error code. Take note that sdpost properly handles partial
* block transfers.
*/
else {
erf = sderror(c, &xfrsz);
if (c->c_reqcnt > 1)
sdmscheckout(c, xfrsz * BSIZE);
else
sddone(c, xfrsz * BSIZE, 0);
reqtime = lbolt - c->c_starttime;
#if __ALLOW_STATS
c->c_stats. hd_reqcnt++;
c->c_stats. hd_blkcnt += xfrsz;
c->c_stats. hd_time += (reqtime > 0) ? reqtime : -reqtime;
c->c_stats. hd_errcnt += (erf != 0);
#endif
set_state(c, (erf) ? SD_RECOVER : SD_IDLE);
}
break;
case SD_RECOVER:
if (!sdrecover(c)) {
sddone(c, 0, EIO);
set_state(c, SD_IDLE);
}
else
set_state(c, SD_RSTARTSCMD);
break;
case SD_RSTARTSCMD:
set_state(c, SD_RSCMDWAIT);
startscsi(c->c_haft, &(c->c_srb));
set_state(c, SD_RSCMDFINISH);
break;
case SD_RSCMDWAIT:
return;
case SD_RSCMDFINISH:
if (c->c_srb. status == ST_PENDING)
done = 1;
else if (sderror(c, NULL)) {
c->c_tries = RETRYLIMIT;
set_state(c, SD_RECOVER);
}
else
set_state(c, SD_IDLE);
break;
}
}
}
#if __USE_PROTO__
LOCAL void sdblock(register buf_t *bp)
#else
LOCAL void
sdblock(bp)
register buf_t *bp;
#endif
{
register sdctrl_p c;
int s;
if (!bp) {
cmn_err(CE_WARN, "haisd: Null buffer address.");
return;
}
bp->b_resid = bp->b_count;
bp->b_actf = bp->b_actl = NULL;
if (major(bp->b_dev) != SCSIMAJOR) {
bioerror(bp, ENXIO);
bdone(bp);
return;
}
c = sddevs[tid(bp->b_dev)];
if ((bp->b_resid % BSIZE) != 0 ||
bp->b_bno > blkdev_size(c->c_plim, bp->b_dev)) {
bioerror(bp, ENXIO);
bdone(bp);
return;
}
s = sphi();
if (!c->c_actf)
c->c_actf = c->c_actl = bp;
else {
c->c_actl->b_actf = bp;
c->c_actl = bp;
}
spl(s);
while (c->c_actf != NULL && c->c_state == SD_IDLE)
sdmachine(c);
}
/***********************************************************************
* sdread()
*
* Read entry point for SCSI DASD devices.
*/
#if __USE_PROTO__
LOCAL void sdread(dev_t dev, IO *iop)
#else
LOCAL void
sdread(dev, iop)
dev_t dev;
IO *iop;
#endif
{
ioreq (NULL, iop, dev, BREAD, BFIOC | BFBLK | BFRAW);
}
/***********************************************************************
* sdwrite()
*
* Write entry point for SCSI DASD devices.
*/
#if __USE_PROTO__
LOCAL void sdwrite(dev_t dev, IO *iop)
#else
LOCAL void
sdwrite(dev, iop)
dev_t dev;
IO *iop;
#endif
{
ioreq (NULL, iop, dev, BWRITE, BFIOC | BFRAW);
}
/***********************************************************************
* sdioctl()
*
* I/O Control for DASD's right now. The options here are for the
* self configuring SCSI kernel.
*/
#if __USE_PROTO__
LOCAL void sdioctl(register dev_t dev, register int cmd, char *vec)
#else
LOCAL void
sdioctl(dev, cmd, vec)
register dev_t dev;
register int cmd;
char *vec;
#endif
{
sdctrl_p c = sddevs[tid(dev)];
hdparm_t hdp;
switch (cmd) {
case HDGETA:
if ((dev & SPECIAL) == 0)
set_user_error(ENXIO);
else {
haihdgeta(tid(dev), &hdp, c->c_plim[0]. p_size);
kucopy(&hdp, vec, sizeof(hdparm_t));
}
break;
case HDSETA:
if ((dev & SPECIAL) == 0)
set_user_error(ENXIO);
else {
if (ukcopy(vec, &hdp, sizeof(hdparm_t)))
haihdseta(tid(dev), &hdp);
}
break;
#if __ALLOW_STATS
case HDGETPERF:
kucopy(&(c->c_stats), vec, sizeof(hdstats_t));
break;
case HDGETPERFCLR:
kucopy(&(c->c_stats), vec, sizeof(hdstats_t));
sdclrstat(&(c->c_stats));
break;
case HDCLRPERF:
sdclrstat(&(c->c_stats));
break;
#endif
default:
set_user_error (ENXIO);
break;
}
} /* sdioctl() */
/* End of file */