|
|
coherent
/*
* io.386/ss.c
*
* Device driver for Seagate ST01/ST02 scsi host adapters.
*
* Revised: Wed May 26 16:57:51 1993 CDT
*/
/*
* 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
*/
/*
* 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
/*
* Includes.
*/
#include <sys/coherent.h>
#ifdef _I386
#include <sys/fakeff.h>
#include <sys/dmac.h>
#endif
#include <sys/io.h>
#include <sys/sched.h>
#include <sys/uproc.h>
#include <sys/proc.h>
#include <sys/con.h>
#include <sys/stat.h>
#include <sys/devices.h> /* SCSI_MAJOR */
#include <errno.h>
#include <sys/fdisk.h>
#include <sys/hdioctl.h>
#include <sys/buf.h>
#include <sys/scsiwork.h>
#include <sys/typed.h>
/*
* Definitions.
* Constants.
* Macros with argument lists.
* Typedefs.
* Enums.
*/
#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;
typedef struct {
uint ncyl;
unchar nhead;
unchar nspt;
} drv_parm_type;
/*
* Functions.
* Import Functions.
* Export Functions.
* Local Functions.
*/
/* 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();
extern int nulldev();
extern int nonedev();
#ifndef _I386
extern unsigned char ffbyte();
#endif
static void ssopen(); /* CON functions */
static void ssclose();
static void ssblock();
static void ssread();
static void sswrite();
static int ssioctl();
static void sswatch();
static void ssload();
static void ssunload();
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();
/*
* Global Data.
* Import Variables.
* Export Variables.
* Local Variables.
*/
extern short n_atdr; /* set by atcount() before any load routines run */
CON sscon = {
DFBLK|DFCHR, /* Flags */
SCSI_MAJOR, /* Major index */
ssopen, /* Open */
ssclose, /* Close */
ssblock, /* Block */
ssread, /* Read */
sswrite, /* Write */
ssioctl, /* Ioctl */
nulldev, /* Powerfail */
sswatch, /* Timeout */
ssload, /* Load */
ssunload, /* Unload */
nulldev /* Poll */
};
/* Patch these Export Variables to configure the driver. */
/*
* 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
*/
uint NSDRIVE = 0x0001;
uint SS_INT = 5; /* ST0[12] use either IRQ3 or IRQ5 */
uint SS_BASE = 0xCA00; /* Segment addr of ST0x communication area */
/* ncyl, nhead, nspt */
drv_parm_type drv_parm[MAX_SCSI_ID] = {
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0}
};
static BUF dbuf; /* For raw I/O */
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);
#ifdef _I386
ss_fp = map_pv(ss_base, (fsize_t)SS_SEL_LEN);
#else /* _I386 */
ss_fp = ptov(ss_base, (fsize_t)SS_SEL_LEN);
#endif /* _I386 */
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
kclear(ss_tbl, 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 (n_atdr counts these)
* then proceed with SCSI drives in increasing id number order.
*/
tbnum = n_atdr; /* 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.
*/
#ifdef _I386
unmap_pv(ss_fp);
#else /* _I386 */
vrelse(ss_fp);
#endif /* _I386 */
/*
* 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)
register 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";
u.u_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";
u.u_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");
u.u_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";
u.u_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";
#ifdef _I386
u.u_error = EINVAL;
#else
u.u_error = EBADFMT;
#endif /* _I386 */
goto bad_open;
}
if (partn != WHOLE_DRIVE && fdp[partn].p_size == 0) {
msg = "partition not found";
u.u_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;
goto end_open;
bad_open:
devmsg(dev, msg);
end_open:
return;
}
/*
* 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;
{
T_PIGGY( 0x20, printf("ssread(iop->io.vbase: %x)", iop->io.vbase); );
ioreq( &dbuf, 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;
{
T_PIGGY( 0x20, printf("sswrite(iop->io.vbase: %x)", iop->io.vbase); );
ioreq( &dbuf, 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 int ssioctl(dev, cmd, vec)
register dev_t dev;
int cmd;
char * vec;
{
int ret = 0;
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] = drv_parm[s_id].ncyl;
hdparm.nhead = drv_parm[s_id].nhead;
hdparm.nspt = 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);
ret = 0;
break;
case HDSETA:
/*
* Set hard disk attributes.
*/
PR3("HDSETA");
fdp = ssp->parmp;
ukcopy(vec, &hdparm, sizeof hdparm);
drv_parm[s_id].ncyl = *(short *)&hdparm.ncyl[0];
drv_parm[s_id].nhead = hdparm.nhead;
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
ret = 0;
break;
default:
u.u_error = EINVAL;
ret = -1;
}
return ret;
}
/*
* 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)
register BUF *bp;
{
struct fdisk_s *fdp;
int partition, drive, s_id;
dev_t dev;
ss_type * ssp;
unchar * msg = NULL;
T_PIGGY( 0x20,
printf("ssblock(bp->b_vaddr: %x, bp->b_paddr: %x)",
bp->b_vaddr, bp->b_paddr);
);
/*
* 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)) {
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;
}
}
/*
* Check for read at end of partition.
* (Need to return with b_resid = BSIZE to signal end of volume.)
*/
else if ((bp->b_req == BREAD) && (bp->b_bno == fdp[partition].p_size)) {
goto bad_blk;
}
/*
* Check for read past end of partition.
*/
else if ( (bp->b_bno + (bp->b_count/BSIZE))
> fdp[partition].p_size ) {
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);
goto end_blk;
/*
* 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);
end_blk:
return;
}
/*
* 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);
goto dum_done;
}
if (!rsel_handshake())
goto dum_done;
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);
dum_done:
return;
}
/*
* 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 = ((sscon.c_mind << 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 (drv_parm[s_id].ncyl == 0) {
drv_parm[s_id].ncyl = cyls;
drv_parm[s_id].nhead = heads;
drv_parm[s_id].nspt = spt;
} else {
printf("Logical: cylinders=%d ",
drv_parm[s_id].ncyl);
printf("heads=%d ", drv_parm[s_id].nhead);
printf("spt=%d\n", 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 {
#if 0
int getbval;
block_done=1;
PR4("DI");
if(getbval = ss_getb(ss_dat,
#ifdef _I386
bp->b_vaddr + xfer_count)) {
#else
bp->b_faddr + xfer_count)) {
#endif /* _I386 */
xfer_good = 0;
#if (DEBUG >= 1)
printf("getb=%d ", getbval);
#endif
}
#else
block_done=1;
#ifdef _I386
if (BSIZE != xpcopy(ss_dat, bp->b_paddr + xfer_count,
BSIZE, SEG_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;
}
#else
ffcopy(ss_dat, bp->b_faddr + xfer_count, BSIZE);
#endif /* _I386 */
#endif /* 0 */
}
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 {
#if 0
int putbval;
block_done=1;
PR4("DO");
if (putbval = ss_putb(ss_dat,
#ifdef _I386
bp->b_vaddr + xfer_count)) {
#else
bp->b_faddr + xfer_count)) {
#endif /* _I386 */
xfer_good = 0;
#if (DEBUG >= 1)
printf("putb=%d ", putbval);
#endif
}
#else
block_done=1;
#ifdef _I386
if (BSIZE != pxcopy(bp->b_paddr + xfer_count, ss_dat,
BSIZE, SEG_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;
}
#else
ffcopy(bp->b_faddr + xfer_count, ss_dat, BSIZE);
#endif /* _I386 */
#endif
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 bdr_ok = 1;
int dev = ((sscon.c_mind << 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]
&& (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;
if (drv_parm[s_id].ncyl == 0
&& F_NULL != (ffp = fifo_open(&boot_gift, 0))) {
if (tp = fifo_read(ffp)) {
BIOS_DISK *bdp = (BIOS_DISK *)tp->ts_data;
if ((T_BIOS_DISK == tp->ts_type) &&
(tbnum == bdp->dp_drive) ) {
drv_parm[s_id].ncyl = bdp->dp_cylinders;
drv_parm[s_id].nhead = bdp->dp_heads;
drv_parm[s_id].nspt = bdp->dp_sectors;
}
}
fifo_close(ffp);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.