Coherent4.2.10/conf/haiss/src/haiss.c

Compare this file to the similar file:
Show the results in this format:

/***********************************************************************
 * haiss.c --   haiscsi compliant Host adapter routines for the Seagate
 *              ST01/ST02 and 8-bit Future Domain controllers.
 *
 *  These controllers use memory mapped I/O like the IBM professional
 *  Graphics array controler.
 *
 *  Copyright (c) 1993 Christopher Sean Hilton - All rights reserved.
 *  
 *  Last Modified: Wed Jul  6 15:04:35 1994 by [kroot]
 */

/***********************************************************************
 * $Id$
 *
 * $Log$
 */

static char rcsid[] = "@(#) haiss.c $Revision$";

#include <sys/coherent.h> 
#include <sys/con.h> 
#include <sys/stat.h>
#include <kernel/timeout.h> 
#include <sys/cmn_err.h> 
#include <sys/haiscsi.h>

/* A forward declaration masquerading as an external. */
extern haft_t	ss_haft;

#define LOCAL	 static  
#define Register register	/* For stupid compilers */

/***********************************************************************
 * The Seagate ST01/02 and Future Domain 8xx 8-bit host adapters use 
 * the same memory mapped layout and SCSI Bus controller chip so they 
 * program similarly.  The differences are: 
 * 
 * Controller            CSR(hex)  Data(hex)    SCSI ID 
 * -------------------- ---------- ---------- ---------- 
 * ST01/02                   1a00       1c00          7 
 * TMC-845 Type		     1c00       1e00          6 
 * TMC-840 Type              1a00       1c00          6 
 * 
 * These differences are taken care of in the ss_load() routine so 
 * the rest of the program need not concern itself with them.  
 */

#define SS_SRAMSTART	0x1800	/* Start of static RAM for test */
#define SS_SRAMEND	0x187f	/* End of Static Ram */ 
#define ST01_CSR	0x1a00	/* ST01 Control/Status Register */ 
#define ST01_DAT	0x1c00	/* ST01 Data Register */ 
#define FD845_CSR	0x1c00	/* Future Domain Control/Status Register */
#define FD845_DAT	0x1e00	/* Future Domain Data Register */
#define SS_RAMSIZE	0x2000	/* Total size of memory mapped area */

#define ST_ID		7 
#define FD_ID		6

/* Command bits within the CSR */

#define RST_OUT 	bit(0)	/* SCSI Reset Command */ 
#define SEL_OUT 	bit(1)	/* SCSI Select */ 
#define BUSY_OUT	bit(2)	/* SCSI Busy */ 
#define ATTN_OUT	bit(3)	/* SCSI Attention */ 
#define STARTARB	bit(4)	/* Start Arbitration */ 
#define ENPARITY	bit(5)	/* Enable Parity */ 
#define ENINTR		bit(6)	/* Enable Interrupt */ 
#define ENSCSI		bit(7)	/* Enable SCSI bus */

#define HAIDLE		(ENPARITY | ENINTR)

/* Status bits within the CSR */

#define BUSY_IN 	bit(0)	/* SCSI Busy signal */ 
#define MSG_IN		bit(1)	/* SCSI Message signal */ 
#define IO_IN		bit(2)	/* SCSI I/O signal */ 
#define CD_IN		bit(3)	/* SCSI Control/Data signal */ 
#define REQ_IN		bit(4)	/* SCSI Request signal */ 
#define SEL_IN		bit(5)	/* SCSI Select signal */ 
#define PARITYERR	bit(6)	/* Parity Error. */ 
#define ARBITCMP	bit(7)	/* Arbitration Complete */

#define RESELECT	(SEL_IN | IO_IN) /* SCSI reselect phase in progress */

/***********************************************************************
 * SCSI Bus IO phases: use with IOPHASE below to match what the bus 
 * is trying to do. 
 */

#define XP_DATAOUT	(0) 		/* Init -> Trgt Data */
#define XP_DATAIN	(IO_IN) 	/* Trgt -> Init Data */
#define XP_COMMAND	(CD_IN) 	/* Init -> Trgt CDB */ 
#define XP_STATUS	(CD_IN|IO_IN)	/* Trgt -> Init CDB Status */ 
#define XP_MSGOUT	(MSG_IN|CD_IN)	/* Init -> Trgt Message */ 
#define XP_MSGIN	(MSG_IN|CD_IN|IO_IN)/* Trgt -> Init Message */

/* SCSI Message Definitions */

#define MSG_CC		0x00	/* Command complete */ 
#define MSG_SAVEDPTR	0x02	/* Save data pointers */ 
#define MSG_RSTRDPTR	0x03	/* Restore data pointers */ 
#define MSG_DISCONNECT	0x04	/* Disconnect */ 
#define MSG_ABORT	0x06	/* Abort the current operation */ 
#define MSG_REJECT	0x07	/* Message reject (last message invalid) */
#define MSG_NOP 	0x08	/* No operation */ 
#define MSG_LCC		0x0a	/* Linked command complete */ 
#define MSG_LCCF	0x0b	/* Linked command complete (with flag) */ 
#define MSG_BUSDEVRST	0x0c	/* Bus device reset */ 
#define MSG_IDENTIFY	0x80	/* Identify message (Family) */ 
#define   ID_DISCONNECT	0x40	/* Disconnect Flag for Identify message */

/***********************************************************************
 * SCSI Bus Timings: 
 * 
 * These are the official SCSI-1 Bus Timings of the NBA, Get your 
 * gamecard now.  
 */

/*	Event 		Coh Time Event		       Actual Time	*/
/*	(Abrv.)		(usec) 	 Full Name		 (usec) */ 
/*      --------------	-------- -----------------	-----------	*/
#define	ARBIT_DLY	3	/* Arbitration Delay 	 2.200	*/
#define ASSERT_PER	1	/* Assertion period      0.090 	*/
#define BUSCLR_DLY	1	/* Bus Clear Delay	 0.800	*/
#define BUSFREE_DLY	1	/* Bus Free Delay	 0.800	*/
#define BUSSET_DLY	2	/* Bus Set Delay	 1.800	*/
#define BUSSETTLE_DLY	1	/* Bus Settle Delay	 0.400	*/
#define CABLESKEW_DLY	1	/* Cable Skew Delay	 0.010	*/
#define DATARLS_DLY	1	/* Data Release Delay	 0.400	*/
#define DESKEW_DLY	1	/* Deskew Delay		 0.045 */
#define HOLD_TM		1	/* Hold Time		 0.045 */
#define NEGATION_PER	1	/* Negation Period	 0.090	*/
#define RSTHOLD_TM	25	/* Reset Hold Time	 25.000	*/
#define SELABORT_TM	200	/* Selection Abort Time	 200.000 */ 
#define SELTO_DLY	250000	/* Sel. Timeout Delay	250,000.000	*/

#define RETRY_DELAY	3	/* Wait on a busy SCSI bus */ 
#define MAXBUSYCOUNT	120	/* Number of busy bus before reset */

#define REQ_BUSDEVICERST	0x0001	/* Request Bus Device Reset */
#define REQ_ABORTSCSICMD	0x0002	/* Request Abort SCSI command */ 
#define REQ_STARTSCSICMD	0x0004	/* Request Start SCSI */

#define CMD_PENDING		0x0001	/* Command pending */ 
#define CMD_DEVBUSY		0x0002	/* Device is busy */ 
#define CMD_OVERRUN		0x0004	/* Data overrun on command */ 
#define CMD_UNDERRUN		0x0008	/* Data underrun on command */ 
#define CMD_NEEDREQACK		0x0010	/* Command need req/ack handshake */

