NetBSD/sys/arch/pc532/stand/common/scsi_hi.c
2006-09-09 04:12:00 +00:00

264 lines
8.0 KiB
C

/* $NetBSD: scsi_hi.c,v 1.6 2006/09/09 04:12:00 simonb Exp $ */
/****************************************************************************
* NS32K Monitor SCSI high-level driver
* Bruce Culbertson
* 8 March 1990
* (This source is public domain source)
****************************************************************************/
#include <lib/libsa/stand.h>
#include <pc532/stand/common/so.h>
#define OK 0
#define NOT_OK OK+1
#define PRIVATE
#define PUBLIC
#define U8 unsigned char
struct cmd_desc { /* SCSI command description */
const U8 *cmd; /* command string */
const U8 *odata; /* data to output, if any */
const struct cmd_desc *chain; /* next command */
};
struct drive { /* SCSI drive description */
U8 adr, lun; /* SCSI address and LUN */
U8 flags; /* drive characteristics */
U8 stat; /* drive state */
const struct cmd_desc *init; /* list of initialize commands */
};
/* for drive.flags */
#define EXTENDED_RDWR 1 /* device does extended read, write */
#define EXTENDED_SENSE 2 /* device does extended sense */
/* for drive.stat */
#define INITIALIZED 1 /* device is initialized */
PRIVATE struct drive drive_tbl[] = {
{0, 0, EXTENDED_RDWR | EXTENDED_SENSE, 1, 0},
};
#define DRV_TBL_SZ (sizeof (drive_tbl) / sizeof (struct drive))
/* Round up to multiple of four since SCSI transfers are always multiples
* of four bytes.
*/
#define CMD_LEN 12 /* longest SCSI command */
#define SENSE_LEN 24 /* extended sense length */
#define MSG_LEN 4
#define STAT_LEN 4
#define MAX_SCSI_RETRIES 6
#define CMD_IX 2
#define CMD_SENSE 0x03
#define CMD_READ 0x08
#define CMD_WRITE 0x0a
#define CMD_XREAD 0x28
#define CMD_XWRITE 0x2a
PRIVATE U8 cmd_buf[CMD_LEN];
#define SENSE_KEY 2
#define NO_SENSE 0
#define RECOVERY_ERR 1
#define UNIT_ATTN 6
#define ADD_SENSE_CODE 12
#define SENSE_RST 0x29
PRIVATE U8 sense_buf[SENSE_LEN];
#define CHECK_CONDITION 2
#define STAT_IX 3
#define STAT_MASK 0x1f
PRIVATE U8 stat_buf[STAT_LEN];
#define IMSG_IX 7
PRIVATE U8 msg_buf[MSG_LEN];
#define ODATA_IX 0
#define IDATA_IX 1
PRIVATE struct scsi_args scsi_args;
int sc_initialize (struct drive *);
PRIVATE int exec_scsi_hi(const U8 *, U8 *, const U8 *, struct drive *);
PRIVATE int get_sense(struct drive *);
/*===========================================================================*
* sc_rdwt *
*===========================================================================*/
/* Carry out a read or write request for the SCSI disk. */
PUBLIC int
sc_rdwt(int op, int block, void *ram_adr, int len, int sc_adr, int lun)
{
int retries, ret;
U8 *p;
struct drive *dp;
/* get drive characteristics */
for (dp = drive_tbl; dp < drive_tbl + DRV_TBL_SZ - 1; ++dp)
if (dp->adr == sc_adr && dp->lun == lun)
break;
if (dp == drive_tbl + DRV_TBL_SZ - 1) {
dp->adr = sc_adr; /* have default, set adr, lun */
dp->lun = lun;
}
for (retries = 0; retries < MAX_SCSI_RETRIES; ++retries) {
if (dp->init && !(dp->stat & INITIALIZED))
if (OK != sc_initialize(dp)) {
printf("SCSI cannot initialize device\n");
return NOT_OK;
}
p = cmd_buf; /* build SCSI command */
if (dp->flags & EXTENDED_RDWR) { /* use extended commands */
*p++ = (op == DISK_READ)? CMD_XREAD: CMD_XWRITE;
*p++ = lun << 5;
*p++ = (block >> 24) & 0xff;
*p++ = (block >> 16) & 0xff;
*p++ = (block >> 8) & 0xff;
*p++ = (block >> 0) & 0xff;
*p++ = 0;
*p++ = (len >> 8) & 0xff;
*p++ = (len >> 0) & 0xff;
*p = 0;
} else { /* use short (SASI) commands */
*p++ = (op == DISK_READ)? CMD_READ: CMD_WRITE;
*p++ = (lun << 5) | ((block >> 16) & 0x1f);
*p++ = (block >> 8) & 0xff;
*p++ = (block >> 0) & 0xff;
*p++ = len;
*p = 0;
}
if (op == DISK_READ)
ret = exec_scsi_hi(cmd_buf, (U8 *)ram_adr, (U8 *)0, dp);
else
ret = exec_scsi_hi(cmd_buf, (U8 *)0, (U8 *)ram_adr, dp);
if (OK == ret)
return OK;
dp->stat &= ~INITIALIZED;
}
printf("SCSI %s, block %d failed even after retries\n",
op == DISK_READ? "READ": "WRITE", block);
return NOT_OK;
}
/*===========================================================================*
* sc_initialize *
*===========================================================================*/
/* Execute the list of initialization commands for the given drive.
*/
int
sc_initialize(struct drive *dp)
{
const struct cmd_desc *cp;
for (cp = dp->init; cp != 0; cp = cp->chain)
if (OK != exec_scsi_hi(cp->cmd, 0, cp->odata, dp)) {
dp->stat &= ~INITIALIZED;
return NOT_OK;
}
dp->stat |= INITIALIZED;
return OK;
}
/*===========================================================================*
* exec_scsi_hi *
*===========================================================================*/
/* Execute a "high-level" SCSI command. This means execute a low level
* command and, if it fails, execute a request sense to find out why.
*/
PRIVATE int
exec_scsi_hi(const U8 *cmd, U8 *data_in, const U8 *data_out, struct drive *dp)
{
scsi_args.ptr[CMD_IX] = (long)cmd;
scsi_args.ptr[STAT_IX] = (long)stat_buf;
scsi_args.ptr[IMSG_IX] = (long)msg_buf;
scsi_args.ptr[IDATA_IX] = (long)data_in;
scsi_args.ptr[ODATA_IX] = (long)data_out;
if (OK != exec_scsi_low(&scsi_args, dp->adr))
return NOT_OK;
*stat_buf &= STAT_MASK; /* strip off lun */
if (*stat_buf == 0)
/* Success -- this should be the usual case */
return OK;
if (*stat_buf != CHECK_CONDITION) {
/* do not know how to handle this so return error */
printf("SCSI device returned unknown status: %d\n", *stat_buf);
return NOT_OK;
}
/* Something funny happened, need to execute request-sense command
* to learn more.
*/
if (OK == get_sense(dp))
/*
* Something funny happened, but the device recovered from it
* and the command succeeded.
*/
return OK;
return NOT_OK;
}
/*===========================================================================*
* get_sense *
*===========================================================================*/
/* Execute a "request sense" SCSI command and check results. When a SCSI
* command returns CHECK_CONDITION, a request-sense command must be executed.
* A request-sense command provides information about the original command.
* The original command might have succeeded, in which case it does not
* need to be retried and OK is returned. Examples: read error corrected
* with error correction code, or error corrected by retries performed by
* the SCSI device. The original command also could have failed, in
* which case NOT_OK is returned.
*/
#define XLOGICAL_ADR \
(sense_buf[3]<<24 | sense_buf[4]<<16 | sense_buf[5]<<8 | sense_buf[6])
#define LOGICAL_ADR \
(sense_buf[1]<<16 | sense_buf[2]<<8 | sense_buf[3])
PRIVATE int
get_sense(struct drive *dp)
{
U8 *p;
p = cmd_buf; /* build SCSI command */
*p++ = CMD_SENSE;
*p++ = dp->lun << 5;
*p++ = 0;
*p++ = 0;
*p++ = (dp->flags & EXTENDED_SENSE)? SENSE_LEN: 0;
*p = 0;
scsi_args.ptr[IDATA_IX] = (long)sense_buf;
scsi_args.ptr[ODATA_IX] = 0;
scsi_args.ptr[CMD_IX] = (long)cmd_buf;
scsi_args.ptr[STAT_IX] = (long)stat_buf;
scsi_args.ptr[IMSG_IX] = (long)msg_buf;
if (OK != exec_scsi_low(&scsi_args, dp->adr)) {
printf("SCSI SENSE command failed\n");
return NOT_OK;
}
if ((*stat_buf & STAT_MASK) != 0) {
printf("SCSI SENSE returned wrong status %d\n", *stat_buf);
return NOT_OK;
}
if (0 == (dp->flags & EXTENDED_SENSE)) {
printf("SCSI request sense, code 0x%x, log_adr 0x%x\n",
sense_buf[0], LOGICAL_ADR);
return NOT_OK;
}
switch (sense_buf[SENSE_KEY] & 0xf) {
case NO_SENSE:
case UNIT_ATTN: /* reset */
return NOT_OK; /* must retry command */
case RECOVERY_ERR:
/*
* eventually, we probably do not want to hear about
* these. */
printf("SCSI ok with recovery, code 0x%x, logical "
"address 0x%x\n", sense_buf[ADD_SENSE_CODE],
XLOGICAL_ADR);
return OK; /* orig command was ok with recovery */
default:
printf("SCSI failure: key 0x%x code 0x%x log adr 0x%x "
"sense buf %p\n", sense_buf[SENSE_KEY],
sense_buf[ADD_SENSE_CODE], XLOGICAL_ADR, sense_buf);
return NOT_OK; /* orig command failed */
}
}