Coherent4.2.10/conf/ss/src/ss.c
/* $Header: /ker/io.386/RCS/ss.c,v 2.4 93/08/19 10:39:29 nigel Exp Locker: nigel $ */
/*
* Device driver for Seagate ST01/ST02 scsi host adapters.
* To do:
* nonzero LUN's
* start new command during disconnect
* rewrite as single state machine, instead of 7 of them
* separate SCSI layer from host-dependent stuff
*
* $Log: ss.c,v $
* Revision 2.4 93/08/19 10:39:29 nigel
* r83 ioctl (), corefile, new headers
*
* Revision 2.3 93/08/19 04:03:19 nigel
* Nigel's R83
*/
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <kernel/typed.h>
#include <kernel/fakeff.h>
#include <sys/coherent.h>
#include <sys/uproc.h>
#include <sys/buf.h>
#include <sys/dmac.h>
#include <sys/io.h>
#include <sys/sched.h>
#include <sys/proc.h>
#include <sys/con.h>
#include <sys/devices.h> /* SCSI_MAJOR */
#include <sys/fdisk.h>
#include <sys/hdioctl.h>
#include <sys/scsiwork.h>
/*
* Debug levels.
* DEBUG = 0 No debug output.
* DEBUG = 1 Debug output on error only.
* DEBUG = 2 Debug output on error and at other selected places.
* DEBUG = 3 Print state machine trace.
* DEBUG = 4 Print info xfer phases and msg_in values.
*/
#if (DEBUG >= 1)
static int s_id;
#define PR1(str) printf("%s%d ", str, s_id)
#else
#define PR1(str)
#endif
#if (DEBUG >= 2)
#define PR2(str) printf(str)
#else
#define PR2(str)
#endif
#if (DEBUG >= 3)
#define PR3(str) printf("%s%d ", str, s_id)
#else
#define PR3(str)
#endif
#if (DEBUG >= 4)
#define PR4(str) printf("%s%d ", str, s_id)
#else
#define PR4(str)
#endif
#define SS_RAM 0x1800 /* Offset of parameter RAM */
/* Future Domain */
#define FD_CSR 0x1C00 /* Offset of control/status register */
#define FD_DAT 0x1E00 /* Offset of data port */
/* Seagate */
#define SS_CSR 0x1A00 /* Offset of control/status register */
#define SS_DAT 0x1C00 /* Offset of data port */
#define SS_RAM_LEN 128 /* ST0x has 128 bytes of RAM */
#define SS_DAT_LEN 0x400 /* Byte range mapped to data port */
#define SS_SEL_LEN 0x2000 /* Total size of memory-mapped area */
#define WC_ENABLE_SCSI 0x80 /* Write Control (WC) register bits */
#define WC_ENABLE_IRPT 0x40
#define WC_ENABLE_PRTY 0x20
#define WC_ARBITRATE 0x10
#define WC_ATTENTION 0x08
#define WC_BUSY 0x04
#define WC_SELECT 0x02
#define WC_SCSI_RESET 0x01
#define RS_ARBIT_COMPL 0x80 /* Read STATUS (RS) register bits */
#define RS_PRTY_ERROR 0x40
#define RS_SELECT 0x20
#define RS_REQUEST 0x10
#define RS_CTRL_DATA 0x08
#define RS_I_O 0x04
#define RS_MESSAGE 0x02
#define RS_BUSY 0x01
#define DEV_SCSI_ID(dev) ((dev >> 4) & 0x0007)
#define DEV_LUN(dev) ((dev >> 2) & 0x0003)
#define DEV_DRIVE(dev) ((dev >> 2) & 0x001F)
#define DEV_PARTN(dev) (dev & 0x0003)
#define DEV_SPECIAL(dev) (dev & 0x0080)
#define HIPRI_RETRIES 5000 /* # of times to retry while hogging CPU */
#define LOPRI_RETRIES 5 /* # of retries with sleep between tries */
#define WHOLE_DRIVE NPARTN
#define RESET_TICKS 50 /* # of clock ticks for reset settling */
#define LOAD_DELAY 30000 /* Loop counter during ssload() only */
#define BUS_FREE ((ffbyte(ss_csr) & (RS_BUSY | RS_SELECT)) == 0)
#define TGT_RSEL \
( (ffbyte (ss_csr) & (RS_SELECT | RS_I_O )) \
&& (ffbyte (ss_dat) & (host_id | (1 << s_id) )) )
#define DELAY_ARB 10 /* delays units are 10 msec (clock ticks) */
#define DELAY_BDR 30
#define DELAY_BSY 20
#define DELAY_RES 50
#define DELAY_RST 40
#define MAX_AVL_COUNT 100
#define MAX_BDR_COUNT 3
#define MAX_BSY_COUNT 3
#define MAX_TRY_COUNT 10
#define INL_MAX_REQ_POLL 800000L
#define WKG_MAX_REQ_POLL 20000L
typedef enum { /* values for current driver state */
SST_DEQUEUE = 0,
SST_BUS_DEV_RESET,
SST_HIPRI_RESET,
SST_LOPRI_RESET,
SST_POLL_ARBITN,
SST_POLL_BEGIN_IO,
SST_POLL_RESELECT,
SST_REQ_SENSE,
SST_RESET_OFF
} SST_TYPE;
typedef enum { /* values for input to recovery routine */
RV_A_TIMEOUT,
RV_P_TIMEOUT,
RV_R_TIMEOUT,
RV_BF_TIMEOUT,
RV_CS_BUSY,
RV_CS_CHECK
} RV_TYPE;
typedef struct ss {
ulong capacity;
ulong blocklen;
ulong bno;
int msg_in;
int dr_watch;
unchar cmdbuf [G1CMDLEN];
int cmdlen;
int cmd_bytes_out;
int cmdstat;
BUF * bp; /* current I/O request node, or NULL */
struct fdisk_s parmp [NPARTN + 1];
SST_TYPE state;
TIM tim; /* for target-specific timers */
unchar avl_count;
unchar bdr_count;
unchar bsy_count;
unchar try_count;
uint busy:1; /* 1 if command uses local buffer */
uint expired:1; /* 1 if target's timer has expired */
uint ptab_read:1; /* 1 if partition table has been read */
uint waiting:1; /* 1 if target timer is running */
} ss_type;
/* functions from bufq.c */
extern int bufq_init ();
extern void bufq_rlse ();
extern void bufq_wr_tail ();
extern BUF * bufq_rd_head ();
extern BUF * bufq_rm_head ();
/* functions from ssas.s */
extern void ss_get ();
extern int ss_put ();
static int bus_dev_reset (); /* additional support functions */
static int chk_reconn ();
static void do_connect ();
static void dummy_reconn ();
static int far_info_xfer ();
static int host_ident ();
static int init_call ();
static void init_pointers ();
static int inquiry ();
static int local_info_xfer ();
static int mode_sense ();
static void next_req ();
static void nonpolled ();
static int read_cap ();
static void recover ();
static int req_sense ();
static int rsel_handshake ();
static void ssdelay ();
static void ss_finished ();
static void ss_mach ();
static void set_timeout ();
static int ssinit ();
static void ssintr ();
static int start_arb ();
static void stop_timeout ();
static void tbparms ();
static unchar xpmod ();
extern short at_drive_ct; /* set by atcount () before any load routines run */
/*
* Configurable variables
*
* In the low byte of NSDRIVE, bit n is 1 if SCSI ID n is an installed target.
* The high byte indicates which type of host adapter:
* 00 - ST01/ ST02
* 80 - TMC-845/850/860/875/885
* 40 - TMC-840/841/880/881
*/
extern unsigned int NSDRIVE;
extern unsigned int SS_INT; /* ST0 [12] use either IRQ3 or IRQ5 */
extern unsigned int SS_BASE; /* Segment addr of ST0x comm area */
/* ncyl, nhead, nspt */
extern _drv_parm_t _sd_drv_parm [];
static paddr_t ss_base; /* physical address of ST0x comm area */
static faddr_t ss_fp; /* (far *) to ST0x comm area */
static faddr_t ss_ram; /* (far *) to parameter RAM */
static faddr_t ss_csr; /* (far *) to control / status */
static faddr_t ss_dat; /* (far *) to data port */
static TIM delay_tim; /* needed for calls to ssdelay () */
static int do_sst_op; /* 1 when state machine iteration continues */
static int ss_expired; /* 1 after local timeout */
static uint max_req_poll; /* this changes after initialization */
static unchar host_id; /* Host is SCSI ID #7 for Seagate, 6 for FD */
static unchar swap_status_bits;
static ss_type * ss_tbl; /* points to block of "ss" structs */
static ss_type * ss [MAX_SCSI_ID];
/*
* host_claimed is -1 if host is available, else it's the SCSI id of the
* target that claims the host.
*
* host is claimed at start of any of the follwoing:
* SCSI bus reset
* arbitration for block I/O request
* reselect
*
* host is released at:
* end of SCSI bus reset
* completion (successful or not) of block I/O request (ss_finished)
* disconnect <-- temporarily disabled
*/
static int host_claimed;
/*
* ssload () - load routine.
*
* Action: The controller is reset and the interrupt vector is grabbed.
* The drive characteristics are set up at this time.
*/
static
void ssload ()
{
int erf = 0; /* 1 if error occurs */
int i;
int max_id = -1;
int num_drives = 0;
int tbnum;
/*
* Allocate a selector to map into ST0x memory-mapped comm area.
*/
ss_base = (paddr_t)((long)(unsigned) SS_BASE << 4);
ss_fp = map_pv (ss_base, (fsize_t )SS_SEL_LEN);
ss_ram = ss_fp + SS_RAM;
/*
* Primitive test of ST0x RAM.
*/
sfword (ss_ram, 0xA55A);
sfword (ss_ram + 2, 0x3CC3);
sfword (ss_ram + SS_RAM_LEN - 4, 0xA55A);
sfword (ss_ram + SS_RAM_LEN - 2, 0x3CC3);
if (ffword (ss_ram) != 0xA55A || /* fetch a "far" word */
ffword (ss_ram + 2) != 0x3CC3 ||
ffword (ss_ram + SS_RAM_LEN - 4) != 0xA55A ||
ffword (ss_ram + SS_RAM_LEN - 2) != 0x3CC3) {
printf ("Error - host failed memory test\n");
erf = 1;
}
/*
* Set host-dependent constants.
*/
switch (NSDRIVE >> 8) {
case 0x00: /* ST01/ ST02 */
ss_csr = ss_fp + SS_CSR;
ss_dat = ss_fp + SS_DAT;
host_id = 0x80; /* host is id #7 */
break;
case 0x80: /* TMC-845/850/860/875/885 */
ss_csr = ss_fp + FD_CSR;
ss_dat = ss_fp + FD_DAT;
host_id = 0x40; /* host is id #6 */
break;
case 0x40: /* TMC-840/841/880/881 */
ss_csr = ss_fp + SS_CSR;
ss_dat = ss_fp + SS_DAT;
host_id = 0x40; /* host is id #6 */
swap_status_bits = 1;
break;
}
NSDRIVE &= ~ (uint) host_id;
/*
* Allocate drive structs.
*
* Do a single call to kalloc () then put allocated pieces into
* array ss.
*
* First allocate and clear storage. Then hook up the pointers.
*/
if (! erf) {
for (i = 0; i < MAX_SCSI_ID; i ++)
if ((NSDRIVE >> i) & 1) {
max_id = i;
num_drives ++;
}
if (num_drives == 0) {
printf ("Error - ss has no valid target id's\n");
erf = 1;
} else if ((ss_tbl = kalloc (num_drives * sizeof (ss_type)))
== NULL) {
printf ("Error - ss can't allocate structs\n");
erf = 1;
} else
memset (ss_tbl, 0, num_drives * sizeof (ss_type));
}
if (! erf) {
ss_type * foo = ss_tbl;
for (i = 0; i < MAX_SCSI_ID; i ++)
if ((NSDRIVE >> i) & 1)
ss [i] = foo ++;
}
/*
* Claim IRQ vector.
*/
setivec (SS_INT, ssintr);
/*
* Initialize drives we know about (i.e. in NSDRIVE bitmap).
*
* Part of this is getting parameters from tboot, if any.
* The drive number in tboot's data block must be matched with
* the SCSI id in question. Drive numbering in tboot is assumed
* to start with any "at" drives (at_drive_ct counts these)
* then proceed with SCSI drives in increasing id number order.
*/
tbnum = at_drive_ct; /* tboot drive number for first SCSI drive */
host_claimed = -1;
bufq_init (max_id + 1);
max_req_poll = INL_MAX_REQ_POLL;
if (! erf) {
for (i = 0; i < MAX_SCSI_ID; i ++)
if ((NSDRIVE >> i) & 1) {
tbparms (tbnum, i); /* get tboot parms */
ssinit (i);
tbnum ++;
}
}
max_req_poll = WKG_MAX_REQ_POLL;
}
/*
* ssunload () - unload routine.
*/
static void
ssunload ()
{
/*
* Deallocate driver heap space.
*/
if (ss_tbl)
kfree (ss_tbl);
bufq_rlse ();
/*
* Free the ST0x selector.
*/
unmap_pv (ss_fp);
/*
* Release IRQ vector.
*/
clrivec (SS_INT);
}
/*
* ssopen ()
*
* Input: dev = disk device to be opened.
* mode = access mode [IPR, IPW, IPR + IPW].
*
* Action: Validate the minor device.
* Update the paritition table if necessary.
*/
static
void ssopen (dev, mode)
dev_t dev;
{
int drive, partn;
struct fdisk_s * fdp;
ss_type * ssp;
int s_id;
unchar * msg;
/*
* Set up local variables.
*/
drive = DEV_SCSI_ID (dev);
partn = DEV_PARTN (dev);
s_id = DEV_SCSI_ID (dev);
ssp = ss [s_id];
fdp = ssp->parmp;
#if (DEBUG >= 3)
devmsg (dev, "ssopen");
#endif
/*
* LUN must be zero.
* SCSI id must have corresponding 1 in NSDRIVE bitmapped variable.
*/
if (DEV_LUN (dev) != 0 || ((1 << drive) & NSDRIVE) == 0) {
msg = "bad LUN or SCSI id";
set_user_error (ENXIO);
goto bad_open;
}
/*
* If "special" bit is set, partition field must be zero.
*/
if (DEV_SPECIAL (dev) && partn != 0) {
msg = "bad special partition";
set_user_error (ENXIO);
goto bad_open;
}
/*
* Subscripting gimmick for partition table.
*/
if (dev & SDEV)
partn = WHOLE_DRIVE;
/*
* If not accessing whole drive and the partition table has not
* been read yet, try to read it now.
* Do this by calling fdisk () with partition table device on the drive
* that is being accessed.
*/
if (partn != WHOLE_DRIVE && !(ssp->ptab_read)) {
int fdisk_dev;
fdisk_dev = (dev | SDEV) & 0xfff0;
#if (DEBUG >= 3)
devmsg (fdisk_dev, "calling fdisk");
if (fdisk (fdisk_dev, fdp)) {
int p;
fdp [WHOLE_DRIVE].p_size = ssp->capacity;
fdp [WHOLE_DRIVE].p_base = 0;
printf ("fdisk () succeeded\n");
for (p = 0; p < WHOLE_DRIVE ; p ++)
printf ("p =%d base =%ld size =%ld\n", p, fdp [p].p_base, fdp [p].p_size);
ssp->ptab_read = 1;
} else {
printf ("fdisk () failed\n");
set_user_error (ENXIO);
goto bad_open;
}
#else
if (fdisk (fdisk_dev, fdp)) {
fdp [WHOLE_DRIVE].p_size = ssp->capacity;
fdp [WHOLE_DRIVE].p_base = 0;
ssp->ptab_read = 1;
} else {
msg = "bad partition table";
set_user_error (ENXIO);
goto bad_open;
}
#endif
}
/*
* Ensure partition lies within drive boundaries and is non-zero size.
*/
if (partn != WHOLE_DRIVE &&
(fdp [partn].p_base + fdp [partn].p_size) >
fdp [WHOLE_DRIVE].p_size) {
msg = "partition exceeds drive capacity";
set_user_error (EINVAL);
goto bad_open;
}
if (partn != WHOLE_DRIVE && fdp [partn].p_size == 0) {
msg = "partition not found";
set_user_error (ENODEV);
goto bad_open;
}
/*
* OK to open the device.
* Start watchdog timer (if not already started) for the host adapter.
*/
++ drvl [SCSI_MAJOR].d_time;
++ ssp->dr_watch;
return;
bad_open:
devmsg (dev, msg);
}
/*
* ssclose ()
*/
static void
ssclose (dev)
dev_t dev;
{
ss_type * ssp;
int s_id;
s_id = DEV_SCSI_ID (dev);
ssp = ss [s_id];
/*
* Decrement the number of watchdog timer requests open for host
* adapter and for target.
*/
-- drvl [SCSI_MAJOR].d_time;
-- ssp->dr_watch;
#if (DEBUG >= 3)
devmsg (dev, "ssclose");
#endif
}
/*
* ssread () - read a block from the raw disk
*
* Input: dev = disk device to be written to.
* iop = pointer to source I/O structure.
*
* Action: Invoke the common raw I/O processing code.
*/
static void
ssread (dev, iop)
dev_t dev;
IO * iop;
{
ioreq (NULL, iop, dev, BREAD, BFRAW | BFBLK | BFIOC);
}
/*
* sswrite () - write a block to the raw disk
*
* Input: dev = disk device to be written to.
* iop = pointer to source I/O structure.
*
* Action: Invoke the common raw I/O processing code.
*/
static
void sswrite (dev, iop)
dev_t dev;
IO * iop;
{
ioreq (NULL, iop, dev, BWRITE, BFRAW | BFBLK | BFIOC);
}
/*
* ssioctl ()
*
* Input: dev = disk device to be operated on.
* cmd = input / output request to be performed.
* vec = (pointer to) optional argument.
*/
static void
ssioctl (dev, cmd, vec)
dev_t dev;
int cmd;
char * vec;
{
hdparm_t hdparm;
struct fdisk_s * fdp;
int s_id;
ss_type * ssp;
s_id = DEV_SCSI_ID (dev);
ssp = ss [s_id];
fdp = ssp->parmp;
switch (cmd) {
case HDGETA:
/*
* Get hard disk attributes.
*/
PR3("HDGETA");
fdp = ssp->parmp;
* (short *) & hdparm.landc [0] =
* (short *) & hdparm.ncyl [0] = _sd_drv_parm [s_id].ncyl;
hdparm.nhead = _sd_drv_parm [s_id].nhead;
hdparm.nspt = _sd_drv_parm [s_id].nspt;
#if (DEBUG >= 3)
printf ("ncyl =%d nhead =%d nspt =%d\n",
hdparm.ncyl [0]+((int)hdparm.ncyl [1]<<8), (int)hdparm.nhead, (int)hdparm.nspt);
#endif
kucopy (& hdparm, vec, sizeof hdparm);
break;
case HDSETA:
/*
* Set hard disk attributes.
*/
PR3("HDSETA");
fdp = ssp->parmp;
ukcopy (vec, & hdparm, sizeof hdparm);
_sd_drv_parm [s_id].ncyl = * (short *) & hdparm.ncyl [0];
_sd_drv_parm [s_id].nhead = hdparm.nhead;
_sd_drv_parm [s_id].nspt = hdparm.nspt;
#if (DEBUG >= 3)
printf ("ncyl =%d nhead =%d nspt =%d\n",
hdparm.ncyl [0]+((int)hdparm.ncyl [1]<<8), (int)hdparm.nhead, (int)hdparm.nspt);
#endif
break;
default:
set_user_error (EINVAL);
}
}
/*
* ssblock () - queue a block to the disk
*
* Input: bp = pointer to block to be queued.
*
* Action: Queue a block to the disk.
* Make sure that the transfer is within the disk partition.
*/
static void
ssblock (bp)
BUF * bp;
{
struct fdisk_s * fdp;
int partition, drive, s_id;
dev_t dev;
ss_type * ssp;
unchar * msg = NULL;
/*
* Set up local variables.
*/
dev = bp->b_dev;
partition = DEV_PARTN (dev);
drive = DEV_DRIVE (dev);
s_id = DEV_SCSI_ID (dev);
ssp = ss [s_id];
if (dev & SDEV)
partition = WHOLE_DRIVE;
fdp = ssp->parmp;
bp->b_resid = bp->b_count;
#if (DEBUG >= 2)
if (bp->b_count != BSIZE)
printf ("b_count =%d ", bp->b_count);
#endif
/*
* Range check disk region.
*/
if (ssp->ptab_read == 0) {
if (partition == WHOLE_DRIVE) {
#if 0
/* Why did we only allow people to access the first block of WHOLE_DRIVE?
in cases where there was not a valid partition table? */
if ((bp->b_bno != 0) || (bp->b_count != BSIZE)) {
msg = "invalid request";
bp->b_flag |= BFERR;
goto bad_blk;
}
#endif
} else {
msg = "no partition table";
bp->b_flag |= BFERR;
goto bad_blk;
}
} else if (bp->b_req == BREAD && bp->b_bno == fdp [partition].p_size) {
/*
* Read at end of partition.
* Need to return with b_resid = BSIZE to signal end of volume.
*/
goto bad_blk;
} else if ((bp->b_bno + (bp->b_count / BSIZE)) >
fdp [partition].p_size) {
/*
* Read past end of partition.
*/
msg = "partition overrun";
bp->b_flag |= BFERR;
goto bad_blk;
}
/*
* Fail if request is for zero bytes or is not even # of blocks.
*/
if ((bp->b_count % BSIZE) || bp->b_count == 0) {
msg = "invalid byte count";
bp->b_flag |= BFERR;
goto bad_blk;
}
/*
* Operation appears valid.
* Fill fields in the node and queue the request.
*/
bufq_wr_tail (s_id, bp);
ss_mach (s_id);
return;
/*
* Operation cannot be done. Release the kernel buffer structure.
* Value of "bp->b_flag" tells caller if error occurred.
*/
bad_blk:
if (msg)
devmsg (dev, msg);
bdone (bp);
}
/*
* ssintr () - Interrupt routine.
*
* If we have been reselected by a recognized target device
* let kernel get out of interrupt mode (defer) and do SCSI
* reconnect stuff.
*/
static void
ssintr ()
{
int s_id;
s_id = chk_reconn ();
if (s_id != -1) {
if (ss [s_id]->state == SST_POLL_RESELECT)
defer (ss_mach, s_id);
else
defer (dummy_reconn, s_id);
PR3("!");
}
}
/*
* dummy_reconn ()
*
* Somehow we are in a state where the driver software does not expect
* a reconnect but a device is trying one anyway. Go thru the motions
* of reconnect because not servicing a hanging reselect seems to leave
* the target hung - in such a way that it fails to respond to reset
* messages and to reset on the SCSI bus.
*/
static void
dummy_reconn (s_id)
int s_id;
{
int bus_timeout;
unchar phase_type;
int s;
int msg_in;
int cmdstat;
int xfer_good = 1;
PR1("DUM");
if (ss [s_id]->state == SST_POLL_RESELECT) {
defer (ss_mach, s_id);
return;
}
if (! rsel_handshake ())
return;
s = sphi ();
while (req_wait (& bus_timeout) && xfer_good) {
phase_type = ffbyte (ss_csr) &
(RS_MESSAGE | RS_I_O | RS_CTRL_DATA);
switch (xpmod (phase_type)) {
case XP_MSG_IN:
msg_in = ffbyte (ss_dat);
switch (msg_in){
case MSG_CMD_CMPLT:
case MSG_DISCONNECT:
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT);
break;
}
break;
case XP_MSG_OUT:
sfbyte (ss_dat, MSG_NOP);
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI);
break;
case XP_STAT_IN:
cmdstat = ffbyte (ss_dat);
break;
case XP_CMD_OUT:
case XP_DATA_OUT:
xfer_good = 0;
break;
case XP_DATA_IN:
ffbyte (ss_dat);
break;
default:
break;
} /* endswitch */
} /* endwhile */
spl (s);
}
/*
* sswatch ()
*
* Invoked once per second if any devices going through this driver are open.
* Poll for any reselect, in case interrupt got lost.
*/
static void
sswatch ()
{
int s_id;
ss_type * ssp;
for (s_id = 0; s_id < MAX_SCSI_ID; s_id ++) {
ssp = ss [s_id];
if (ssp && ssp->dr_watch)
defer (ss_mach, s_id);
} /* endfor */
}
/*
* bus_wait ()
*
* Wait for specified bit values to appear in Status Register.
* This uses a tight loop and does not expect to be interrupted.
*
* Argument "flags" is a double-byte value; the high byte is ANDed with
* status register contents, and the result is tested for equality with
* the low byte.
*
* Return 1 if values wanted appeared, 0 if timeout occurred.
*/
static int
bus_wait (flags)
unsigned short flags;
{
int found, i;
unsigned char status;
found = 0;
for ( i = 0; i < HIPRI_RETRIES; i ++) {
status = ffbyte (ss_csr);
if ((status & (flags >> 8)) == (flags & 0xff)) {
found = 1;
break;
}
}
#if (DEBUG >= 1)
if (! found)
printf ("TO:f =%x s =%x ", flags, status);
#endif
return found;
}
/*
* ssinit ()
*
* Attempt to initialize the (unique) drive with a given SCSI id.
* Assume only one drive per SCSI id, having LUN = 0.
*
* Return 1 if success, 0 if failure.
*/
static int
ssinit (s_id)
int s_id;
{
int retval = 1;
unchar query_buf [MODESENSELEN];
ss_type * ssp = ss [s_id];
int dev = ((SCSI_MAJOR << 8) | 0x80 | (s_id << 4));
printf ("SCSI ID %d LUN 0\n", s_id);
if (retval)
if (init_call (inquiry, s_id, query_buf)) {
query_buf [INQUIRYLEN] = 0;
#if (debug >= 2)
devmsg (dev, query_buf + 8);
#endif
if (query_buf [0] == 0) {
retval = 1;
} else
devmsg (dev, "Not Direct Access Device");
} else
devmsg (dev, "Inquiry Failed");
if (retval)
if (init_call (read_cap, s_id, query_buf)) {
retval = 1;
ssp->capacity = query_buf [3] | (query_buf [2] << 8)
| (((long)(query_buf [1])) << 16)
| (((long)(query_buf [0])) << 24);
ssp->blocklen = query_buf [7] | (query_buf [6] << 8)
| (((long)(query_buf [5])) << 16)
| (((long)(query_buf [4])) << 24);
printf ("Capacity =%ld blocks Block length =%ld\n",
ssp->capacity, ssp->blocklen);
} else
devmsg (dev, "Read Capacity Failed");
if (retval)
if (init_call (mode_sense, s_id, query_buf)) {
/*
* Display physical drive parameters.
*/
#define FMT_PG (4 + 8 + 8 + 12)
#define DDG_PG (4 + 8 + 8 + 12 + 24)
unchar heads;
unsigned short spt;
ulong cyls;
spt =((int)query_buf [FMT_PG + 10]<<8)
+ query_buf [FMT_PG + 11];
cyls =((int)query_buf [DDG_PG + 2]<<16)
+ ((int)query_buf [DDG_PG + 3]<<8)
+ query_buf [DDG_PG + 4];
heads = query_buf [DDG_PG + 5];
printf ("Physical: cylinders =%ld ", cyls);
printf ("heads =%d ", heads);
printf ("spt =%d\n", spt);
if (_sd_drv_parm [s_id].ncyl == 0) {
_sd_drv_parm [s_id].ncyl = cyls;
_sd_drv_parm [s_id].nhead = heads;
_sd_drv_parm [s_id].nspt = spt;
} else {
printf ("Logical: cylinders =%d ",
_sd_drv_parm [s_id].ncyl);
printf ("heads =%d ", _sd_drv_parm [s_id].nhead);
printf ("spt =%d\n", _sd_drv_parm [s_id].nspt);
}
} else
devmsg (dev, "Mode Sense Failed");
return retval;
}
/*
* far_info_xfer ()
*
* Do bus cycle information transfer phases.
* This includes message in / out, command in / out, and data in / out.
*
* If cmdlen is nonzero, cmdbuf is an array of bytes of that length,
* to be sent to the target.
*
* Return 1 if bus timeout did not occur, else 0.
*
* pseudocode:
*
* while (wait for REQ true or BUSY false on SCSI bus)
* if (BUSY false)
* break from while loop
* else
* switch (xfer phase = RS_CTRL_DATA | RS_I_O | RS_MESSAGE)
* case XP_MSG_IN / XP_MSG_OUT /...
* handle the indicated information transfer phase
* endswitch
* endif
* endwhile
*/
static int
far_info_xfer (s_id)
int s_id;
{
int bus_timeout;
unchar phase_type;
unchar msg_in;
int s;
int bytes_to_send;
ss_type * ssp = ss [s_id];
BUF * bp = ssp->bp;
int xfer_good = 1;
int xfer_count = bp->b_count - bp->b_resid;
int irpts_masked;
int block_done = 0;
ssp->cmd_bytes_out = 0;
ssp->msg_in = -1;
irpts_masked = 0;
while (req_wait (& bus_timeout) && xfer_good) {
phase_type = ffbyte (ss_csr) & (RS_MESSAGE | RS_I_O | RS_CTRL_DATA);
if (! irpts_masked) {
s = sphi ();
irpts_masked = 1;
}
switch (xpmod (phase_type)) {
case XP_MSG_IN:
msg_in = ffbyte (ss_dat);
switch (msg_in){
case MSG_CMD_CMPLT:
PR4("Mcc");
ssp->msg_in = msg_in;
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT);
break;
case MSG_DISCONNECT:
PR4("Mdc");
ssp->msg_in = msg_in;
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT);
break;
case MSG_SAVE_DPTR:
PR4("Msd");
break;
case MSG_RSTOR_DPTR:
PR4("Mrd");
break;
case MSG_ABORT:
PR4("Mab");
break;
case MSG_DEV_RESET:
PR4("Mdr");
break;
case MSG_IDENTIFY:
PR4("Mmi");
break;
case MSG_IDENT_DC:
PR4("Mmd");
break;
}
break;
case XP_MSG_OUT:
PR4("MO");
/*
* This case shouldn't happen. We weren't
* asserting ATTENTION. Abort the bus cycle.
*/
sfbyte (ss_dat, MSG_NOP);
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI);
break;
case XP_STAT_IN:
PR4("SI");
ssp->cmdstat = ffbyte (ss_dat);
break;
case XP_CMD_OUT:
/*
* Ship out command bytes.
* Reset SCSI bus if too many command bytes are wanted.
*/
bytes_to_send = ssp->cmdlen - ssp->cmd_bytes_out;
if (bytes_to_send > 0) {
sfbyte (ss_dat, ssp->cmdbuf [ssp->cmd_bytes_out ++]);
/*
* If just sent last byte, allow interrupts.
*/
if (bytes_to_send == 1) {
PR4("CO");
if (bp->b_req == BREAD) {
if (irpts_masked) {
spl (s);
irpts_masked = 0;
}
}
}
} else { /* This case should not happen. */
xfer_good = 0;
}
break;
case XP_DATA_IN:
/*
* If caller's buffer has room, keep incoming
* data byte.
*/
if (block_done) {
xfer_good = 0;
PR1("Data in overrun");
} else if (bp->b_req != BREAD) {
xfer_good = 0;
} else {
block_done = 1;
if (BSIZE != xpcopy (ss_dat, bp->b_paddr + xfer_count,
BSIZE, SEL_386_KD | SEG_VIRT)) {
devmsg (bp->b_dev, "XP_DATA_IN: ss_dat: %x, bp->bpaddr: %x, xfer_count: %x\n",
ss_dat, bp->b_paddr, xfer_count);
break;
}
}
break;
case XP_DATA_OUT:
/*
* Copy output buffer bytes to data register.
*/
if (block_done) {
xfer_good = 0;
PR1("Data out overrun");
} else if (bp->b_req != BWRITE) {
xfer_good = 0;
} else {
block_done = 1;
if (BSIZE != pxcopy (bp->b_paddr + xfer_count, ss_dat,
BSIZE, SEL_386_KD | SEG_VIRT)) {
devmsg (bp->b_dev, "XP_DATA_OUT: bp->b_paddr: %x, xfer_count: %x, ss_dat: %x\n",
bp->b_paddr, xfer_count, ss_dat);
break;
}
if (irpts_masked) {
spl (s);
irpts_masked = 0;
}
}
break;
default:
break;
} /* endswitch */
}
if (irpts_masked)
spl (s);
#if (DEBUG >= 1)
switch (ssp->cmdstat) {
case -1:
if (msg_in != MSG_DISCONNECT)
printf ("CS-", ssp->cmdstat);
break;
case CS_GOOD:
break;
case CS_CHECK:
printf ("CSK", ssp->cmdstat);
break;
case CS_BUSY:
printf ("CSY", ssp->cmdstat);
break;
case CS_RESERVED:
default:
printf ("CS%x", ssp->cmdstat);
}
#endif
return (bus_timeout) ? 0 : 1 ;
}
/*
* req_wait ()
*
* This routine is called at the start of each information transfer
* phase and after the last such phase.
*
* It returns 1 if REQ is asserted on the SCSI bus, meaning another phase
* may begin, and 0 otherwise. A REQ signal will not be seen if the function
* times out or if BUSY drops. A value of 1 is written to the pointer argument
* if timeout occurred, else 0 is written.
*/
static int
req_wait (to_ptr)
int * to_ptr;
{
int req_found;
unsigned char status;
ulong poll_ct;
int s;
s = splo ();
* to_ptr = 1;
req_found = 0;
for (poll_ct = 0L; poll_ct < max_req_poll; poll_ct ++) {
status = ffbyte (ss_csr);
if (status & RS_REQUEST) {
req_found = 1;
* to_ptr = 0;
break;
} else if ((status & RS_BUSY) == 0) {
* to_ptr = 0;
break;
}
}
#if (DEBUG >= 1)
if (* to_ptr) {
printf ("TX: s =%x ", status);
}
#endif
spl (s);
return req_found;
}
/*
* req_sense ()
*
* Request Sense for a device. The main reason for doing this is to
* clear a standing Command Status of Device Check.
*
* Full results are discarded. Return 1 if Device returns No Sense or
* or Unit Attention. Else return 0.
*
*/
static int
req_sense (s_id)
int s_id;
{
unchar sense_buf [SENSELEN];
unchar cmdbuf [G0CMDLEN];
int ret = 0;
cmdbuf [0] = ScmdREQUESTSENSE;
cmdbuf [1] = 0;
cmdbuf [2] = 0;
cmdbuf [3] = 0;
cmdbuf [4] = SENSELEN;
cmdbuf [5] = 0;
#if (DEBUG >= 2)
{int i; for (i = 0; i < SENSELEN; i ++) sense_buf [i]= 0;}
#endif
PR2("rqs:");
if (! start_arb ()) {
PR2("NO arb");
#if (DEBUG >= 2)
printf ("status =%x ", ffbyte (ss_csr));
#endif
goto rqs_done;
}
if (! host_ident (s_id, 0)) {
PR2("NO host ident");
#if (DEBUG >= 2)
printf ("status =%x ", ffbyte (ss_csr));
#endif
goto rqs_done;
}
if (! local_info_xfer (cmdbuf, G0CMDLEN, sense_buf, SENSELEN, NULL, 0)) {
PR2("NO local xfer");
goto rqs_done;
} else {
/*
* Return 1 if drive responded with any of these sense keys:
* 0x00 No Sense
* 0x06 Unit Attention
* 0x0B Aborted Command
* In any of the above cases, a retry will likely succeed
* without Buse Device Reset or SCSI Bus Reset.
*/
switch (sense_buf [2]) {
case 0x00:
case 0x06:
case 0x0B:
ret = 1;
break;
} /* endswitch */
}
rqs_done:
#if (DEBUG >= 2)
{
int i;
for (i = 0; i < SENSELEN;i ++)
printf ("%x ", sense_buf [i]);
printf ("\n");
}
#endif
return ret;
}
/*
* inquiry ()
*
* Inquiry command for a device.
* Find out if device is direct access, removable, etc.
*
* Put result of inquiry into supplied buffer.
* Return 1 if command succeeds, else 0.
*/
static int
inquiry (s_id, buf)
int s_id;
unchar * buf;
{
int ret = 0;
unchar cmdbuf [G0CMDLEN];
cmdbuf [0] = ScmdINQUIRY;
cmdbuf [1] = 0;
cmdbuf [2] = 0;
cmdbuf [3] = 0;
cmdbuf [4] = INQUIRYLEN;
cmdbuf [5] = 0;
if (start_arb () && host_ident (s_id, 0) &&
local_info_xfer (cmdbuf, G0CMDLEN, buf, INQUIRYLEN, NULL, 0))
ret = 1;
return ret;
}
/*
* mode_sense ()
*
* Mode Sense command for a device.
* Use this to get disk parameters:
* number of cylinders
* number of heads
* number of sectors per track.
*
* Put result of mode sense into supplied buffer.
* Return 1 if command succeeds, else 0.
*/
static int
mode_sense (s_id, buf)
int s_id;
unchar * buf;
{
int ret = 0;
unchar cmdbuf [G0CMDLEN];
cmdbuf [0] = ScmdMODESENSE;
cmdbuf [1] = 0;
cmdbuf [2] = 0x3F;
cmdbuf [3] = 0;
cmdbuf [4] = MODESENSELEN;
cmdbuf [5] = 0;
if (start_arb () && host_ident (s_id, 0) &&
local_info_xfer (cmdbuf, G0CMDLEN, buf, MODESENSELEN, NULL, 0))
ret = 1;
return ret;
}
/*
* read_cap ()
*
* Read Capacity command for a device.
*
* Return 1 if command succeeds, else 0.
*/
static int
read_cap (s_id, buf)
int s_id;
unchar * buf;
{
int ret = 0;
unchar cmdbuf [G1CMDLEN];
cmdbuf [0] = ScmdREADCAPACITY;
cmdbuf [1] = 0;
cmdbuf [2] = 0;
cmdbuf [3] = 0;
cmdbuf [4] = 0;
cmdbuf [5] = 0;
cmdbuf [6] = 0;
cmdbuf [7] = 0;
cmdbuf [8] = 0;
cmdbuf [9] = 0;
if (start_arb () && host_ident (s_id, 0) &&
local_info_xfer (cmdbuf, G1CMDLEN, buf, READCAPLEN, NULL, 0))
ret = 1;
return ret;
}
/*
* bus_dev_reset ()
*
* Send Bus Device Reset message to the given SCSI id.
* Return 1 if host adapter was not busy and no obvious timeouts occurred,
* else 0.
*/
static int
bus_dev_reset (s_id)
int s_id;
{
int bdr_ok = 1;
int dev = ((SCSI_MAJOR << 8) | 0x80 | (s_id << 4));
PR1("BDR");
if (bdr_ok) {
/*
* Do ST0x arbitration.
*
* De-assert SCSI enable bit.
* Write my SCSI id to port.
* Start arbitration.
*/
sfbyte (ss_csr, WC_ENABLE_PRTY);
sfbyte (ss_dat, host_id);
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ARBITRATE);
/*
* SCSI spec says there is "no maximum" to the wait for
* arbitration complete.
*/
if (! bus_wait (RS_ARBIT_COMPL << 8 | RS_ARBIT_COMPL)) {
bdr_ok = 0;
}
}
/*
* Arbitration complete. Now select, with ATN to allow messages.
*/
if (bdr_ok) {
sfbyte (ss_dat, host_id | (1 << s_id)); /* Write both SCSI id's */
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION | WC_SELECT);
if (! bus_wait (RS_BUSY << 8 | RS_BUSY))
bdr_ok = 0;
}
if (bdr_ok) {
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION);
if (! bus_wait (((RS_REQUEST | RS_CTRL_DATA | RS_I_O | RS_MESSAGE) << 8)
| (RS_REQUEST | RS_CTRL_DATA | RS_MESSAGE)))
bdr_ok = 0;
}
if (bdr_ok) {
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI);
sfbyte (ss_dat, MSG_DEV_RESET);
if (! bus_wait ((0xFF << 8) | 0))
bdr_ok = 0;
}
return bdr_ok;
}
/*
* chk_reconn ()
*
* Check SELECT to see if any SCSI device has tried to reconnect to the host
* adapter. Called if there is an interrupt, and by the timer in case
* we somehow lose an interrupt.
*
* Return -1 if no reselect detected, or the SCSI ID of the reselecting
* target if there is one.
*/
static int
chk_reconn ()
{
unchar csr, dat;
int s_id = -1;
csr = ffbyte (ss_csr);
if (csr & (RS_SELECT | RS_I_O)) {
dat = ffbyte (ss_dat);
if ((dat & host_id) && (dat & NSDRIVE)) {
dat &= ~ host_id;
s_id = 0;
while (dat >>= 1)
s_id ++;
}
}
return s_id;
}
/*
* ss_mach ()
*
* Gives a distinct state machine for each target device.
*/
void
ss_mach (s_id)
int s_id;
{
ss_type * ssp = ss [s_id];
BUF * bp;
do_sst_op = 1; /* plan to run this routine again in most cases */
while (do_sst_op) {
bp = ssp->bp; /* nonpolled () below can change ssp->bp */
switch (ssp->state) {
/*
* Polling states execute whether ssp->waiting or not.
*/
case SST_POLL_ARBITN:
PR3("XPAR");
if (ffbyte (ss_csr) & RS_ARBIT_COMPL) {
ssp->waiting = 0;
if (host_ident (s_id, 1))
do_connect (s_id);
else
recover (s_id, RV_P_TIMEOUT);
} else {
if (ssp->expired) {
ssp->expired = 0;
recover (s_id, RV_A_TIMEOUT);
} else
do_sst_op = 0;
}
break;
case SST_POLL_RESELECT:
PR3("XPRS");
if (TGT_RSEL) {
ssp->waiting = 0;
if (host_claimed == -1)
host_claimed = s_id;
else if (host_claimed != s_id) {
#if (DEBUG >= 1)
printf ("%d->%d ", host_claimed, s_id);
#endif
}
if (rsel_handshake ()) {
do_connect (s_id);
} else {
recover (s_id, RV_P_TIMEOUT);
}
} else { /* Reselect poll is negative */
if (ssp->expired) {
ssp->expired = 0;
recover (s_id, RV_R_TIMEOUT);
} else
do_sst_op = 0;
}
break;
case SST_POLL_BEGIN_IO:
PR3("XPBI");
if (bp == NULL)
ssp->state = SST_DEQUEUE;
else {
/*
* At this point a SCSI command is about to
* be initiated. It may be a retry.
*/
if (host_claimed == -1 && BUS_FREE && BUS_FREE) {
ssp->waiting = 0;
init_pointers (s_id);
if (start_arb ()) {
host_claimed = s_id;
if (host_ident (s_id, 1)) {
do_connect (s_id);
} else {
recover (s_id, RV_P_TIMEOUT);
}
} else {
/*
* If arbitration does not succeed right away, it is usually
* because another drive is trying to reselect the host.
*/
set_timeout (s_id, DELAY_ARB);
}
} else { /* host busy or bus not free */
int o_id;
if ((o_id = chk_reconn ()) != -1)
defer (dummy_reconn, s_id);
++ ssp->avl_count;
if (ssp->avl_count >= MAX_AVL_COUNT)
recover (s_id, RV_BF_TIMEOUT);
else
set_timeout (s_id, DELAY_BSY);
}
}
break;
default:
if (ssp->waiting)
do_sst_op = 0;
else {
/*
* Nonpolling states execute only if no
* target timer is running.
*/
nonpolled (s_id);
}
} /* endswitch */
} /* endwhile */
}
/*
* nonpolled ()
*
* Part of ss_mach () - handling of nonpolling states is taken out simply
* for readability.
*/
static void
nonpolled (s_id)
int s_id;
{
ss_type * ssp = ss [s_id];
BUF * bp = ssp->bp;
struct fdisk_s * fdp;
int partition;
dev_t dev;
switch (ssp->state) {
case SST_BUS_DEV_RESET:
PR3("XBDR");
if (bus_dev_reset (s_id)) {
do_sst_op = 0;
set_timeout (s_id, DELAY_BDR);
ssp->state = SST_REQ_SENSE;
} else
recover (s_id, RV_P_TIMEOUT);
break;
case SST_DEQUEUE:
if (bufq_rd_head (s_id) != NULL && ! ssp->busy) {
PR3("XDQU");
ssp->busy = 1;
bp = bufq_rm_head (s_id);
ssp->bp = bp;
dev = bp->b_dev;
partition = DEV_PARTN (dev);
if (dev & SDEV)
partition = WHOLE_DRIVE;
fdp = ssp->parmp;
if (partition != WHOLE_DRIVE)
ssp->bno = fdp [partition].p_base + bp->b_bno;
else
ssp->bno = bp->b_bno;
if (bp->b_req == BREAD)
ssp->cmdbuf [0] = ScmdREADEXTENDED;
else
ssp->cmdbuf [0] = ScmdWRITEXTENDED;
ssp->cmdbuf [1] = 0;
ssp->cmdbuf [2] = ssp->bno >> 24;
ssp->cmdbuf [3] = ssp->bno >> 16;
ssp->cmdbuf [4] = ssp->bno >> 8;
ssp->cmdbuf [5] = ssp->bno;
ssp->cmdbuf [6] = 0;
ssp->cmdbuf [7] = 0;
ssp->cmdbuf [8] = 1;
ssp->cmdbuf [9] = 0;
ssp->cmdlen = G1CMDLEN;
init_pointers (s_id);
ssp->bdr_count = 0;
ssp->bsy_count = 0;
ssp->try_count = 0;
ssp->state = SST_POLL_BEGIN_IO;
} else /* queue is empty or ssp->busy */
do_sst_op = 0;
break;
case SST_HIPRI_RESET:
case SST_LOPRI_RESET:
PR1("XRST");
/*
* SST_LOPRI_RESET is same as SST_HIPRI_RESET for now.
* Later, can implement a delay to allow other targets to
* finish pending operations.
*/
if (host_claimed == s_id || host_claimed == -1) {
host_claimed = s_id;
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_SCSI_RESET); /* reset ON */
ssp->state = SST_RESET_OFF;
set_timeout (s_id, DELAY_RST);
PR1("+");
} else
set_timeout (s_id, DELAY_RST);
break;
case SST_REQ_SENSE:
PR1("XRQS");
/*
* Come here at end of SCSI Bus reset (and at other times).
* If we have host claimed, release it.
*/
if (host_claimed == s_id)
host_claimed = -1;
if (req_sense (s_id))
ssp->state = SST_POLL_BEGIN_IO;
else
recover (s_id, RV_P_TIMEOUT);
break;
case SST_RESET_OFF:
PR3("XRFF");
sfbyte (ss_csr, WC_ENABLE_PRTY); /* reset OFF */
ssp->state = SST_REQ_SENSE;
set_timeout (s_id, DELAY_RST);
} /* endswitch */
}
/*
* start_arb ()
*
* return 1 if host adapter returned Arbitration Complete within allotted
* number of tries, else 0
*/
static int
start_arb ()
{
int ret = 0;
int poll_ct;
sfbyte (ss_csr, WC_ENABLE_PRTY);
sfbyte (ss_dat, host_id);
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ARBITRATE);
/*
* SCSI spec says there is "no maximum" to the wait for arbitration
* complete.
*/
for (poll_ct = 0; poll_ct < HIPRI_RETRIES; poll_ct ++) {
if (ffbyte (ss_csr) & RS_ARBIT_COMPL) {
ret = 1;
break;
} else if (chk_reconn () != -1) {
sfbyte (ss_csr, WC_ENABLE_PRTY);
break;
}
}
#if (DEBUG >= 1)
if (! ret)
PR1("oSA");
#endif
return ret;
}
/*
* host_ident ()
*
* This routine is the bridge in a SCSI bus cycle between Abitration
* Complete and the Information Transfer phases.
*
* return 1 if everything went ok, 0 in case of timeout
*/
static int
host_ident (s_id, disconnect)
int s_id;
int disconnect;
{
int ret = 0;
/*
* Arbitration complete. Now select, with ATN to allow messages.
*/
sfbyte (ss_dat, host_id | (1 << s_id)); /* Write both SCSI id's */
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION | WC_SELECT);
if (bus_wait (RS_BUSY << 8 | RS_BUSY)) {
/*
* Assert ATTN so target expects incoming message byte.
*/
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION);
if (bus_wait (((RS_REQUEST | RS_CTRL_DATA | RS_I_O | RS_MESSAGE) << 8)
| (RS_REQUEST | RS_CTRL_DATA | RS_MESSAGE))) {
if (disconnect) {
sfbyte (ss_dat, MSG_IDENT_DC);
} else {
sfbyte (ss_dat, MSG_IDENTIFY);
}
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ENABLE_IRPT);
ret = 1;
} else {
PR1("oHI2");
}
} else {
PR1("oHI1");
}
return ret;
}
/*
* rsel_handshake ()
*
* After Reselect is detected, a couple steps are needed before entering
* Information Transfer phases. This routine does those steps.
*
* return 1 if ok, 0 in case of timeout.
*/
static int
rsel_handshake ()
{
int ret = 0;
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_BUSY);
if (bus_wait (RS_SELECT << 8 | 0)) {
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI);
ret = 1;
}
return ret;
}
/*
* set_timeout ()
*
* Start a timer so as not to wait forever in case something goes wrong while
* waiting for an event. Available delays are:
*
* DELAY_ARB - wait for arbitration complete
* DELAY_BDR - allow settling time after Bus Device Reset
* DELAY_BSY - wait for not HOST_BUSY and bus free
* DELAY_RES - wait for reselect by target
* DELAY_RST - allow settling times when doing SCSI Bus Reset
*
* Second argument is number of clock ticks to wait until timer expiration.
*/
static void
set_timeout (s_id, delay)
int s_id, delay;
{
ss_type * ssp = ss [s_id];
ssp->expired = 0;
ssp->waiting = 1;
do_sst_op = 0;
timeout (& ssp->tim, delay, stop_timeout, s_id);
}
/*
* stop_timeout ()
*
* Called on expiration of the timer for a given target.
* Don't expire a timer if it's no longer active.
*/
static void
stop_timeout (s_id)
int s_id;
{
ss_type * ssp = ss [s_id];
if (ssp->waiting) {
ssp->expired = 1;
ssp->waiting = 0;
}
ss_mach (s_id);
}
/*
* init_pointers ()
*
* Initialize command and data pointers when starting (or restarting)
* a block I/O command.
*/
static void
init_pointers (s_id)
int s_id;
{
ss_type * ssp = ss [s_id];
BUF * bp = ssp->bp;
ssp->cmdstat = -1;
ssp->cmd_bytes_out = 0;
ssp->avl_count = 0;
}
/*
* recover ()
*
* This routine is called directly or indirectly from ss_mach (). It
* determines what to do when the interface fails to behave as desired.
*
* Arguments are the SCSI id of the target HDC and an error type.
* Error types are:
*
* RV_A_TIMEOUT (arbitration timeout)
* Host adapter takes too long to respond with arbitration complete.
*
* RV_P_TIMEOUT (protocol timeout)
* Timeout waiting for desired SCSI bus status while connected to a target.
*
* RV_R_TIMEOUT (reconnect timeout)
* Timeout after target disconnects, waiting for reconnect.
*
* RV_BF_TIMEOUT (bus free timeout)
* Waited too long for host not busy and BUS_FREE.
*
* RV_CS_BUSY (target device busy)
* Command status returned was Busy.
*
* RV_CS_CHECK (target device check)
* Command status returned was CHECK.
*
* Whenever an error occurs, one of the above inputs, together with the SCSI id
* of the target, is sent to the recovery process. The recovery process in turn
* programs the next state for the machine.
*/
static void
recover (s_id, errtype)
int s_id;
RV_TYPE errtype;
{
ss_type * ssp = ss [s_id];
BUF * bp = ssp->bp;
#if (DEBUG >= 1)
int foo;
if ((foo = chk_reconn ()) != -1)
printf ("HONK%d ", foo);
#endif
++ ssp->try_count;
if (ssp->try_count < MAX_TRY_COUNT) {
switch (errtype) {
case RV_CS_BUSY:
++ ssp->bsy_count;
if (ssp->bsy_count < MAX_BSY_COUNT) {
ssp->state = SST_POLL_BEGIN_IO;
set_timeout (s_id, DELAY_BSY);
} else
ssp->state = SST_BUS_DEV_RESET;
break;
case RV_CS_CHECK:
ssp->state = SST_REQ_SENSE;
break;
case RV_P_TIMEOUT:
/* fall thru */
case RV_R_TIMEOUT:
++ ssp->bdr_count;
if (ssp->bdr_count < MAX_BDR_COUNT)
ssp->state = SST_BUS_DEV_RESET;
else
ssp->state = SST_LOPRI_RESET;
break;
case RV_BF_TIMEOUT:
/* fall thru */
case RV_A_TIMEOUT:
ssp->state = SST_HIPRI_RESET;
}
} else { /* try_count >= MAX_TRY_COUNT */
if (bp) {
bp->b_flag |= BFERR;
printf ("(%d,%d): ", major (bp->b_dev), minor (bp->b_dev));
printf ("%s error bno =%ld\n",
(bp->b_req == BREAD) ? "read" : "write",
bp->b_bno);
}
ss_finished (s_id);
}
}
/*
* ss_finished
*
* Release current I/O buffer to the O / S.
*/
static void
ss_finished (s_id)
int s_id;
{
ss_type * ssp = ss [s_id];
BUF * bp = ssp->bp;
int go_again = 1;
if (host_claimed == s_id)
host_claimed = -1;
ssp->busy = 0;
if (bp) {
if (!(bp->b_flag & BFERR))
bp->b_resid -= BSIZE;
if ((bp->b_flag & BFERR) || bp->b_resid == 0) {
ssp->bp = NULL;
bdone (bp);
go_again = 0;
}
}
if (go_again) {
ssp->state = SST_POLL_BEGIN_IO;
ssp->bdr_count = 0;
ssp->bsy_count = 0;
ssp->try_count = 0;
ssp->bno ++;
ssp->cmdbuf [2] = ssp->bno >> 24;
ssp->cmdbuf [3] = ssp->bno >> 16;
ssp->cmdbuf [4] = ssp->bno >> 8;
ssp->cmdbuf [5] = ssp->bno;
} else {
/*
* After processing a kernel I/O request, stop the
* state machine for the current id. Then start
* this or some other machine which has a request
* pending.
*/
do_sst_op = 0;
ssp->state = SST_DEQUEUE;
next_req (s_id);
}
}
/*
* next_req ()
*
* Given the SCSI id where an I/O request just completed, start handling
* another I/O request - which may be for the same or other SCSI id.
* For now, use round-robin scheduling.
*/
static void
next_req (s_id)
int s_id;
{
int next_id = s_id;
while (1) {
next_id ++;
if (next_id >= MAX_SCSI_ID)
next_id = 0;
if (ss [next_id] != NULL &&
(ss [next_id]->state != SST_DEQUEUE || bufq_rd_head (next_id))) {
defer (ss_mach, next_id);
break;
}
if (next_id == s_id)
break;
}
}
/*
* do_connect ()
*
* This function is called when the host is successfully connected to
* the target. It invokes information transfer protocol and then sets
* up some sort of recovery unless the command completed successfully
* or there was a normal disconnect.
*/
static void
do_connect (s_id)
int s_id;
{
int result;
ss_type * ssp = ss [s_id];
result = far_info_xfer (s_id);
if (! result)
recover (s_id, RV_P_TIMEOUT);
else if (ssp->msg_in == MSG_DISCONNECT) {
ssp->state = SST_POLL_RESELECT;
set_timeout (s_id, DELAY_RES);
#if 0
if (host_claimed == s_id)
host_claimed = -1;
#endif
} else if (ssp->msg_in == MSG_CMD_CMPLT && ssp->cmdstat == CS_GOOD)
ss_finished (s_id);
else if (ssp->cmdstat == CS_BUSY)
recover (s_id, RV_CS_BUSY);
else if (ssp->cmdstat == CS_CHECK)
recover (s_id, RV_CS_CHECK);
else /* something else went wrong */
recover (s_id, RV_P_TIMEOUT);
}
/*
* local_info_xfer ()
*
* Do bus cycle information transfer phases.
* Transfer is for a command which will produce local results in the driver.
* Other ...info_xfer routine handles kernel block I/O commands.
*
* Return 1 if transfer succeeded, else 0.
*
*/
static int
local_info_xfer (cmdbuf, cmdlen, inbuf, inlen, outbuf, outlen)
unchar * cmdbuf, * inbuf, * outbuf;
uint cmdlen, inlen, outlen;
{
int bus_timeout;
unchar phase_type;
int s;
int cmd_bytes_out = 0;
int data_bytes_in = 0;
int data_bytes_out = 0;
int ret = 0;
int xfer_good = 1;
int cmdstat = -1;
int msg_in = -1;
#if (DEBUG >= 1)
int x, xct = 0;
unchar xch [100];
#endif
s = sphi ();
while (req_wait (& bus_timeout) && xfer_good) {
phase_type = ffbyte (ss_csr) & (RS_MESSAGE | RS_I_O | RS_CTRL_DATA);
#if (DEBUG >= 1)
if (xct < 100)
xch [xct ++]= phase_type;
#endif
switch (xpmod (phase_type)) {
case XP_MSG_IN:
msg_in = ffbyte (ss_dat);
switch (msg_in){
case MSG_CMD_CMPLT:
case MSG_DISCONNECT:
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT);
break;
}
break;
case XP_MSG_OUT:
/*
* This case shouldn't happen. We weren't
* asserting ATTENTION.
*/
sfbyte (ss_dat, MSG_NOP);
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI);
break;
case XP_STAT_IN:
cmdstat = ffbyte (ss_dat);
break;
case XP_CMD_OUT:
/*
* Ship out command bytes.
*/
if (cmd_bytes_out < cmdlen) {
sfbyte (ss_dat, cmdbuf [cmd_bytes_out ++]);
#if 1
/*
* If just sent last byte, allow interrupts.
*/
if (cmd_bytes_out == cmdlen) {
spl (s);
s = sphi ();
}
#endif
} else { /* This case should not happen. */
xfer_good = 0;
}
break;
case XP_DATA_IN:
/*
* If caller's buffer has room, keep incoming
* data byte. Else toss it.
*/
if (data_bytes_in < inlen) {
#if 0
do {
inbuf [data_bytes_in ++] = ffbyte (ss_dat);
} while (data_bytes_in < inlen);
#else
inbuf [data_bytes_in ++] = ffbyte (ss_dat);
#endif
} else
xfer_good = 0;
break;
case XP_DATA_OUT:
/*
* Copy output buffer bytes to data register.
*/
if (data_bytes_out < outlen) {
sfbyte (outbuf [data_bytes_out ++], ss_dat);
} else { /* This case should not happen. */
xfer_good = 0;
}
break;
default:
break;
} /* endswitch */
}
spl (s);
if (bus_timeout) {
PR1("oLX1");
} else if (! xfer_good) {
PR1("oLX2");
} else if (cmdstat != CS_GOOD) {
PR1("oLX3");
#if (DEBUG >= 1)
printf ("cmdstat =%x ", cmdstat);
#endif
} else
ret = 1;
#if (DEBUG >= 1)
if (! ret) {
printf ("csr =%x ", ffbyte (ss_csr));
printf ("xct =%d ", xct);
for (x = 0; x < xct; x ++)
printf ("%x ", xch [x]);
}
#endif
return ret;
}
/*
* scsireset ()
*
* Reset the SCSI bus.
* Allow settling time when turning reset on / off.
* Settling times were determined empirically.
* Each tick is 10 msec.
*/
static void
scsireset ()
{
int s;
#if (DEBUG >= 1)
printf ("scsireset ");
#endif
s = splo ();
sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_SCSI_RESET);
ssdelay (RESET_TICKS);
sfbyte (ss_csr, WC_ENABLE_PRTY);
ssdelay (RESET_TICKS);
spl (s);
}
/*
* ssdelay ()
*
* Delay for some number of arbitrary ticks.
*
* Using sleep () causes a panic if this driver is linked to the kernel,
* even though this routine is called only via ssload ().
*/
static void
ssdelay (ticks)
int ticks;
{
#if 0
timeout (& delay_tim, ticks, wakeup, (int)& delay_tim);
sleep ((char *)& delay_tim, CVPAUSE, IVPAUSE, SVPAUSE);
#else
int i, j;
for (i = 0; i < ticks; i ++)
for (j = 0; j < LOAD_DELAY; j ++);
#endif
}
/*
* init_call ()
*
* Call SCSI command function during initialization, with error recovery.
* If the simple command fails, try a Bus Device Reset, then SCSI Bus reset.
*/
static int
init_call (fn, s_id, buf)
int (* fn)();
int s_id;
unchar * buf;
{
int ret = 1;
int i;
int o_id;
int s;
s = sphi ();
for (i = 0; i < 2; i ++) {
o_id = chk_reconn ();
if (o_id != -1)
dummy_reconn (s_id);
if ((* fn) (s_id, buf))
goto init_call_done;
req_sense (s_id);
if ((* fn) (s_id, buf))
goto init_call_done;
if (bus_dev_reset (s_id)) {
ssdelay (RESET_TICKS);
req_sense (s_id);
if ((* fn) (s_id, buf))
goto init_call_done;
}
scsireset ();
req_sense (s_id);
if ((* fn) (s_id, buf))
goto init_call_done;
}
ret = 0;
init_call_done:
spl (s);
return ret;
}
/*
* xpmod ()
*
* Command / Data and Message bits are swapped on-board (outside the chip)
* on older Future Domain host boards.
*/
static unchar
xpmod (oldphase)
unchar oldphase;
{
unchar ret = oldphase;
if (swap_status_bits) {
ret &= ~(RS_CTRL_DATA | RS_MESSAGE);
if (oldphase & RS_MESSAGE)
ret |= RS_CTRL_DATA;
if (oldphase & RS_CTRL_DATA)
ret |= RS_MESSAGE;
}
return ret;
}
/*
* tbparms ()
*
* If the drive table has already been patched for this SCSI id, do nothing.
* Otherwise, given the real-mode drive number (tbnum) and the SCSI id (s_id),
* look for drive parameters from tertiary boot, and copy into driver
* data block if we find them.
*/
static void
tbparms (tbnum, s_id)
int tbnum, s_id;
{
FIFO * ffp;
typed_space * tp;
extern typed_space boot_gift;
cmn_err (CE_NOTE, "tbparms (%d, %d) ", tbnum, s_id);
if (_sd_drv_parm [s_id].ncyl == 0
&& F_NULL != (ffp = fifo_open (& boot_gift, 0))) {
while (tp = fifo_read (ffp)) {
BIOS_DISK * bdp = (BIOS_DISK *)tp->ts_data;
cmn_err (CE_NOTE, "ts_type = %x, drive = %d", tp->ts_type, bdp->dp_drive);
if ((T_BIOS_DISK == tp->ts_type) &&
(tbnum == bdp->dp_drive) ) {
_sd_drv_parm [s_id].ncyl = bdp->dp_cylinders;
_sd_drv_parm [s_id].nhead = bdp->dp_heads;
_sd_drv_parm [s_id].nspt = bdp->dp_sectors;
break;
}
}
fifo_close (ffp);
}
}
CON sscon = {
DFBLK | DFCHR, /* Flags */
SCSI_MAJOR, /* Major index */
ssopen, /* Open */
ssclose, /* Close */
ssblock, /* Block */
ssread, /* Read */
sswrite, /* Write */
ssioctl, /* Ioctl */
NULL, /* Powerfail */
sswatch, /* Timeout */
ssload, /* Load */
ssunload, /* Unload */
NULL /* Poll */
};