#define HAST_OK			0x00 
#define HAST_SELTO		0x11
#define HAST_OVERRUN		0x12 
#define HAST_UNDERRUN		0x12 
#define HAST_BUSFREE		0x13
#define HAST_BADBUSPH		0x14 
#define HAST_INVDCDB		0x20


#define MAXSEGCOUNT		16 
#define DATAREGSIZE		512

/***********************************************************************
 * Device states.  
 *
 * These states give an idea of what the device is doing in the context
 * of this host adapter. 
 */

typedef enum { 
    DEV_IDLE = 0,	/* Device is idle in this host's context */ 
    DEV_QUEUED,		/* Device has a command queued and waiting */ 
    DEV_SCSIPROT,	/* Device is in some stage of the SCSI protocol */ 
    DEV_WORKING,	/* Device is working on a request */
    DEV_FINISHED	/* Device is finished with SCSI request */ 
} devstate_t;

typedef struct ssccb_s *ssccb_p;

typedef struct ssccb_s { 
    devstate_t 		c_state; /* Current state of the device. */ 
    unsigned 		c_id;	 /* Command id number */ 
    unsigned		c_reqflags; /* Request flags */
    unsigned		c_cmdflags; /* Command flags */ 
    unsigned char	*c_cdb;	 /* Pointer to cdb to execute */ 
    unsigned 		c_cdblen; /* Length of cdb */ 
    unsigned		c_time;
    unsigned		c_target; 
    srb_p		c_srb;	 /* Current SCSI Request Block */ 
    ssccb_p		c_next;	 /* Next ccb in Request chain */ 
    size_t		c_segcount; /* Number of segments in the s/g list */ 
    size_t		c_dataofs; /* Offset into the current segment */ 
    haisgsegm_p		c_datain; /* pointer to s/g list for datain. */ 
    haisgsegm_p		c_dataout; /* pointer to s/g list for dataout. */ 
    haisgsegm_t		c_sgs[MAXSEGCOUNT]; /* Scatter gather list for xfer */
    unsigned short 	c_scsistatus; /* Status of the SCSI command */ 
} ssccb_t;

static ssccb_p ssdevs[MAXDEVS] = { 
    NULL, 
    NULL, 
    NULL, 
    NULL, 
    NULL, 
    NULL,
    NULL, 
    NULL 
};

static ssccb_t dummy; 
static srb_t dummysrb; 
static ssccb_p normal_queue = NULL,
	       priority_queue = NULL; 		
static unsigned char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };

/***********************************************************************
* Configurable variables - specified in Space.c */ 

enum {
    ST01=0,			/* ST01/02 controller */
    FD845=1,			/* TMC-845/850/860/875/885 */
    FD840=2			/* TMC-840/841/880/881 */ 
};

extern int haiss_type;		/* From the above enum */ 
extern int haiss_intr;		/* IRQ number */ 
extern int haiss_base;		/* Paragraph addr for BIOS */ 
extern int haiss_slowdev;	/* Bitmap of devices needing slow handshake */

extern int HAI_DISK;		/* Bit mapped values where disks are */ 
extern int HAI_TAPE;		/* Bit mapped values for where tapes are */ 
extern int HAI_CDROM;		/* Bit mapped values for where CD-ROMS are */

static int ss_devices;		/* Bitwise-or of HAI device bitmaps. */

static ssccb_p rawccb = NULL;
static unsigned char *ss_vbase = NULL; /* Controller Base selector */ 
static unsigned char *ss_csr = NULL, /* Command/Status register */
                     *ss_dat = NULL; /* Data Register */
static unsigned short hostid;	/* host adapter SCSI Bus address */ 
static unsigned short swap_status_bits = 0; 
static char ss_xfrbuf[DATAREGSIZE]; /* transfer buffer for slow devices */

static TIM sstim;

#define CSR 		(*(volatile unsigned char *) ss_csr)
#define DAT 		(*(volatile unsigned char *) ss_dat)

#define setcsr(v)	(CSR = (v)) 
#define setdat(v)	(DAT = (v))

#define min(a, b)	((a) < (b) ? (a) : (b))

#define reselect()	((CSR & (SEL_IN | IO_IN)) == (SEL_IN | IO_IN))

#define set_host_status(r, newstat)	((r)->hastat = (newstat))

#define DEBUG_TRACE	0 
#define DEBUG_QTRACE	0

/***********************************************************************
 * A squawk function for printing stuff out while debugging.  
 */

#if	DEBUG_TRACE 
#define trace(str)	cmn_err(CE_CONT, "!" (str))
#else 
#define trace(str) 
#endif
  
/***********************************************************************
 *  a quicker squawk function for more difficult bugs.
 */

#if	DEBUG_QTRACE
static char spinnerstr[] = "-/|\\";

#define qtrace(x, y, c)	_chirp((c), 2 * (80 * (y) + (x)))
#define spinner(x, y, i) \
{ if (++(i) >= sizeof(spinnerstr)) i = 0; qtrace((x), (y), spinnerstr[(i)]); }
#else
#define	qtrace(x, y, c)
#endif

/***********************************************************************
 *  Forward declarations for things which must be defined out of order.
 */

#if __USE_PROTO__
LOCAL void	ssintr(void);
#else
LOCAL void	ssintr();
#endif

/***********************************************************************
 *  Utility routines for doing timing functions. All times here are
 *  measured in micro seconds and converted to Coherent Clock ticks.
 *  
 *  One nasty side effect of these timers is that you cannot have two
 *  contexts trying to access them at the same time. They use static
 *  values for masks. This shouldn't be a problem with this design
 *  as only one routine accesses the SCSI bus, and thus needs the timers
 *  and masks, anyway.
 */

#define usec2t0(usec)	(((usec * 11932) + 5000) / 10000)

/*
 * Forward declarations for kernel functions so gcc doesn't complain
 */

int busyWait	__PROTO((int (*)(void), unsigned));
int busyWait2	__PROTO((int (*)(void), unsigned));
vaddr_t map_pv	__PROTO((paddr_t, size_t));
void unmap_pv	__PROTO((vaddr_t));
unsigned kucopy	__PROTO((void *, void *, unsigned));
unsigned ukcopy	__PROTO((void *, void *, unsigned));
void setivec	__PROTO((int, void (*)(void)));
void clrivec	__PROTO((int));
void pxcopy	__PROTO((paddr_t, vaddr_t, size_t, int));
void xpcopy	__PROTO((vaddr_t, paddr_t, size_t, int));
void timeout	__PROTO((TIM *, int, void (*)(), ...));

/***********************************************************************
 *  eventwait() --   Wait for an event (really?)
 *
 *  Wait up to ticks milliseconds for an event to happen. This is better
 *  than using a for loop.
 */

#if __USE_PROTO__
LOCAL int eventwait(int (*event)(), Register unsigned timeout_val)
#else
LOCAL int
eventwait(event, timeout_val)
int 			(*event)();
Register unsigned 	timeout_val;
#endif
{
    if (timeout_val == 0) {
	if (event)
	    return (*event)();
	else
	    return 0;
    }
    else if (timeout_val < 10000)
	return busyWait2(event, usec2t0(timeout_val));
    else
	return busyWait(event, (timeout_val + 5000) / 10000);
}	/* eventwait() */

static unsigned chkmask,	/* shared check value for allbitswait */
		chkevent;	/* shared check value for allbitswait */

#if __USE_PROTO__
LOCAL int allbchk(void)
#else
LOCAL int
allbchk()
#endif
{
	return ((CSR & chkmask) == chkevent);
}	/* allbchk() */

#if __USE_PROTO__
LOCAL int allbitswait(unsigned char mask, unsigned char event, unsigned ticks)
#else
LOCAL int
allbitswait(mask, event, ticks)
unsigned char	mask,
		event;
unsigned	ticks;
#endif
{
	chkmask = mask;
	chkevent = event;

	return (eventwait(&allbchk, ticks));
}	/* allbitswait() */

#if __USE_PROTO__
LOCAL void hareset(unsigned char newstate)
#else
LOCAL void
hareset(newstate)
unsigned char newstate;
#endif
{
    /*******************************************************
     *  Reset the Host adapter. This resets all of the
     *  devices on the bus also. The SCSI spec stats that
     *  the reset hold time is 25 usec or longer. Wait
     *  through two reset hold times here.
     */
    
    setcsr(ENPARITY | ENSCSI | RST_OUT);
    eventwait(NULL, 2 * RSTHOLD_TM);
    setcsr(newstate);
    
    /*******************************************************
     *  Allow 5 seconds for all devices on the bus to ready
     *  themselves after this reset. In practice only the
     *  disk drives will be ready after 5 sec but nobody
     *  should be trying to use the tape drive this soon
     *  anyway.
     */
    
    eventwait(NULL, 5000000U);
}

/***********************************************************************
 *  hainit() -- Reset the host adapter and set it up to accept
 *              commands.
 *
 *  This should look very familiar to you Hal.
 */

#if __USE_PROTO__
LOCAL int ss_load(void)
#else
LOCAL int
ss_load()
#endif
{
    paddr_t 	pbase;	/* Physical base of controller ram. */
    ssccb_p 	ctrl;	/* Pointer to a device control structure */
    unsigned	i, id,
    		numdevs;/* number of devices configured on the bus. */
    int 	erf;
    
    cmn_err(CE_CONT,
	    "Haiss: Seagate ST0x, Future Domain 8xx Host Adapter module.\n");
    
    /* Map the HA's memory. */
    
    pbase = ((paddr_t) (haiss_base << 4));
    ss_vbase = (unsigned char *) map_pv(pbase, SS_RAMSIZE);
    
    /*******************************************************************
     *  Check for the presence of the card by writing to the host
     *  adapter's Static Ram and then read back to make sure that the
     *  ram is there.  If it is assume that the card is present.
     */
    
    for (i = SS_SRAMSTART; i <= SS_SRAMEND; ++i)
	ss_vbase[i] = (i & 0xff);
    
    for (i = SS_SRAMSTART, erf = 0; !erf && i < SS_SRAMEND; ++i)
	erf |= (ss_vbase[i] != (i & 0xff));
    
    if (erf) {
	cmn_err(CE_CONT, "\tStatic ram test failed.\n");
	return (ss_haft.hf_present = 0);
    }
    
    /*******************************************************************
     * Now setup the parameters used to work with the HA.  The differences
     * between the Seagates and the Future Domains are resolved here.
     * Aside from the addressing differences earlier Future Domain
     * controllers swapped the SCSI C/D and MESSAGE bits off the chip.
     * This is compensated for with the flag swap_status_bits and with
     * function busphase().
     */
    
    swap_status_bits = 0;
    switch (haiss_type) {
    case ST01:
	cmn_err(CE_CONT, "\tConfigured for Seagate ST01/ST02\n");
	ss_csr = ss_vbase + ST01_CSR;
	ss_dat = ss_vbase + ST01_DAT;
	hostid = ST_ID;
	break;
    case FD845:
	cmn_err(CE_CONT, "\tConfigured for Future Domain TMC-845/850/860/875/885\n");
	ss_csr = ss_vbase + FD845_CSR;
	ss_dat = ss_vbase + FD845_DAT;
	hostid = FD_ID;
	break;
    case FD840:
	cmn_err(CE_CONT, "\tConfigured for Future Domain TMC-840/841/880/881\n");
	ss_csr = ss_vbase + ST01_CSR;
	ss_dat = ss_vbase + ST01_DAT;
	hostid = FD_ID;
	swap_status_bits = 1;
	break;
    default:
	cmn_err(CE_CONT, "\tbad configuration.\n");
	return (ss_haft.hf_present = 0);
    }
    
#if 	defined(INIT_RST)
    hareset(ENPARITY);
#endif	
    /***************************************************************
     *  Lastly search through the installed devices mask and find
     *  out how many control structures we need to for this
     *  configuration.
     */
    
    ss_devices = ( HAI_DISK | HAI_TAPE | HAI_CDROM );
    numdevs = 0;
    for (i = 0; i < MAXDEVS; ++i) {
        if (ss_devices & bit(i))
            ++numdevs;
    }
    ctrl = rawccb = (ssccb_p) kalloc(numdevs * sizeof(ssccb_t));
    if (!rawccb) {
	cmn_err(CE_PANIC,
		"haiss: Cannot allocate Host adapter control structures.\n");
	return (ss_haft.hf_present = 0);
    }
    
    /***************************************************************
     *  Clear out the control structures and put them into the reference
     *  table for use when the driver is working.
     */
    
    memset(rawccb, 0, numdevs * sizeof(ssccb_t));
    for (id = 0; id < MAXDEVS; ++id)
	if (ss_devices & bit(id)) {
	    ssdevs[id] = ctrl++;
	    ssdevs[id]->c_target = id;
	}
    
    /*******************************************************************
     *  All done here for now.  If I were only controlling DASD devices
     *  I could now go and initialize all the devices on the bus.  That
     *  is now the job of the individual device routines.  This will be
     *  a problem because at some point in time I will have to read and
     *  interpret the partition table of the disk...
     */
    
    /* Grab the Interrupt vector */
    
    setivec(haiss_intr, ssintr);
    cmn_err(CE_CONT,
	    "\tBase Memory:\t\t0x%x\n"
	    "\tInterrupt vector:\t%d\n",
	    haiss_base,
	    haiss_intr);


    /*
     * This call doesn't belong here. This really should be done from 
     * the disk drive init routines. But this is in testing so...
     */

    {
	extern int at_drive_ct;
	int biosnum = at_drive_ct;

	for (id = 0 ; id < MAXDEVS ; ++id)
	    if (HAI_DISK & bit(id))
		loadbiosparms(biosnum++, id);
    }
    return (ss_haft.hf_present = 1) /* 1 */;
}	/* hainit() */

/***********************************************************************
 *  ssunload()  --  Return memory, IRQ, and Selector back to Kernel
 *                  when done.
 */

#if __USE_PROTO__
LOCAL void ss_unload(void)
#else
LOCAL void
ss_unload()
#endif
{
	/* Return memory occupied by control structures. */
	if (rawccb)
		kfree(rawccb);

	/* Return the selector */
	unmap_pv((vaddr_t) ss_vbase);

	/* Return the IRQ */
	clrivec(haiss_intr);
}	/* ssunload() */

/***********************************************************************
 *  busphase() --       Get the I/O phase from the bus.
 *
 *  Read the bus I/O phase signals.  Swap the MSG_IN and CD_IN signals
 *  if this is an old Future Domain card which swaps them for us.
 */

#if __USE_PROTO__
LOCAL unsigned char busphase(void)
#else
LOCAL unsigned char
busphase()
#endif
{
    unsigned rawphase = CSR & (MSG_IN | IO_IN | CD_IN);
    
    if (swap_status_bits) {
	unsigned ret = rawphase & ~(MSG_IN | CD_IN);
	
	if (rawphase & MSG_IN) 
	    ret |= CD_IN;
	if (rawphase & CD_IN)  
	    ret |= MSG_IN;
	return (unsigned char) ret;
    }
    else
	return (unsigned char) rawphase;
}	/* busphase() */

/***********************************************************************
 *  macro busfree() --  Wait for a bus free phase.
 */

#define busfree()	((CSR & (BUSY_IN | SEL_IN)) == 0)

/***********************************************************************
 *  set_priority_queue() -- Set the priority queue pointer for the
 *                          sswork loop.
 *  
 *  Reselect phases occur on the SCSI bus to finish up commands that
 *  allow disconnect. These will be seen as reselect phases from the
 *  interrupt routine or when trying to arbitrate for the bus. The
 *  work loop is designed to process these requests first. To do this
 *  it needs to know which device is trying to reselect the host. 
 *  set_priority_queue puts the CCB for this device into the variable:
 *  priority_queue. This will be picked up by sswork and the device
 *  will be serviced.
 */


#if __USE_PROTO__
LOCAL ssccb_p set_priority_queue(void)
#else
LOCAL ssccb_p 
set_priority_queue()
#endif
{
    Register unsigned	scsibus;
    Register unsigned	target;
    Register unsigned	hostmask = bit(hostid);
    
    /*******************************************************
     *  Read the SCSI bus to see if we are needed.
     */
    
    scsibus = DAT;
    if ((scsibus & hostmask) == hostmask) {
	/***********************************************
	 *  Strip the host's tid bit from the SCSI
	 *  Bus and make sure that what's left is a
	 *  power of two. This will be the tid bit of 
	 *  the target that's attempting to reconnect
	 *  to the host adapter.
	 */
	
	scsibus &= ~hostmask;
	if (((scsibus - 1) & (scsibus)) == 0) {
	    for (target = 0 ; scsibus > 1 ; ++target, scsibus >>= 1)
		;
	    while (scsibus > 1) {
		scsibus >>= 1;
		++target;
	    }       /* while */
	    
	    if (!ssdevs[target] || ssdevs[target]->c_state != DEV_WORKING) {
		dummy.c_scsistatus = dummysrb.status = ST_PENDING;
		dummysrb.target = hostid;
		dummysrb.lun = 0;
		dummy.c_datain = dummy.c_dataout = NULL;
		dummy.c_srb = &dummysrb;
		priority_queue = &dummy;
		cmn_err(CE_WARN,
			"haiss: Invalid reselect at target %d,"
			" device state: %x",
			target,
			ssdevs[target]->c_state);
	    }       /* if */
	    else
		priority_queue = ssdevs[target];
	}       /* if */
	else
	    cmn_err(CE_NOTE, "haiss: Invalid SCSI reselect.");
    }       /* if */
    
    return priority_queue;
}	/* set_priority_queue() */

/***********************************************************************
 *  SCSI bus phase handlers. 
 *  
 *  These functions handle the various SCSI bus phase that we expect
 *  to see in normal operation.
 */

/***********************************************************************
 *  scsi_arbitrate() --     Arbitrate for the SCSI Bus.
 */

#if __USE_PROTO__
LOCAL int scsi_arbitrate(int host)
#else
LOCAL int
scsi_arbitrate(host)
int	host;
#endif
{
    if (busfree() && busfree()) {
	setcsr(HAIDLE);
	setdat(bit(host));  	/* set up our address on the bus */
	setcsr(ENINTR | ENPARITY | ENSCSI | STARTARB);
	if (allbitswait(ARBITCMP, ARBITCMP, 100) != 0)
	    return 1;
	else {
	    setcsr(HAIDLE);
	    return 0;
	}
    }
    else 
	return 0;
}	/* scsi_arbitrate() */

#if __USE_PROTO__
LOCAL int msgoutstart(void)
#else
LOCAL int
msgoutstart()
#endif
{
	return ((CSR & REQ_IN) != 0 && (busphase() == XP_MSGOUT));
}

/***********************************************************************
 *  msgoutack() --  Return (1) if we have a message out phase.
 */
#if __USE_PROTO__
LOCAL int msgoutack(void)
#else
LOCAL int
msgoutack()
#endif
{
	return (busphase() != XP_MSGOUT);
}	/* msgoutack() */

/***********************************************************************
 *  scsi_connect() --       Connect to a target on the SCSI bus.
 *  
 *  After arbitration this establishes a connection to a target on
 *  the SCSI bus. If needed a message out phase is generated and a
 *  message will be passed to the target device.
 */

#if __USE_PROTO__
LOCAL int scsi_connect(int host, int target, unsigned char message)
#else
LOCAL int 
scsi_connect(host, target, message)
int		host;
int		target;
unsigned char	message;
#endif
{
    int attn_req = (message != MSG_NOP);
    int i;
    
    /***************************************************************
     *  We won arbitration. Now begin the selection phase by 
     *  asserting the sel signal on the SCSI bus. Keep busy high
     *  too since we are dropping the arbitration signal. Then
     *  wait a Bus Clear Delay and a Bus Settle Delay.
     */
    
    setcsr(ENSCSI | ENINTR | ENPARITY | SEL_OUT | BUSY_OUT);
    busyWait2(NULL, 3);		/* Wait 2.6 us */
    
    /***************************************************************
     *  Drive the requested device's id onto the bus, wait 2 deskew
     *  delays.
     */
    
    setdat(bit(host) | bit(target));
    busyWait2(NULL, 2);
    
    setcsr((attn_req) ? (ENSCSI | ENPARITY | ENINTR | SEL_OUT | ATTN_OUT)
	   : (ENSCSI | ENPARITY | ENINTR | SEL_OUT));
    
    if (!allbitswait(BUSY_IN, BUSY_IN, SELTO_DLY)) {
	setdat(0);
	setcsr(ENSCSI | ENPARITY | ENINTR | SEL_OUT);
	if (!allbitswait(BUSY_IN, BUSY_IN, SELABORT_TM + (2 * DESKEW_DLY))) {
	    setcsr(HAIDLE);
	    cmn_err(CE_NOTE, "haiss: Select Timed Out on ID%d", target);
	    return 0;
	}		
    }
    
    busyWait2(NULL, 2);
    if (attn_req) {
	setcsr(ENSCSI | ENPARITY | ENINTR | ATTN_OUT);
	if (!busyWait(&msgoutstart, 500)) {
	    cmn_err(CE_WARN, "Cannot start message out phase!\n");
	    return 0;
	}
        
	setcsr(ENSCSI | ENPARITY | ENINTR);      /* Drop the ATTN Condition */
	setdat(message);                    /* Send the message */
	setcsr(ENSCSI | ENPARITY | ENINTR);
	for (i = 0 ; i < 3; ++i) {
	    if (busyWait(&msgoutack, 5)) {
		return 1;
	    }
	    else {
		cmn_err(CE_NOTE,
			"haiss: Resending message: (0x%x)\n", message);
		setdat(message);
		setcsr(ENSCSI | ENPARITY | ENPARITY);
		message = MSG_NOP;
	    }
	}
	return 0;
    }
    
    setcsr(ENSCSI | ENPARITY | ENINTR);
    return 1;
}	/* scsi_connect() */

/***********************************************************************
 *  scsi_reconnect() --     Reconnect to a device on the bus after
 *                          disconnect.
 *  
 *  Connect is Host Adapter Driven, reconnect is target driver. A target
 *  has grabbed the bus and selected the host. This function handshakes
 *  the bus into the ioxfer phase.
 */

#if __USE_PROTO__
LOCAL int scsi_reconnect(void)
#else
LOCAL int scsi_reconnect()
#endif
{
	setcsr(ENPARITY | ENSCSI | ENINTR | BUSY_OUT);
	if (allbitswait(SEL_IN, 0, SELTO_DLY)) {
		setcsr(ENPARITY | ENSCSI | ENINTR);
		return 1;
	}
	else {
		setcsr(HAIDLE);
		cmn_err(CE_NOTE, "haiss: SCSI Reselect failed.");
	}
	return 0;
}	/* scsi_reconnect() */

static int	bsyhigh;

#if __USE_PROTO__
LOCAL int reqcheck(void)
#else
LOCAL int 
reqcheck()
#endif
{
    unsigned char csr = CSR;
    
    if (csr & REQ_IN)
	return 1;
    
    if ((csr & BUSY_IN) == 0) {
	bsyhigh = 0;
	return 1;
    }
    
    return 0;
}	/* reqcheck() */

/***********************************************************************
 *  reqwait() --    Wait for the request and busy signals from the
 *                  target.
 *
 *  During the I/O phases the target should assert REQ_IN and BUSY_IN
 *  while it wants to do I/O.  Poll for those values in a loop which
 *  will timeout eventually. Like all other loops in this driver this
 *  one will have to be replaced with the new busyWait function when
 *  I get documentation and the function has been standardized (not
 *  in that order).
 *
 *  Input:  Pointer to caller's timeout flag.  This will be set to
 *      one if the function if the function times out, zero otherwise.
 */

#if __USE_PROTO__
LOCAL int reqwait(Register int *toptr)
#else
LOCAL int
reqwait(toptr)
Register int *toptr;
#endif
{
#if 0
    if (toptr)
	*toptr = 0;

    bsyhigh = 1;
    if (eventwait(&reqcheck, (4 * SELTO_DLY)) && bsyhigh)
	return 1;
    else if (bsyhigh && toptr) {
	cmn_err(CE_WARN, "haiss: SCSI REQ Timeout");
	if (ss_msgin_cc)
	    cmn_err(CE_WARN, "haiss: MSGIN:CC");
	*toptr = 1;
    }

    return 0;
#else
    int counter;
    unsigned char csr;

    if (toptr)
	*toptr = 0;

    counter = 10000000L;
    do {
	csr = (CSR & (BUSY_IN | REQ_IN));
	if ((csr & REQ_IN) == REQ_IN)
	    return 1;
	if ((csr & BUSY_IN) == 0)
	    return 0;
    } while (--counter > 0);

    cmn_err(CE_WARN, "haiss: SCSI REQ Timeout (CSR: 0x%x)", CSR);
    if (toptr)
	*toptr = 1;
    
    return 0;
#endif
}	/* reqwait() */

/***********************************************************************
 *  sswrite() -- byte by byte copy with handshake.
 *  
 *  Copy byte by byte from kernel memory to kernel memory.
 *  
 *  Assumes that one of the address is the data port from the Host
 *  Adapter.
 */

#if __USE_PROTO__
LOCAL size_t sswrite(void *source,
		     void *destination,
		     size_t size,
		     unsigned char phase)
#else
LOCAL size_t 
sswrite(source, destination, size, phase)
char 		*source;	/* Source Address */
char 		*destination;	/* Destination Address */
size_t		size;		/* Size of copy */
unsigned char	phase;		/* required busphase */
#endif
{
    Register char *src = source;
    Register char *dst = destination;
    Register int index = 0;

#if 0
    static char capbuf[64];
    
    while (index < sizeof(capbuf) && index < size && busphase() == phase) {
	
	/*******************************************************
	 * For kernel reads and writes syncronize each byte 
	 * going back and forth over the host. This is closer
	 * to the behaviour that the old ss driver used to use.
	 */
	
	if (reqwait(NULL)) {
	    capbuf[index] = dst[index] = src[index];
	    ++index;
	}
	else
	    break;
    }
#endif
    while (index < size && busphase() == phase) {
	
	/*******************************************************
	 * For kernel reads and writes syncronize each byte 
	 * going back and forth over the host. This is closer
	 * to the behaviour that the old ss driver used to use.
	 */
	
	if (reqwait(NULL)) {
	    dst[index] = src[index];
	    ++index;
	}
	else
	    break;
    }
    
#if 0
    dumpmem("\nsswrite capture buffer:", capbuf, min(index, sizeof(capbuf)));
#endif
    
    return index;
}	/* sswrite() */

/***********************************************************************
 *  dataout() --    Handle the Data out phase of the bus.  This gets
 *                  special treatment because of the way Coherent's
 *                  memory management works.  Up for review when the
 *                  memory management system changes.
 */

#if __USE_PROTO__
LOCAL void dataout(ssccb_p c, int *toflag)
#else
LOCAL void
dataout(c, toflag)
ssccb_p		c;
unsigned	*toflag;
#endif
{
    haisgsegm_p dataend;
    haisgsegm_p dataseg;
    size_t dataofs;
    char *dst = (c->c_cmdflags & CMD_NEEDREQACK) ? ss_xfrbuf : (char *) ss_dat;
    size_t xfrsz;
    int s;
    
    s = sphi();
    dataend = (c->c_sgs + c->c_segcount);
    dataseg = c->c_dataout;
    dataofs = c->c_dataofs;
    while (dataseg && busphase() == XP_DATAOUT) {
	xfrsz = dataseg->sgs_segsize - dataofs;
	if (xfrsz > DATAREGSIZE)
	    xfrsz = DATAREGSIZE;
	
	pxcopy(dataseg->sgs_segstart + dataofs, dst, xfrsz, SEL_386_KD);
	if (dst != (char *) ss_dat)
	    sswrite(dst, ss_dat, xfrsz, XP_DATAOUT);
	
        /*
         * The fast copy routine doesn't modify xfrsz so we could have
         * problems here but the errors should be obvious in any case
         * the slow transfer case can count bytes so it does. This is 
         */
	dataofs += xfrsz;
	if (dataofs >= dataseg->sgs_segsize) {
	    dataofs = 0;
	    if (++dataseg >= dataend)
		dataseg = NULL;
	} /* if */

	if (!reqwait(toflag))
	    break;
    } /* while */
    c->c_dataofs = dataofs;
    c->c_dataout = dataseg;
    spl(s);
    
    if (reqwait(toflag) && busphase() == XP_DATAOUT) {
	while (reqwait(toflag) && busphase() == XP_DATAOUT) 
	    DAT = 0x00;
	
	c->c_cmdflags |= CMD_UNDERRUN;
    }	/* if */
    
    if (!c->c_dataout)
	c->c_cmdflags &= ~CMD_OVERRUN;
}	/* dataout() */

/***********************************************************************
 *  datain() --     Handle the Data out phase of the bus. This gets
 *                  special treatment because of the way Coherent's
 *                  memory management works.  Up for review when the
 *                  memory management system changes.
 */

#if __USE_PROTO__
LOCAL void datain(Register ssccb_p c, int *toflag)
#else
LOCAL void
datain(c, toflag)
ssccb_p		c;
int		*toflag;
#endif
{
    haisgsegm_p dataend;
    haisgsegm_p dataseg;
    size_t dataofs;
    size_t xfrsz;
    char *src = (c->c_cmdflags & CMD_NEEDREQACK) ? ss_xfrbuf : (char *) ss_dat;
    unsigned char bb;
    int s;

    s = sphi();
    dataend = (c->c_sgs + c->c_segcount);
    dataseg = c->c_datain;
    dataofs = c->c_dataofs;
    while (dataseg && busphase() == XP_DATAIN) {
	xfrsz = dataseg->sgs_segsize - dataofs;
	if (xfrsz > DATAREGSIZE)
	    xfrsz = DATAREGSIZE;
	
	if (src != (char *) ss_dat)
	    sswrite(ss_dat, src, xfrsz, XP_DATAIN);
        xpcopy(src, dataseg->sgs_segstart + dataofs, xfrsz, SEL_386_KD);
	
	dataofs += xfrsz;
	if (dataofs >= dataseg->sgs_segsize) {
	    dataofs = 0;
	    if (++dataseg >= dataend)
		dataseg = NULL;
	} /* if */
	
	if (!reqwait(toflag))
	    break;
    } /* while */
    c->c_dataofs = dataofs;
    c->c_datain = dataseg;
    spl(s);
    
    if (reqwait(toflag) && busphase() == XP_DATAIN) {
	while (reqwait(toflag) && busphase() == XP_DATAIN)
	    bb = DAT;
	c->c_cmdflags |= CMD_OVERRUN;	
    }
    
    if (!c->c_datain) 
	c->c_cmdflags &= ~CMD_UNDERRUN;
}	/* datain() */

/***********************************************************************
 *  scsi_ioxfer() --        Handle SCSI I/O transfer phases.
 *  
 *  SCSI I/O phases are driven by the target. This function follows
 *  the target until the I/O phases are complete.
 */

#if __USE_PROTO__
LOCAL int scsi_ioxfer(Register ssccb_p c)
#else
LOCAL int 
scsi_ioxfer(c)
Register ssccb_p	c;
#endif
{
    Register srb_p	r = c->c_srb;
    unsigned		msgin;
    unsigned		bb;
    int			toflag = 0;
    int 		errors = 0;
    int			done = 0;

    /***************************************************************
     *  For as long as the target is requesting information send some
     *  even if it comes from the bit bucket. This may cause an infinite
     *  loop but only if the target is broken or incredibly hungry.
     *  This may need to be changed in the future, no problem.
     */

    if (c == NULL)
	cmn_err(CE_PANIC, "haiss: Null buffer passed to scsi_ioxfer");

    while (!done && reqwait(&toflag)) {
	if (errors) {
	    cmn_err(CE_WARN, "haiss: Error in SCSI I/O phases.");
	    c->c_datain = c->c_dataout = NULL;
	    c->c_cdb = NULL;
	    c->c_cdblen = 0;
	    setcsr(ENSCSI | ENPARITY | ENINTR | ATTN_OUT);
	}
	
	switch (busphase()) {
	    /*******************************************************
	     *  COMMAND: Transfer a SCSI CDB from the initiator to the
	     *  target.  Ex: Send the read command to the Hard disk.
	     */
	    
	case XP_COMMAND:
	    if (c->c_cdb) {
		while (c->c_cdblen > 0 && reqwait(&toflag)) {
		    setdat(*c->c_cdb++);
		    if (--c->c_cdblen == 0) {
			c->c_cdb = NULL;
			c->c_cmdflags |= CMD_PENDING;
		    }
		}
	    }
	    else {
		++errors;
		sswrite(test_unit_ready, ss_dat, sizeof(test_unit_ready), XP_COMMAND);
	    }
	    break;
	    
	case XP_DATAOUT:
	    dataout(c, &toflag);
	    break;
	    
	case XP_DATAIN:
	    datain(c, &toflag);
	    break;
	    
	    /*******************************************************
	     *  STATUS: Transfer the status byte from the last CDB
	     *  to the Initiator. This allows us to figure out if
	     *  the transfer went okay. An unexpected status byte
	     *  is not treated as an error.
	     */
	    
	case XP_STATUS:
	    if (errors) {
		bb = DAT;
		++errors;				
	    }
	    else
		c->c_scsistatus = DAT; 
	    break;
	    
	    /*******************************************************
	     *  MSG_OUT: This is the last phase of an error 
	     *  condition. It should happen for perhaps a bus
	     *  cyle or two before the target acknowledges the
	     *  Abort message and drops the SCSI bus.
	     */
	    
	case XP_MSGOUT:
	    if (errors < 3)
		DAT = MSG_ABORT;
	    else
		DAT = MSG_BUSDEVRST;
	    break;
	    
	    /*******************************************************
	     *  MSGIN:  Get a message in from the device. There
	     *  are actually only a handful that we care about
	     *  but I'm sure that some will have to be added because
	     *  there are some that require a response which will
	     *  add some logic to the driver.
	     */
	    
	case XP_MSGIN:
	    if (errors) {
		bb = DAT;
		++errors;
		break;
	    }
	    
	    msgin = DAT;
	    switch (msgin) {
		/***********************************************
		 *  Command Complete Message.
		 */
	    case MSG_CC:
		c->c_state = DEV_FINISHED;
		c->c_cmdflags &= ~CMD_PENDING;
		if (c->c_scsistatus == ST_BUSY)
		    c->c_cmdflags |= CMD_DEVBUSY;
		if (c->c_scsistatus == ST_PENDING)
		    c->c_scsistatus = ST_GOOD;
		done = 1;
		break;
		
		/***********************************************
		 *  Disconnect Message.
		 */
	    case MSG_DISCONNECT:
		c->c_state = DEV_WORKING;
		done = 1;
		break;
		
	    case MSG_NOP:
	    case MSG_SAVEDPTR:
	    case MSG_RSTRDPTR:
		break;
		
		/***********************************************
		 *  Whole host of messages that we could care
		 *  less about.
		 */
	    default:
		if (!(msgin & MSG_IDENTIFY))
		    cmn_err(CE_NOTE, "haiss: Bad message 0x%x\n", msgin);
		break;
	    }       /* switch */
	    break;

	default:
	    cmn_err(CE_WARN, "haiss: Invalid SCSI Bus Phase.");
	    ++errors;
	    break;
	}	/* switch */
    }	/* while */
    setcsr(HAIDLE);

    if (errors != 0) {
	c->c_state = DEV_FINISHED;
	c->c_scsistatus = ST_ABRT;
	r->hastat = HAST_BADBUSPH;
    }

    return (errors == 0);
}	/* scsi_ioxfer() */

/***********************************************************************
 *  ssdone()
 */

#if __USE_PROTO__
LOCAL void ssdone(Register ssccb_p c)
#else
LOCAL void
ssdone(c)
Register ssccb_p	c;
#endif
{
    Register srb_p r = c->c_srb;

    drvl[SCSIMAJOR].d_time &= ~(bit(c->c_target));
    c->c_cmdflags = 0;
    c->c_cdblen = 0;
    c->c_cdb = NULL;
    c->c_datain = c->c_dataout = NULL;
    c->c_srb = NULL;
    c->c_state = DEV_IDLE;
    if (r) {
        r->status = c->c_scsistatus;
        if (r->cleanup) 
            (*(r->cleanup))(r);
    }
}	/* ssdone() */

/***********************************************************************
 *  sswork() --         Process work requests which require the SCSI
 *                      Bus.
 *  
 *  This loop handles all work requeusts which require the SCSI Bus.
 *  This is needed because the SCSI bus may be busy when a device tries
 *  to start a command.
 *
 *
 *  Returns 1 if a new context was generated and 0 if not.
 */

#if __USE_PROTO__
LOCAL int sswork(void)
#else
LOCAL int
sswork()
#endif

{
    Register ssccb_p	q;
    Register srb_p	r;
    static int 		buserrors = 0;	/* Start buserrors at 0 */
    static volatile int	locked = 0;	/* Activity flag */
    int 		alldone = 0;	/* Assume not! */
    int			s;
    
    /***************************************************************
     *  It could get confusing if more than one context of this
     *  routine was loaded at any one time.I also cannot figure
     *  out a locking mechanism to for each call so I chose to
     *  do this here at the work loop.
     */
    
    s = sphi();
    if (locked) {
	spl(s);
	return 0;
    }
    else
	++locked;
    spl(s);
    
    /***************************************************************
     *  Check here to see if the bus has been busy too long. If
     *  it has then do a SCSI Bus Reset. This will cancel all 
     *  commands on all devices and return their states to normal.
     *  
     *  This removes two failure conditions: 
     *      1.  When the bus is truly hung and needs to be reset.
     *  
     *      2.  When a third party SCSI command goes awry or 
     *          takes to long and causes 1.
     */
    
    if (buserrors > MAXBUSYCOUNT) {
	cmn_err(CE_WARN, "haiss: SCSI bus hung, resetting");
	hareset(HAIDLE);
	buserrors = 0;
    }
    
    /***************************************************************
     *  Loop in here until there isn't any work to do or we can't
     *  get the bus. The order in here is for interrupt service
     *  when most of the time we will be processing normal 
     *  requests.
     */
    
    for ( ; ; ) {
	
	/*******************************************************
	 *  Come here to either start work anew or finish up
	 *  some previous request. The busy could be in any
	 *  phase here but we shouldn't be driving it at all.
	 */
	
	if ((q = priority_queue) != NULL) {
	    priority_queue = NULL;
	    r = q->c_srb;
	    /***********************************************
	     *  Try the reselect. If it fails just drop
	     *  the task. The device is the only thing
	     *  that can initiate closure here.
	     */
	    
	    if (!scsi_reconnect()) {
		set_host_status(r, HAST_SELTO);
		break;
	    }

	    scsi_ioxfer(q);
	    s = sphi();
	    if (q->c_state == DEV_FINISHED)
		ssdone(q);
	    buserrors = 0;
	    spl(s);
	}	/* if */
	
	/*******************************************************
	 *  Next start any normal work that needs to get done.
	 *  This may be a mistake but so far all work requires
	 *  the SCSI Bus. The arbitration code for the bus
	 *  goes here.
	 */
	
	else if ((q = normal_queue) != NULL) {
	    r = q->c_srb;
	    /***********************************************
	     *  So far all requests require arbitration
	     *  so do it here. This also allows us to check
	     *  for a reselect/arbitration colision here.
	     */
	    
	    if (!scsi_arbitrate(hostid)) {
		if (reselect() && set_priority_queue())
		    continue;
		else
		    break;
	    }	/* if */
	    
	    /***********************************************
	     *  Only do one of the next three actions.
	     *  Bus Device Reset is highest priority followed
	     *  by Abort Command, followed by Start Command.
	     */
	    
	    if (q->c_reqflags & REQ_BUSDEVICERST) {
		if (!scsi_connect(hostid, q->c_target, MSG_BUSDEVRST)) {
		    set_host_status(r, HAST_SELTO);
		    cmn_err(CE_WARN,
			    "haiss: SCSI Reset at ID %d failed (SELTO)",
			    q->c_target);
		    break;
		}
		q->c_state = DEV_FINISHED;
	    }	/* else if */
	    else if (q->c_reqflags & REQ_ABORTSCSICMD) {
		if (!scsi_connect(hostid, r->target, MSG_ABORT)) {
		    set_host_status(r, HAST_SELTO);
		    cmn_err(CE_WARN,
			    "haiss: Abort SCSI command failed (SELTO)");
		    break;
		}
		q->c_scsistatus = ST_DRVABRT;
		q->c_state = DEV_FINISHED;
		cmn_err(CE_NOTE,
			"haiss: Command 0x%x at ID %d LUN %d aborted.",
			r->cdb.g0.opcode,
			r->target,
			r->lun);
	    }	/* else if */
	    else if (q->c_reqflags & REQ_STARTSCSICMD) {
		if (!scsi_connect(hostid,
				  r->target,
				  MSG_IDENTIFY | ID_DISCONNECT | r->lun)) {
		    set_host_status(r, HAST_SELTO);
		    break;
		}
		scsi_ioxfer(q);
	    }
	    s = sphi();
            q->c_reqflags = 0;
	    normal_queue = q->c_next;
	    q->c_next = NULL;
	    if (q->c_state == DEV_FINISHED)
		ssdone(q);
	    buserrors = 0;
	    spl(s);
	}	/* else ... if */
	
	/*******************************************************
	 *  Finally if we get down here then there isn't any
	 *  work to do so mark the alldone flag and exit.
	 */
	
	else {
	    alldone = 1;
	    break;
	}	/* else */
	
	/*******************************************************
	 *  At this point the host should be Idle. Wait like
	 *  that for a bus settle delay or so to clear out
	 *  any pending commands that the devices have finished.
	 */
	
	eventwait(NULL, 4 * BUSSETTLE_DLY);
    }	/* for */
    
    /***************************************************************
     *  If this doesn't finish up all the work the bus is probably
     *  busy, do something else for a couple of cycles and come
     *  back here.
     */
    
    if (!alldone) {
	timeout(&sstim, 2, (void (*)()) &sswork, 0);
	++buserrors;
    }
    
    locked = 0;
    return 1;
}	/* sswork() */

/***********************************************************************
 *  ssintr() --         Handle hardware interrupts.
 *  
 *  In the Implementations that I see of this card the SCSI Select line
 *  is tied to the interrupt generation hardware. External assertion
 *  of this line causes an interrupt to be generated. This will normally
 *  be caused by a SCSI reselect phase on the bus. The proper action
 *  here is to determine if a reconnect is happening and if so whether
 *  or not the host is the reselected device.
 */
#if __USE_PROTO__
LOCAL void ssintr(void)
#else
LOCAL void
ssintr()
#endif

{
    /***************************************************************
     *  Interrupt check for a reselect the expected reselect phase.
     */
    while (reselect()) {
	
	/*******************************************************
	 *  Try and set up the priority queue from the reselect
	 *  info and then run with it.
	 */
	
	if (set_priority_queue()) {
	    if (!sswork())
		break;
	}
    }	/* while */
}	/* ssintr() */

/***********************************************************************
 *  ssinsqueue() --     Insert a request into the driver's work queue.
 *  
 *  Insert a request into the driver's work queue as long as one's
 *  not there already.
 */

#if __USE_PROTO__
LOCAL void ssinsqueue(Register ssccb_p c, int newreqflags)
#else
LOCAL void
ssinsqueue(c, newreqflags)
Register ssccb_p	c;
int			newreqflags;
#endif
{
    Register ssccb_p 	q;
    int			s;
    
    s = sphi();
    c->c_reqflags |= newreqflags;
    if (!normal_queue) {
	normal_queue = c;
    }
    else {
	q = normal_queue;
	
	for ( ; ; ) {
	    if (q == c) {
		q = NULL;
		break;
	    }
	    if (!q->c_next)
		break;
	    else
		q = q->c_next;
	}
	
	if (q) {
	    q->c_next = c;
	    c->c_next = NULL;			
	}
    }
    spl(s);
}	/* ssinsqueue() */

/***********************************************************************
 *  ss_start() --      Start a scsi command. 
 *  
 *  For this host adapter make sure the request is valid, set the
 *  flags and start the work queue.
 */

#if __USE_PROTO__
LOCAL int ss_start(Register srb_p r)
#else
LOCAL int
ss_start(r)
Register srb_p	r;
#endif
{
    Register ssccb_p	c;
    static int		srbid = 0;	/* Number of srb */
    int s;

    s = sphi();
    ++r->tries;
    if (r->target >= MAXDEVS ||
	r->lun >= MAXUNITS ||
	!(c = ssdevs[r->target])) {
	spl(s);
	cmn_err(CE_WARN,
		"haiss: Illegal device ID%d LUN%d\n",
		r->target,
		r->lun);
	return 0;
    }
    
    if (c->c_state != DEV_IDLE) {
	spl(s);
	cmn_err(CE_WARN, "haiss: ID%d busy.", r->target);
	return 0;
    }
    r->status = c->c_scsistatus = ST_INVDSRB;
    spl(s);
    
    c->c_srb = r;
    c->c_cdb = &(r->cdb.g0.opcode);
    switch (c->c_cdb[0] & GROUPMASK) {
    case GROUP0:
	c->c_cdblen = 6;
	break;
    case GROUP1:
    case GROUP2:
	c->c_cdblen = 10;
	break;
    case GROUP5:
	c->c_cdblen = 12;
	break;
    default:
	c->c_state = DEV_FINISHED;
	c->c_cdb = NULL;
	c->c_cdblen = 0;
	r->hastat = HAST_INVDCDB;
	return 0;
    }
    
    /***************************************************************
     *  Setup the data area.
     */
    
    c->c_id = 0x10000000 | (r->target << 24) | (srbid++ & 0xffffff);
    c->c_time = r->timeout;
    c->c_cmdflags = 0;

    if ((r->buf.space & WATCH_REQACK) != 0 || 
        (haiss_slowdev & bit(c->c_target)) != 0)
        c->c_cmdflags |= CMD_NEEDREQACK;
    
    if (r->buf.ba_phys) {
	switch (r->buf.space & SPACE_MASK) {
	case PHYS_ADDR:		/* Physical Address - (who knows) */
	    c->c_segcount = 1;
	    c->c_sgs[0].sgs_segstart = r->buf.ba_phys;
	    c->c_sgs[0].sgs_segsize = r->buf.size;
	    break;
	case KRNL_ADDR:		/* Kernel Address */
	case USER_ADDR:		/* User Address */
	    c->c_segcount = ukbuftosgl(r->buf.ba_virt,
				       r->buf.size,
				       &(c->c_sgs[0]),
				       MAXSEGCOUNT);
	    if (c->c_segcount == 0) {
		cmn_err(CE_WARN, "haiss: Cannot create scatter/gather list.");
		return 0;
	    }
	    break;
	case SYSGLBL_ADDR:		/* System Global address (yeah) */
	    c->c_segcount = sysgbuftosgl(r->buf.ba_phys,
					 r->buf.size,
					 &(c->c_sgs[0]),
					 MAXSEGCOUNT);
	    if (c->c_segcount == 0) {
		cmn_err(CE_WARN, "haiss: Cannot create scatter/gather list.");
		return 0;
	    }
	    break;
	case SGLIST_ADDR:		/* Scatter gather list */
	    if (r->buf.size > MAXSEGCOUNT) {
		cmn_err(CE_WARN, "haiss: Cannot handle scatter/gather list.");
		return 0;
	    }
	    else {
		c->c_segcount = r->buf.size;
		memcpy(&(c->c_sgs[0]),
		       r->buf.ba_sglist,
		       r->buf.size * sizeof(haisgsegm_t));
	    }
	    break;
	} /* switch */
    } /* if */
    c->c_datain = c->c_dataout  = NULL;
    c->c_dataofs = 0;
    if (r->xferdir & DMAWRITE) {
	c->c_dataout = &(c->c_sgs[0]);
	c->c_cmdflags |= CMD_OVERRUN;
    }	/* if */
    else if (r->xferdir & DMAREAD) {
	c->c_datain = &(c->c_sgs[0]);
	c->c_cmdflags |= CMD_UNDERRUN;
    }	/* else - if */

    r->status = c->c_scsistatus = ST_PENDING;
    drvl[SCSIMAJOR].d_time |= bit(r->target);
    ssinsqueue(c, REQ_STARTSCSICMD);
    sswork();
    return 1;
}	/* ss_start() */

/***********************************************************************
 *  ss_abort() --      Abort a SCSI command in progress.
 *  
 *  Stop a scsi command on the bus or just drop one that's in a long
 *  retry loop.
 */

#if __USE_PROTO__
LOCAL void ss_abort(Register srb_p r)
#else
void
LOCAL ss_abort(r)
Register srb_p	r;
#endif
{
    Register ssccb_p	c;
    Register int	s;
    
    s = sphi();
    if (!r || r->target >= MAXDEVS || r->lun >= MAXUNITS)
	return;

    if (!(c = ssdevs[r->target]) || c->c_state != DEV_WORKING || c->c_srb != r)
	return;

    cmn_err(CE_NOTE,
	    "haiss: Abort SCSI command ID: %d LUN: %d opcode 0x%x",
	    r->target,
	    r->lun,
	    r->cdb.g0.opcode);
    if (!(c->c_cmdflags & CMD_PENDING))
	return;
    
    ssinsqueue(c, REQ_ABORTSCSICMD);
    sswork();
    spl(s);
}	/* ss_abort() */

/***********************************************************************
 *  ss_bdr() --    Reset a SCSI device.
 *  
 *  Send a SCSI device the Bus Device Reset message. The should cause
 *  the device to go through it's power on reset sequence of operations
 *  and then come back to a known state.
 */

#if __USE_PROTO__
LOCAL void ss_bdr(Register int target)
#else
LOCAL void
ss_bdr(target)
Register int    target;
#endif
{
    Register ssccb_p	c;
    int s;
    
    s = sphi();
    if (target >= MAXDEVS || !(c = ssdevs[target]))
	return;
    
    ssinsqueue(c, REQ_BUSDEVICERST);
    sswork();
    spl(s);
    
    return;
}	/* ss_bdr() */

#if __USE_PROTO__
LOCAL void ss_timer(void)
#else
LOCAL void
ss_timer()
#endif
{
    Register ssccb_p c;
    Register int id;
    int s;

    for (id = 0 ; id < MAXDEVS ; ++id) {
	if ((c = ssdevs[id]) != NULL) {
	    s = sphi();
	    if ((drvl[SCSIMAJOR].d_time & bit(id)) &&
		c->c_time != 0 &&
		--(c->c_time) == 0) {
		spl(s);
		if (c->c_srb) {
		    cmn_err(CE_WARN, "haiss: timeout at ID %d", c->c_target);
		    ss_abort(c->c_srb);
		}
	    }
	    else
		spl(s);
	}
    }
    return;
}	/* hatimer() */

/*
 * The host adapter function table.
 */

haft_t ss_haft = {
	0,
	ss_timer,		/* Timeout handler */
	ss_load,		/* Initialization routine */
	ss_unload,		/* shutdown routine */
	ss_start,		/* Start a SCSI command from an srb */
	ss_abort,		/* Abort a SCSI command from an srb */
	ss_bdr			/* Reset a scsi device */
};