/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: sec.c 1.37 89/08/29 $"

/*
 * sec.c
 *	Various procedures dealing with SEC's.
 */

/* $Log:	sec.c,v $
 */
#include "sys/types.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/systm.h"
#include "sys/map.h"
#include "sys/user.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/vm.h"
#include "sys/buf.h"
#include "sys/cmn_err.h"
#include "sys/conf.h"
#include "sys/boot.h"

#include "sys/clock.h"
#include "sys/slic.h"
#include "sys/slicreg.h"
#include "sys/cfg.h"

#include "sys/pte.h"
#include "sys/hwparam.h"
#include "sys/intctl.h"
#include "sys/trap.h"
#include "sys/mftpr.h"
#include "sys/plocal.h"

#include "sys/mbad.h"

#include "sys/sec.h"
#include "sys/consmemioctl.h"
#include "sys/sec_modes.h"
#include "sys/sec_ioconf.h"
#include "sys/sec_device.h"

#include "sys/autoconf.h"

int sec_limit;			/*  max number of sec's */
struct sec_dev *se_sec_dev;     /* ptr to blk of sec_dev for se ether. */
struct	sec_gmode cbdgsm;	/* get/setmodes command */
struct	sec_smode wdtsm;	/* for setmodes and startio commands */
struct	reboot	bootdata;
struct	sec_cib *cbdcib;	/* address of Console board device */
struct	sec_cib *wdtcib;	/* address of watchdog timer cib */

extern	int	wd_tid;		/* watchdog timer id */
extern	short	upyet;		/* Set when 1st processor is initialized */
extern	unchar	cons_scsi;	/* slic address of console scsi */
extern	int sec_wdt_timeout;	/* how long before timeout */

#ifdef	DEBUG
int	sec_debug = 0;		/* 0=off, 1=little, 2=all */
extern int boot_debug;
#endif	/* DEBUG  */

struct sec_desc	*SEC_desc;	/* array alloc'd and filled by conf_scsi() */
int	 	NSEC = 0;       /* # SEC's to map at boot */
uint	 	SECvec;		/* bit-vector of existing SEC's */
unchar		SEC_errbase;	/* Base vector number for SEC reported errors */
/*
 * Throw in some extra space n SEC_accerr to work around SEC bug!!
 */
unchar	SEC_accerr[2*MAXNUMSEC];	/* access errors reported by SEC */
extern	unsigned	sec0eaddr;	/* lower 24 bits of ether address */
					/* later used to contain # users */
bool_t		conscsi_yet;		/* Console SCSI init'd yet */

static int sec_boot(), sec_return_fw(), sec_inittodr(), sec_resettodr();
static int sec_wdtinit(), sec_wdtreset();
static void bad_b_iotype();

/* driver records for integral devices */
static struct driver_module co_driver = {
	"co",
	};

static struct driver_module sm_driver = {
	"sm",
	};

static struct driver_module se_driver = {
	"se",
	};

#ifdef KERNEL_PROFILING
static struct driver_module kp_driver = {
	"kp",
	};
#endif  /* KERNEL_PROFILING */

struct firmwaresw sced_interface = {
	sec_boot,	sec_return_fw,	sec_inittodr,	sec_resettodr,
	sec_wdtinit,	sec_wdtreset
};

extern struct sec_device sec_devices[];
extern struct device_config sec_scsi_config[];
extern struct device_config sb8000_config[];
struct	sec_cib	*sec_cibs[MAXNUMSEC];	  
extern void cointr();
extern ulong co_flags;
extern unchar co_bin;
extern short co_req_size;
extern short co_doneq_size;
extern void smintr();
extern ulong sm_flags;
extern unchar sm_bin;
extern short sm_req_size;
extern short sm_doneq_size;
extern void (*se_intr_ptr)();
extern ulong se_flags;
extern unchar se_bin;
extern short se_i_req_size;
extern short se_i_doneq_size;
extern short se_o_req_size;
extern short se_o_doneq_size;

bool_t		probescsi_yet;	/* probed the SCSI bus yet */
bool_t		CCS_present;	/* are the drives embedded SCSI */

/*
 * Distinguished location for embedded SCSI drive.  If there is a CCS disk
 * at this target, we will assume that all disks on the SCSI bus are
 * embedded, and we will scan wildcarded SEC targets from low to high.
 * If the device at this target is not a CCS disk (or does not exist),
 * assume we have target-adaptor-based disks and scan SCSI target
 * wildcards from high to low.
 */

int	CCS_magic_target = 0;


/*
 * appropriate bit is set if a target is found on a SCSI bus that
 * can only have one Logical Unit Number on it, or we are convinced that
 * there is no target there.  This bitmap allows us to scan the SCSI bus
 * more rapidly by eliminating possibilities.
 */

unchar SECnoprobe[MAXNUMSEC];

#define	SEC_EXISTS(SEC_idx)	(SECvec & (1 << (SEC_idx)))
#define	WILDCARD(sd)		(((sd)->sd_sec_idx == -1) || ((sd)->sd_target == -1) || ((sd)->sd_unit == -1))

#define	PLURAL(x)		 ((x)==1?"":"s")

static void	SEC_error();	/* SCED error catcher */
unchar		*SECgarbuf;	/* SCED garbage buffer pointer */

struct	sec_desc *SEC_probe();

sec_probe( device )
	struct gen_device *device;
{
	struct sec_device *dev = (struct sec_device *)device;
	register struct ctlr_toc *toc = &CD_LOC->c_toc[SLB_SCSIBOARD];
	register struct	ctlr_desc *cd = &CD_LOC->c_ctlrs[toc->ct_start];
	int i, secnum = dev - &sec_devices[0];

	for (i = 0; i < toc->ct_count; i++, cd++) {
		if (dev->config->dev_addr == cd->cd_slic) {
			dev->cd = cd;	/* This is the matching SEC */
			if (FAILED(cd)) 
				return 0;	/* Short circuit search */
			if (cd->cd_sc_cons)
				CPRINTF( "# Console is on sec%d\n        ",
				       secnum);
			return 1;	/* Found */
		}
	}
	return 0;			/* Not found */
}

/*
 * sec_init()
 *	Initialize SCSI boards.
 */
void 
sec_init( driver )
	struct driver_module *driver;
{
	register struct ctlr_toc *toc = &CD_LOC->c_toc[SLB_SCSIBOARD];
	register struct	ctlr_desc *cd;
	register struct	sec_desc *sec;
	extern	struct	sec_cib *todcib;
	register int	i;
	register int	j;
	struct sec_scsi_device *cons, *sms, *ses;
	struct sec_dev *co, *co0, *sm, *sm0, *se, *se0;
	struct sec_device *dev;	  /* the current sec */

	sec_limit = driver->numdev;
	sec = SEC_desc =
		(struct sec_desc *)calloc((int)sec_limit * sizeof(*sec));
	NSEC = 0;

	/*
	 * Initialize SEC_desc array, SEC error 
	 * reporting vectors, and allocate SEC cibs.
	 * The allocation of SEC error reporting 
	 * vectors relies on ivecall() returning 
	 * consecutive vectors. 
	 */
	SEC_errbase = ivecpeek(SEC_ERROR_BIN);

	for (i = 0, dev = (struct sec_device *)driver->devices; i < sec_limit;
	     i++, dev++, sec++) {
		ivecinit(SEC_ERROR_BIN,  ivecall(SEC_ERROR_BIN), SEC_error);
		dev->desc = sec;
		dev->errvec = SEC_errbase + i;
		dev->accerr = SEC_accerr + i;
		dev->sec_device_chain = (struct sec_scsi_device *)0;
		
		sec_cibs[i] = (struct sec_cib *)
			calloc(SDEV_NUM_DEVICES*sizeof(struct sec_cib));
		dev->cibs = sec_cibs[i];
		
		if ( !sec_cibs[i] )
			cmn_err( CE_PANIC,
				"conf_sec: No memory for channels, sec %d!",
				i); 
		/*
		 *+ When trying to allocate channel structures for an SEC, the
		 *+ kernel ran out of memory.  This usually indicates a serious
		 *+ problem such as a memory card malfunction or a device
		 *+ driver "calloc"ing a few megabytes.
		 */

		if (!(cd = dev->cd)) 
			continue;	/* Device is not on Sequent bus */

		sec->sec_diag_flags = cd->cd_diag_flag;
		sec->sec_slicaddr = cd->cd_slic;
		sec->sec_target_no = cd->cd_sc_host_num;
				
		/*
		 * If console SCED board, save away pointers to the cib's
		 * for the TOD clock, Watchdog timer, and console board device.
		 */
		if (sec->sec_is_cons = cd->cd_sc_cons) {
			dev->conscsi_yet = &conscsi_yet;
			/*
			 * Console SCED board, save away pointers to the cib's
			 * for the TOD clock, WatchDog Timer and Console Board
			 * Device.
			 */
			cbdcib = &sec_cibs[i][SDEV_SCSIBOARD];
			todcib = &sec_cibs[i][SDEV_TOD];
			wdtcib = &sec_cibs[i][SDEV_WATCHDOG];
		}
		sec->sec_powerup = (struct sec_powerup *) cd->cd_sc_init_queue;
		sec->sec_version = cd->cd_sc_version;
		for (j = 0; j < 6; ++j)
			sec->sec_ether_addr[j] = cd->cd_sc_enet_addr[j];

		if ((cd->cd_diag_flag & (CFG_FAIL|CFG_DECONF)) == 0) {
			++NSEC;
			SECvec |= 1 << i;
		} 
#ifdef DEBUG
		if (cd->cd_diag_flag)
			CPRINTF("sec%d flags 0x%b\n", i, cd->cd_diag_flag,
			"\20\2FAIL\3DECONF\4BDUSE\5SCSIFAIL\6ETHERFAIL\7SLICFAIL\10TODFAIL\11PORT0FAIL\12PORT1FAIL\13SEFAIL\14DIFFFAIL");
#endif	/* DEBUG  */
	}

	cd = &CD_LOC->c_ctlrs[toc->ct_start];
	/*
	 * Save lower 24 bits of ether address to be passed to init,
	 * if not already initialized (eg, on a B21k -- see
	 * conf_clkarb()).  This comes from the SCED in the lowest
	 * numberred Sequent bus slot, SCED zero in the system
	 * table of contents, not to be confused with sec0 from
	 * the system configuration above.
	 *
	 * Note: sec0eaddr is later used to contain number of users.
	 * BTW: typecast to int gets around a compiler bug.
	 */
	if (toc->ct_count && sec0eaddr == (unsigned)-1) {
		unchar *s = &cd->cd_sc_enet_addr[0];
			sec0eaddr=(int)s[5] | (int)(s[4]<<8) | (int)(s[3]<<16);
	}

	if (NSEC < toc->ct_count) {
		CPRINTF("Not using SCSI/Ether Controllers: slic");
		for (i = 0; i < toc->ct_count; ++i, cd++) {
			for (j = 0, dev = (struct sec_device *)driver->devices;
			     j < sec_limit; j++, dev++) 
				if (dev->cd == cd) 
					break; 	/* corresponding one found */
			if (j == sec_limit || !SEC_EXISTS(j))
				CPRINTF(" %d", cd->cd_slic);
		}
		CPRINTF(".\n");
	}

	/*
	 * Allocate a garbage buffer for raw io.
	 * Only one is needed for *all* SCED controllers.
	 * Must not cross a 64kbyte physical boundry because of
	 * SEC DMA hardware limitation.
	 */

	callocrnd(DEV_BSIZE);
	SECgarbuf = (unchar *)calloc(DEV_BSIZE*sizeof(unchar));

	/* Initialize integral devices */
	
	/* console */
	co0 = (struct sec_dev *)calloc( 4 * sec_limit
				      * sizeof (struct sec_dev) );
	cons = (struct sec_scsi_device *)
		calloc( 4 * sec_limit * sizeof (struct sec_scsi_device) );
	co_driver.devices = (struct gen_device *) cons;
	co_driver.size_dev = sizeof (struct sec_scsi_device);

	if ( ! ( co0 && cons ) )
		cmn_err( CE_PANIC, "sec_init: not enough memory to init co");
	/*
	 *+ The kernel ran out of memory before it could allocate the data
	 *+ structures necessary to initialize the SEC serial driver.
	 */

	for ( co = co0, i = 0, dev = (struct sec_device *)driver->devices;
	     i < sec_limit; i++, dev++, cons += 4 ) {
		if (dev->cd) {
			if ( (SEC_desc[i].sec_diag_flags & CFG_PORT0) == 0 ) {
				co[0].sd_alive = 1;
				co[1].sd_alive = 1;
				cons[0].driver = &co_driver;
				cons[1].driver = &co_driver;
				cons[0].next = (struct gen_device *)(cons + 1);
				cons[1].next = (struct gen_device *)
					dev->sec_device_chain;
				dev->sec_device_chain = cons;
			}
			if ( (SEC_desc[i].sec_diag_flags & CFG_PORT1) == 0 ) {
				co[2].sd_alive = 1;
				co[3].sd_alive = 1;
				cons[2].driver = &co_driver;
				cons[3].driver = &co_driver;
				cons[2].next = (struct gen_device *)(cons + 3);
				cons[3].next = (struct gen_device *)
					dev->sec_device_chain;
				dev->sec_device_chain = cons + 2;
			}
		}
		for ( j = 0; j < 4; j++, co++) {
			cons[j].sec_dev = co;
			co->sd_bin = co_bin;
			co->sd_sec_idx = i;
			co->sd_target = -1;
			co->sd_unit = j;
			co->sd_desc = SEC_desc + i;
			co->sd_vector = ivecall( co_bin );
			if ( co->sd_alive ) {
				ivecinit( co_bin, co->sd_vector, cointr);
				co->sd_flags = co_flags;
				co->sd_req_size = co_req_size;
				co->sd_doneq_size = co_doneq_size;
				co->sd_chan = SDEV_CONSOLE0IN + j;
				co->sd_destslic = SL_GROUP | TMPOS_GROUP;
				co->sd_cib = sec_cibs[i] + SDEV_CONSOLE0IN + j;
				SEC_allocate_progqs( co );
			}
		}
	}
	coboot( (int)(4 * sec_limit), co0 );

	/* SEC memory */
	sm0 = (struct sec_dev *)calloc( sec_limit
				       * sizeof (struct sec_dev) );
	sms = (struct sec_scsi_device *)
		calloc( sec_limit * sizeof (struct sec_scsi_device) );
	sm_driver.devices = (struct gen_device *) sms;
	sm_driver.size_dev = sizeof (struct sec_scsi_device);

	if ( ! ( sm0 && sms ) )
		cmn_err( CE_PANIC, "sec_init: not enough memory to init sm");
	/*
	 *+ The kernel ran out of memory before it could allocate the data
	 *+ structures necessary to initialize the SEC memory driver.
	 */

	for ( sm = sm0, i = 0, dev = (struct sec_device *)driver->devices; 
	     i < sec_limit; i++, dev++, sm++, sms++ ) {
		sms->driver = &sm_driver;
		sms->next = (struct gen_device *)dev->sec_device_chain;
		dev->sec_device_chain = sms;
		sms->sec_dev = sm;
		sm->sd_flags = sm_flags;
		sm->sd_bin = sm_bin;
		sm->sd_sec_idx = i;
		sm->sd_target = -1;
		sm->sd_unit = 0;
		sm->sd_desc = SEC_desc + i;
		sm->sd_vector = ivecall( sm_bin );
		if (dev->cd) {
			ivecinit( sm_bin, sm->sd_vector, smintr);
			sm->sd_req_size = sm_req_size;
			sm->sd_doneq_size = sm_doneq_size;
			sm->sd_chan = SDEV_MEM;
			sm->sd_destslic = SL_GROUP | TMPOS_GROUP;
			sm->sd_alive = 1;
			sm->sd_cib = sec_cibs[i] + SDEV_MEM;
			SEC_allocate_progqs( sm );
		}
	}
	smboot( (int) sec_limit, sm0 );
		
	/* Ethernet */
	if ( se_intr_ptr ) {
		/* Only do this if an ether interrupt routine is defined */
		se0 = (struct sec_dev *)calloc( 2 * sec_limit
					       * sizeof (struct sec_dev) );
		ses = (struct sec_scsi_device *)
			calloc( 2 * sec_limit
			       * sizeof (struct sec_scsi_device) );
		se_driver.devices = (struct gen_device *) ses;
		se_driver.size_dev = sizeof (struct sec_scsi_device);

		if ( ! ( se0 && ses ) )
			cmn_err( CE_PANIC,
				"sec_init: not enough memory to init se");
		/*
		 *+ The kernel ran out of memory before it could allocate the
		 *+ data structures necessary to initialize the SEC Ethernet
		 *+ driver. 
		 */
		
		for ( se=se0, i=0, dev=(struct sec_device *)driver->devices;
		     i < sec_limit; i++, dev++, ses += 2 ) {
			if (dev->cd 
			&& (SEC_desc[i].sec_diag_flags & CFG_S_ETHER) == 0) {
				se[0].sd_alive = 1;
				se[1].sd_alive = 1;
				se[0].sd_req_size = se_i_req_size;
				se[0].sd_doneq_size = se_i_doneq_size;
				se[1].sd_req_size = se_o_req_size;
				se[1].sd_doneq_size = se_o_doneq_size;
				ses[0].driver = &se_driver;
				ses[1].driver = &se_driver;
				ses[0].next = (struct gen_device *)(ses + 1);
				ses[1].next = (struct gen_device *)
					dev->sec_device_chain;
				dev->sec_device_chain = ses;
			}
			for ( j = 0; j < 2; j++, se++) {
				ses[j].sec_dev = se;
				se->sd_flags = se_flags;
				se->sd_bin = se_bin;
				se->sd_sec_idx = i;
				se->sd_target = -1;
				se->sd_unit = j;
				se->sd_desc = SEC_desc + i;
				se->sd_vector = ivecall( se_bin );
				if ( se->sd_alive ) {
					ivecinit( se_bin, se->sd_vector,
						 *se_intr_ptr);
					se->sd_destslic = 
						SL_GROUP | TMPOS_GROUP;
					/*
					 * Next lines depends on
					 * SDEV_ETHERWRITE = SDEV_ETHERREAD + 1
					 */
					se->sd_chan = SDEV_ETHERREAD + j;
					se->sd_cib = sec_cibs[i] + 
						SDEV_ETHERREAD + j;
					SEC_allocate_progqs( se );
				}
			}
		}
		se_sec_dev = se0;
		/*
		 * The ether device is booted by the se pseudo_device driver.
		 * The routine se_init is called from conf_pseudo(), which is
		 * still before sec_sec_scsi_alloc maps() is called., which is
		 * necessary for proper operations.
		 */
	}				  /* end of if (se_intr_ptr) */
#ifdef KERNEL_PROFILING
	/* kp -- kernel profiler */

	/*
	 * Kernel Profiling Driver is configured.  It is a part of SOME SECs
	 * (those witht the profiling firmware...).  If we have defined
	 * KERNEL_PROFILING, we look for this device one all SECs in the
	 * system. The first one we find is it.
	 */
	for ( j = 0, dev = (struct sec_device *) driver->devices;
	     j < sec_limit; j++, dev++ ) {
		if ( !(SECvec & 1<<j) )
			continue;
		if ( kpprobe( &SEC_desc[j] ) )
			break;
	}
	if ( j <  sec_limit ) {
		struct sec_scsi_device *kps;
		extern struct sec_dev sec_kp;
		kps = (struct sec_scsi_device *)
			calloc(sizeof(struct sec_scsi_device));
		kp_driver.devices = (struct gen_device *) kps;
		kp_driver.size_dev = sizeof (struct sec_scsi_device);

		if ( ! kps )
			cmn_err( CE_PANIC,
				"sec_init: not enough memory to init kp");
		/*
		 *+ The kernel ran out of memory before it
		 *+ could allocate the data structures
		 *+ necessary to initialize the SEC Kernel
		 *+ Profiler.  
		 */
		
		kps->driver = &kp_driver;
		kps->next = (struct gen_device *) dev->sec_device_chain;
		dev->sec_device_chain = kps;
		kps->sec_dev = &sec_kp;
		sec_kp.sd_sec_idx = j;
		sec_kp.sd_desc = SEC_desc + j;
		sec_kp.sd_chan = SDEV_PROFILER;
		sec_kp.sd_alive = 1;
		sec_kp.sd_cib = sec_cibs[j] + SDEV_PROFILER;
		SEC_allocate_progqs( &sec_kp );
		kpboot( &sec_kp );
	}
#endif	/* KERNEL_PROFILING */

}

/*
 * sec_sec_scsi_probe_setup()
 *	Probe for devices attached to SCSI controllers.
 *
 * Probing gets tricky when ambiguous specifications of devices
 * are allowed.  Too much wildcarding can require that we
 * keep track of where devices were found, to avoid looking
 * for other devices there.  The array 'found' is a 2-D table of
 * bits; a one in the (sec, chan) position of the table means we found
 * a device on that SEC at that channel number (SEC device number
 * between 0 and 95).
 *
 * For the moment, let's just "know" how to index this,
 * as I don't know where the macros that manipulate such
 * a table should live.
 */

sec_sec_scsi_probe_setup( device)
	struct gen_device *device;
{
	struct sec_scsi_device *dev = (struct sec_scsi_device *) device;
	struct sec_dev *sd;
	register struct sec_desc	*sec_desc;
	static ulong	found[MAXNUMSEC*3];
	static sec_scsi_probed_yet = 0;
	int secno, chan, i;

	if ( ! sec_scsi_probed_yet ) {
		sec_scsi_probed_yet++;
		for (secno = 0; secno < MAXNUMSEC; ++secno)
			for (chan = 0; chan < 3; ++chan)
				found[secno*3 + chan] = 0;
	}
	
	/*
	 * Before we go any further, we need to fill out the sec_dev structure
	 * so that devices which don't know about new config will still work.
	 * (sec_dev is pointed to by the sec_scsi_device structure.  The only
	 * reason it is not part of the structure is so old drivers will still
	 * work with the new autoconfig.)  
	 */

	if ( !dev->sec_dev ) {
		/*
		 * We haven't allocated a block of sec_dev structures for this
		 * driver yet. Do it now, and allocate them to each device.
		 */
		sd = (struct sec_dev *)
			calloc(dev->driver->numdev * sizeof (struct sec_dev));
		if ( ! sd )
			cmn_err( CE_PANIC,
	      "sec_sec_scsi_probe_setup: No memory for %s driver sec_dev array",
				dev->driver->name);
		/*
		 *+ On the first probe to any sec_scsi based device, the kernel
		 *+ allocates a block of
		 *+ memory for the probe and init routines (for
		 *+ backwards compatibility).  The kernel did not have enough
		 *+ memory left to allocate this structure for the named
		 *+ driver.  Unless a very large number of devices are
		 *+ configured, this error usually points to a serious shortage
		 *+ of memory or to a bug in some driver requesting hugh chunks of
		 *+ kernel memory.
		 */

		/* Now, parcel out the array */
		for ( dev = (struct sec_scsi_device *)dev->driver->devices,
		     i=0;
		     i < device->driver->numdev;
		     i++, sd++,
		     dev = (struct sec_scsi_device *)
		                     ((char *)dev + device->driver->size_dev) )
			dev->sec_dev = sd;
		/* restore dev to its former glory */
		dev = (struct sec_scsi_device *) device;
	}
	dev->sec_dev->sd_sec_idx = DEVICEN(dev->bus->owner);
	dev->sec_dev->sd_target = dev->config->space;
	dev->sec_dev->sd_unit = dev->config->dev_addr;
	dev->sec_dev->sd_bin = device->config->busint;
	/* this last one can be overridden by the individual probe routines */
	dev->sec_dev->sd_flags = 0;

	/*
	 * If it's there, fill out configure fields in `dev'.
	 */
	
	if ((sec_desc = SEC_probe( dev, found)) == 0)
		return 0;
	found[(sec_desc-SEC_desc)*3 + (dev->sec_dev->sd_chan >> 5)]
		|= 1 << (dev->sec_dev->sd_chan & 0x1F);
	dev->sec_dev->sd_desc = sec_desc;
	dev->sec_dev->sd_alive = 1;

	/*
	 * Allocate the request and done queues.
	 * The program pointers themselves are filled
	 * in later by the driver (at boot time).
	 * The pointer to the cib queue is filled in
	 * by SEC_init_channels
	 */
	SEC_allocate_progqs( dev->sec_dev);
	ASSERT(dev->sec_dev->sd_requestq != 0, "sec_sec_scsi: requestq null");
	/*
	 *+ SCED initialization is invalid.  The system is in an inconsistent
	 *+ internal state.
	 */
	ASSERT(dev->sec_dev->sd_doneq != 0, "sec_sec_scsi: doneq null");
	/*
	 *+ SCED initialization is invalid.  The system is in 
	 *+ an inconsistent internal state.
	 */
	dev->sec_dev->sd_cib = sec_cibs[ dev->sec_dev->sd_sec_idx ]
		+ SDEV_SCSISTART + 8*dev->sec_dev->sd_target
			+ dev->sec_dev->sd_unit;
	ASSERT(dev->sec_dev->sd_cib != 0, "sec_sec_scsi: channel cib null");
	/*
	 *+ SCED initialization is invalid.  The system is in 
	 *+ an inconsistent internal state.
	 */
	ASSERT(dev->sec_dev->sd_bin != 0, "sec_sec_scsi: channel bin 0");
	/*
	 *+ SCED initialization is invalid.  The system is in 
	 *+ an inconsistent internal state.
	 */
	/* Augment probe's message */
	CPRINTF("# %s config unit %d on %s is target adaptor %d ",
	       dev->driver->name,
	       (dev->config - dev->on_bus->config_array)/dev->on_bus->nspaces,
	       bus_type[dev->bus->bustype], dev->sec_dev->sd_target);
	CPRINTF("logical unit %d channel %d\n        ",
	       dev->sec_dev->sd_unit, dev->sec_dev->sd_chan);
	return 1;
}

/*
 * SEC_probe()
 *	Probe for a device on a scsi/ether controller.
 *
 * Handles wild-card SEC specification, finding a SEC with the device
 * if it exists.  But we won't look where a device has already been found.
 *
 * Returns pointer to SEC descriptor that device lives on or zero if
 * device not found.
 */

static struct sec_desc *
SEC_probe( dev, found )
	struct sec_scsi_device *dev;
	ulong found[MAXNUMSEC*3];
{
	register struct sec_dev *sd = dev->sec_dev;
	register struct sec_desc *sec;
	register int target, lun;
	int lun_len;
	int proberes, lun_wild = 0, target_wild = 0;
	struct sec_dev sp;

	/*
	 * SEC is either wild-carded or must exist in the configuration.
	 */

	if (!SEC_EXISTS(sd->sd_sec_idx))
		return ((struct sec_desc *)0);

	/*
	 * If no probe procedure, then assume it exists if the
	 * desired SEC exists.  Note:  SEC_EXISTS is false for
	 * wildcard SEC index.
	 */
	
	if ( dev->config->dev_len == 0)
		lun_wild++;

	if (!dev->driver->probe) {
		/*
		 * If we get this far and don't have a probe routine,
		 * autoconfigure must not think we are wildcarded.  The only
		 * wildcard it doesn't know about is logical unit number
		 */
		if ( lun_wild ) {
			cmn_err( CE_WARN,
				"sec_scsi: can't probed %s for wildcards.\n",
				dev->driver->name);
			/*
			 *+ The SCED autoconfiguration code
			 *+ doesn't handle wildcarded logical unit
			 *+ numbers.
			 */
			return ((struct sec_desc *)0);
		}
		if (SEC_EXISTS(sd->sd_sec_idx))
			return (&SEC_desc[sd->sd_sec_idx]);
		else
			return ((struct sec_desc *)0);
	}

	/*
	 * It has a probe procedure, so we have to try to find it.
	 * For SCSI devices, this means we may have to look at the target
	 * adapter and unit, as well as the controller.
	 */
	if ( dev->unit < 0 ) 
		target_wild++;

	sec = &SEC_desc[sd->sd_sec_idx];
	SECnoprobe[sd->sd_sec_idx] |= 1 << sec->sec_target_no;
	sd->sd_desc = sec;	
	/*
	 * If this is our first time here, probe the guy at the
	 * distinguished target location to see if it is an embedded
	 * SCSI disk. We only count on "sd" knowning how to do this probe.
	 */
	if ((strcmp(dev->driver->name, "sd") == 0)
	    && target_wild
	    && (CCS_magic_target != sec->sec_target_no)
	    && (probescsi_yet == 0)) {
		probescsi_yet++;
		sp.sd_desc = sd->sd_desc;
		sp.sd_flags = sd->sd_flags;
		sp.sd_target = CCS_magic_target;
		sp.sd_unit = 0;
		sp.sd_chan = SDEV_SCSISTART + CCS_magic_target*8;
		
		dev->sec_dev = &sp;
		/* The probe itself is enough to set CCS_present */
		proberes = (*dev->driver->probe)(dev);
#ifdef DEBUG
		if ( boot_debug )
			CPRINTF( "SEC_probe: magic probe = 0x%x\n", proberes);
#endif /* DEBUG */
		dev->sec_dev = sd;
	}

	if ( target_wild )
		target = CCS_present ? 0 : 7;
	else	target = dev->config->space;

	if ( lun_wild ) {
		/*
		 * Wildcard logical unit number.  We need to find the right
		 * block of config records to start in, observing the
		 * conventions.  Note that we (SEC_probe) are keeping track of
		 * where on the SEC_SCSI buses, devices are being found.
		 */
		for ( dev->config = &sec_scsi_config[0];
		     dev->config->dev_len != -1;
		     dev->config++ )
			if ( dev->config->space == target
			    && dev->config->dev_len == (CCS_present? 8 : 1) )
				break;
	}
	lun = dev->config->dev_addr;

	for ( ;dev->config->dev_len != -1; dev->config++ ) {
		/* Non-wildcard probes failed */
		if ( !target_wild && dev->config->space != target )
			break;
		if ( !lun_wild && dev->config->dev_addr != lun )
			break;
		sd->sd_target = target = dev->config->space;
		sd->sd_unit = lun = dev->config->dev_addr;
		sd->sd_chan = SDEV_SCSISTART + target*8 + lun;
		if (SECnoprobe[sd->sd_sec_idx] & (1 << target)) {
			while( dev->config->dev_len != -1
			      && dev->config->space == target )
				dev->config++;
			if ( dev->config->dev_len == -1 )
				break;
			else {
				dev->config--; /* cancel loop increment */
				continue;
			}
		}
		if (found[sd->sd_sec_idx*3 + (sd->sd_chan >> 5)]
		    & (1 << (sd->sd_chan & 0x1F)))
			continue;
		
		proberes = (*dev->driver->probe)(dev);

#ifdef DEBUG
		if ( boot_debug )
			CPRINTF( "%s: target %d lun %d chan 0x%x result 0x%x\n",
			       "SEC_probe", sd->sd_target, sd->sd_unit,
			       sd->sd_chan, proberes); 
#endif /* DEBUG */
		if ((proberes & SECP_ONELUN) == SECP_ONELUN) {
			lun_len = 8;
			SECnoprobe[sd->sd_sec_idx] |= 1 << target;
		} else	lun_len = 1;

		if ((proberes & SECP_FOUND) == SECP_FOUND) {
			/* Be sure the right type of record is returned */
			if ( lun_len != dev->config->dev_len ) {
				for ( dev->config = &sec_scsi_config[0];
				     dev->config->dev_len != -1;
				     dev->config++)
					if ( dev->config->dev_len == lun_len
					    && dev->config->space == target
					    && dev->config->dev_addr == lun )
						break;
				if ( dev->config->dev_len == -1 ) {
					cmn_err( CE_WARN,
			    "sec_scsi_config malformed, probe of %s%d aborted",
						dev->driver->name,
						((char *)dev
						 -(char *)dev->driver->devices)
						/dev->driver->size_dev);
			/*
			 *+ One or more devices on the SCSI bus
			 *+ have invalid configuration data.
			 */
					break;
				}
			}
			sd->sd_alive = 1;
			return (sec);
		}

		/*
		 * no need to scan for
		 * more units or ever look at this
		 * target again.
		 */
		if (proberes & SECP_NOTARGET) {

			/*
			 * NO TARGET disables further probes on this target
			 * adapter only if logical unit nmber zero is the one
			 * probed.  Otherwise, it indicates that the
			 * target/logical unit number pair doesn't exist.  We
			 * pretent we found it so we don't probe it again.
			 */
			if (lun == 0) {
				SECnoprobe[sd->sd_sec_idx] |= 1 << target;
			}
			found[sd->sd_sec_idx*3 + (sd->sd_chan >> 5)]
			      |= 1 << (sd->sd_chan & 0x1f);
		}
	}

	/*
	 * Ran through the existing targets, etc. and didn't find it;
	 * Short circuit target and lun probe.
	 */
	while ( dev->config->dev_len != -1 )
		dev->config++;

	return ((struct sec_desc *)0);
}



/*
 * Allocate program queues for SEC devices.
 */

static
SEC_allocate_progqs(sd)
	register struct sec_dev *sd;
{
	register int nbytes;

	/*
	 * Allocate the request queue, and the programs.
	 */
	nbytes = (int)(&((struct sec_progq *)0)->pq_un.pq_progs[sd->sd_req_size]);
	if (nbytes <= 0) {
		sd->sd_requestq = (struct sec_progq *)0;
	} else {
		sd->sd_requestq = (struct sec_progq *) calloc(nbytes);
	}

	if (sd->sd_chan != SDEV_ETHERREAD) {
		/*
		 * Non-ether-read output queue.
		 * These all look alike from device to device.
		 * We just allocate the output queue, as the programs
		 * will be filled in by firmware as the programs complete.
		 */
		nbytes = (int)(&((struct sec_progq *)0)->pq_un.pq_progs[sd->sd_doneq_size]);
		if (nbytes <= 0) {
			sd->sd_doneq = (struct sec_progq *)0;
		} else {
			sd->sd_doneq = (struct sec_progq *)calloc(nbytes);
		}
	} else {
		/*
		 * Ether read output (done) queue looks different.
		 */
		nbytes = (int)(&((struct sec_eprogq *)0)->epq_status[sd->sd_doneq_size]);
		if (nbytes <= 0) {
			sd->sd_doneq = (struct sec_progq *)0;
		} else {
			sd->sd_doneq = (struct sec_progq *)calloc(nbytes);
		}
	}
}

/*
 * SEC_fill_progq - fill a program queue with device programs.
 *
 * This may be called by the device driver from its boot procedure
 * to fill in the array of drive program pointers allocated by
 * the autoconfig code.
 */

SEC_fill_progq(progq, n, width)
	register struct sec_progq *progq;
{
	register int i;
	caddr_t	base = calloc(n*width);

	for (i = 0; i < n; ++i) {
		progq->pq_un.pq_progs[i] = (struct sec_dev_prog *)(base + i*width);
#ifdef DEBUG
		if (sec_debug > 1 || (sec_debug > 0 && i == 0))
			CPRINTF("0x%x->pq_un.pq_progs[%d] = 0x%x\n",
					progq, i, progq->pq_un.pq_progs[i]);
#endif	/* DEBUG  */
	}
	progq->pq_head = progq->pq_tail = 0;
}


/*
 * SEC_error()
 *	Error interrupt from SEC board(s).
 *
 * SEC received an access error. Report and panic.
 * This is a SPLHI interrupt routine.
 */

static void
SEC_error(vector)
	unchar	vector;
{
	int offset;

	offset = vector - SEC_errbase;
	cmn_err(CE_CONT, "SEC%d received Access Error\n", offset);
	access_error(SEC_accerr[offset]);
	cmn_err(CE_PANIC, "SEC Access Error");
	/*
	 *+ The SCED received an access error.
	 */
}

/* 
 * The following are run-time support routines used by device drivers.
 */

#if	NBPG >= IATBYTES			/* eg, SGS */

/*
 * buf_iat()
 *	Takes a "bp" (struct buf *) and iat (struct iat *) to
 *	setup an indirect address table for the SED.
 *
 * Returns returns nothing.
 * Does *no* locking.
 * Panics if bad pte found; "can't" happen.
 *
 * Implementation assumes that the number of iat slots
 * pointed to by the iat parameter has enough entries to
 * handle (bp->b_bcount+(IATBYTES-1))/IATBYTES iat slots.
 *
 * B_RAWIO, B_PTEIO, B_PTBIO cases must flush TLB to avoid stale mappings
 * thru Usrptmap[], since this is callable from interrupt procedures.
 *
 * Also assumes NBPG >= IATBYTES, and IATBYTES divides CLBYTES.
 */

/*ARGSUSED*/
buf_iat(bp, iat, niat)
	struct	buf	*bp;			/* buffer header */
	register struct	sec_iat	*iat;		/* iat descriptor */
	int		niat;			/* Max iat's to spray */
{
	register unsigned paddr = 0;
	register int	bcount = bp->b_bcount;
	struct	pte	*pte;
	int		junkbytes = 0;
	unsigned	pgoffset;
	unsigned	iatoffset;
	struct pte	*vtopte();

#ifdef	DEBUG
	ASSERT(iat != 0, "buf_iat: zero iat");
	ASSERT(bcount > 0, "buf_iat: b_bcount <=0");
	ASSERT(niat > 0, "buf_iat: zero niat");
#endif	/* DEBUG  */

	/*
	 * Source/target pte's are found differently based on type
	 * of IO operation.
	 */

	switch(bp->b_iotype) {
	case B_RAWIO:					/* RAW IO */
		/*
		 * In this case, must look into alignment of physical
		 * memory, since we can start on arbitrary boundary.
		 * Once the offset into the page is found and mapped,
		 * the iat's are sprayed in IATBYTE chunks until b_bcount 
		 * is zero.
		 */

		flush_tlb();
		junkbytes = bcount % DEV_BSIZE;
		pte = vtopte(bp->b_proc, btop(bp->b_un.b_addr));
		pgoffset = (int)bp->b_un.b_addr & (NBPG-1);
		iatoffset = pgoffset & (IATBYTES-1);
		paddr = PTETOPHYS(*pte) + pgoffset;
		iat->iat_data = (unchar *) paddr;
		iat->iat_count = MIN(IATBYTES-iatoffset, bcount);
		bcount -= iat->iat_count;
		paddr += iat->iat_count;
#ifdef	DEBUG
		--niat;
		if (bcount > 0)
			ASSERT((paddr&(IATBYTES-1)) == 0, "buf_iat: RAW paddr");
		if (sec_debug)
			CPRINTF("iat = 0x%x %d\n", iat->iat_data,iat->iat_count);
#endif	/* DEBUG  */
		++pte;
		++iat;
		break;

	case B_FILIO:					/* file-sys IO */
		/*
		 * Filesys/buffer-cache IO.  These are always cluster aligned
		 * both physically and virtually.  b_bcount is a multiple of 
		 * DEV_BSIZE.
		 */

		pte = &Sysmap[btop(bp->b_un.b_addr)];
#ifdef	DEBUG
		if (((int)bp->b_un.b_addr & (IATBYTES-1)) != 0) {
			CPRINTF("bp=0x%x, addr=0x%x\n", bp, bp->b_un.b_addr);
			cmn_err(CE_PANIC, "buf_iat: bad FS IO");
		}
		if (bp->b_bcount <= 0 || bp->b_bcount > MAXBSIZE) {
			CPRINTF("bp=0x%x, bcount=%d\n", bp, bp->b_bcount);
			cmn_err(CE_PANIC, "buf_iat: bad FS count");
		}
#endif	/* DEBUG  */
		break;

	case B_PTBIO:					/* Page-Table IO */
		/*
		 * Page-Table IO: like B_PTEIO, but can start/end with
		 * non-cluster aligned memory (but is always HW page
		 * aligned).  Count is multiple of NBPG.
		 *
		 * Since NBPG >= IATBYTES, this case is identical to
		 * B_PTEIO, so...
		 */

	case B_PTEIO:					/* swap/page IO */
		/*
		 * Pte-based IO -- already know pte of 1st page, which
		 * is cluster aligned, and b_count is a multiple of CLBYTES.
		 */

		flush_tlb();
		pte = bp->b_un.b_pte;
		break;

	default:
		bad_b_iotype("buf_iat");
		/*NOTREACHED*/
	}

	/*
	 * Check count, and set up the iat's.
	 */

#ifdef	DEBUG
	ASSERT(howmany(bcount, IATBYTES) <= niat, "buf_iat: too big");
#endif	/* DEBUG  */

	for (; bcount > 0; paddr += IATBYTES, bcount -= IATBYTES, iat++) {
		if ((paddr & (NBPG-1)) == 0) {
#ifdef	DEBUG
			ASSERT(PTEPF(*pte) != 0, "buf_iat: null pfnum");
#endif	/* DEBUG  */
			paddr = PTETOPHYS(*pte);
			++pte;
		}
		iat->iat_count = MIN(bcount, IATBYTES);
		iat->iat_data = (unchar *) paddr;
#ifdef	DEBUG
		if (sec_debug)
			CPRINTF("iat = 0x%x %d\n", iat->iat_data,iat->iat_count);
		ASSERT(iat->iat_count > 0, "buf_iat: zero iat count detected");
#endif	/* DEBUG  */
	}

	/*
	 * On raw xfers, round up to a DEV_BSIZE (512) byte transfer because
	 * interface is block (DEV_BSIZE) oriented.
	 */

	if (junkbytes) {
		iat->iat_count = DEV_BSIZE - junkbytes;
		iat->iat_data = SECgarbuf;
#ifdef DEBUG
		if (sec_debug > 1)
			CPRINTF("buf_iat: %d garbage bytes from bcount = %d\n",
					iat->iat_count, bp->b_bcount); 
#endif	/* DEBUG  */
	}
}

/*
 * bufiat_sz()
 *	Takes a "bp" (struct buf *) and returns	the number of
 *	indirect address tables needed for this io operation.
 *
 * Returns count of iats required.
 *
 * Does *no* locking.
 *
 * Assumes NBPG >= IATBYTES.
 */

buf_iatsz(bp)
	register struct	buf	*bp;			/* buffer header */
{

	switch(bp->b_iotype) {
	case B_RAWIO:					/* RAW IO */
		/*
		 * Raw can start on any byte boundary so will add an
		 * extra iat to map an additional page when it is
		 * needed.
		 *
		 * IATVARIANCE is used to tell the device drivers the 
		 * number of iat's used for data alignment so that the
		 * the drivers can calculate the maximum transfer count
		 * based on an iat count.
		 *
		 * Add an additional iat for garbage collection of the
		 * last block.
		 */

		return (howmany(bp->b_bcount, IATBYTES) + IATVARIANCE +1);

	case B_PTEIO:					/* swap/page IO */
	case B_PTBIO:					/* Page-Table IO */
	case B_FILIO:					/* file-sys IO */
		/*
		 * Pte-based IO -- already know pte of 1st page, which
		 * is cluster aligned, and b_count is a multiple of CLBYTES.
		 *
		 * Page-table based IO is page-aligned and b_bcount is
		 * a multiple of NBPG.
		 *
		 * Filesys/buffer-cache IO.  These always start cluster aligned
		 * both physically and virtually. 
		 */
		return (howmany(bp->b_bcount, IATBYTES));

	default:
		bad_b_iotype("buf_iatsz");
		/*NOTREACHED*/
	}
}

#else	/* NBPG < IATBYTESP */ /* eg, FGS */

/*
 * PTESEDOFF returns offset of memory pointed at by pte.
 */

#define	PTESEDOFF(pte) \
		((unsigned)((*(int*)(&(pte))) & ((~(NBPG-1))&(IATBYTES-1))))

/*
 * buf_iat()
 *	Takes a "bp" (struct buf *) and iat (struct iat *) to
 *	setup an indirect address table for the SED.
 *
 * Returns returns nothing.
 * Does *no* locking.
 * Panics if bad pte found; "can't" happen.
 *
 * Implementation assumes that the number of iat slots
 * pointed to by the iat parameter has enough entries to
 * handle (bp->b_bcount+(CLBYTES-1))/CLBYTES iat slots.
 * Also assumes NBPG<=IATBYTES<=CLBYTES, and IATBYTES divides CLBYTES.
 */

buf_iat(bp, iat, niat)
	register struct	buf	*bp;		/* buffer header */
	register struct	sec_iat	*iat;		/* iat descriptor */
	int		niat;			/* Max iat's to spray */
{
	register struct pte *pte;
	register int	count;
	register int	bcount;
	int		offset;
	struct pte	*vtopte();
	int		iatbytes = 0;
	extern		unchar	*SECgarbuf;

#ifdef	DEBUG
	ASSERT(iat != 0, "buf_iat: zero iat");
	ASSERT(bp->b_bcount > 0, "buf_iat: b_bcount <=0");
	ASSERT(niat > 0, "buf_iat: zero niat");
#endif	/* DEBUG  */

	/*
	 * Source/target pte's are found differently based on type
	 * of IO operation.
	 */

	switch(bp->b_iotype) {
	case B_RAWIO:					/* RAW IO */
		/*
		 * In this case, must look into alignment of physical
		 * memory, since we can start on arbitrary boundary.
		 * Once the offset into the page is found and mapped,
		 * the iat's are sprayed in IATBYTE chunks until b_bcount 
		 * is zero.
		 */

		flush_tlb();
		pte = vtopte(bp->b_proc, btop(bp->b_un.b_addr));
		offset = PTESEDOFF(*pte) + ((int)bp->b_un.b_addr & (NBPG-1));
		pte -= btop(offset);
		iat->iat_data = (unchar *)PTETOPHYS(*pte) + offset;
		iat->iat_count = MIN(IATBYTES-offset, bp->b_bcount);
		bcount = bp->b_bcount - iat->iat_count;
		count = (bcount + (IATBYTES-1)) / IATBYTES;
		iat++;
		niat--;
		pte += IATSIZE;
		iatbytes = bp->b_bcount % DEV_BSIZE;
		break;

	case B_FILIO:					/* file-sys IO */
		/*
		 * Filesys/buffer-cache IO.  These are always cluster aligned
		 * both physically and virtually.  b_bcount is a multiple of 
		 * DEVBSIZE (512, currently).
		 */

		pte = &Sysmap[btop(bp->b_un.b_addr)];
		bcount = bp->b_bcount;
		count = (bcount + IATBYTES-1) / IATBYTES;
#ifdef	DEBUG
		if (((int)bp->b_un.b_addr & (IATBYTES-1)) != 0) {
			CPRINTF("bp=0x%x, addr=0x%x\n", bp, bp->b_un.b_addr);
			cmn_err(CE_PANIC, "buf_iat: bad FS IO");
		}
		if (bp->b_bcount <= 0 || bp->b_bcount > MAXBSIZE) {
			CPRINTF("bp=0x%x, bcount=%d\n", bp, bp->b_bcount);
			cmn_err(CE_PANIC, "buf_iat: bad FS count");
		}
#endif	/* DEBUG  */
		break;

	case B_PTEIO:					/* swap/page IO */
		/*
		 * Pte-based IO -- already know pte of 1st page, which
		 * is cluster aligned, and b_count is a multiple of CLBYTES.
		 * Since IATBYTES <= CLBYTES, and IATBYTES divides CLBYTES,
		 * can just divide IATBYTES.
		 *
		 * One special case here is read/write of Uarea during
		 * swap -- swap code passes in b_bcount = UPAGES*NBPG
		 * and b_iotype == B_PTEIO.  Thus, insure CLSIZE divides
		 * UPAGES.
		 */

#		if UPAGES % CLSIZE != 0
			ERROR -- code assumes UPAGES is multiple of CLSIZE
#		endif

		flush_tlb();
		pte = bp->b_un.b_pte;
		bcount = bp->b_bcount;
		count = bcount / IATBYTES;
		break;

	case B_PTBIO:					/* Page-Table IO */
		/*
		 * Page-Table IO: like B_PTEIO, but can start/end with
		 * non-cluster aligned memory (but is always HW page
		 * aligned).  Count is multiple of NBPG.  Case mostly
		 * borrowed from B_RAWIO.
		 *
		 * Separate case for greater efficiency in B_PTEIO.
		 */

		flush_tlb();
		pte = bp->b_un.b_pte;
		offset = PTESEDOFF(*pte);
		pte -= btop(offset);
		iat->iat_data = (unchar *)PTETOPHYS(*pte) + offset;
		iat->iat_count = MIN(IATBYTES-offset, bp->b_bcount);
		bcount = bp->b_bcount - iat->iat_count;
		count = (bcount + (IATBYTES-1)) / IATBYTES;
		iat++;
		niat--;
		pte += IATSIZE;
		break;

	default:
		bad_b_iotype("buf_iat");
		/*NOTREACHED*/
	}

	/*
	 * Check count, and set up the iat's.
	 */

	ASSERT(count <= niat, "buf_iat: too big");
	/*
	 *+ The system didn't allocate enough indirect address tables
	 *+ for the size of the transfer.
	 */

	for (; count--; pte += (IATBYTES/NBPG), bcount -= IATBYTES, iat++) {
		iat->iat_count = MIN(bcount, IATBYTES);
		iat->iat_data = (unchar *)PTETOPHYS(*pte);
#ifdef	DEBUG
		if (sec_debug)
			CPRINTF("iat = 0x%x %d\n", PTETOPHYS(*pte), iat->iat_count);
		ASSERT(PTEPF(*pte) != 0, "buf_iat: null pfnum");
		ASSERT((PTETOPHYS(*pte) & (IATBYTES-1)) == 0, "buf_iat: unaligned");
		ASSERT(iat->iat_count > 0, "buf_iat: zero iat count detected");
#endif	/* DEBUG  */
	}

	/*
	 * On raw xfers, round up to a DEV_BSIZE (512) byte transfer because
	 * interface is block (DEV_BSIZE) oriented.
	 */
	if (iatbytes>0) {
		iat->iat_count = DEV_BSIZE - iatbytes;
		iat->iat_data = SECgarbuf;
#ifdef DEBUG
		if (sec_debug>1)
			CPRINTF("collecting garbage bytes of %d from a bcount of %d\n",
					iat->iat_count, bp->b_bcount); 
#endif	/* DEBUG  */
	}
}

/*
 * bufiat_sz()
 *	Takes a "bp" (struct buf *) to
 *	determine the number of indirect address tables needed for
 *	this io operation.
 *
 * Returns count of iats required.
 * Does *no* locking.
 *
 * Guesstimate number of iat's needed for this xfer.
 * Assumes NBPG<=IATBYTES<=CLBYTES
 */

buf_iatsz(bp)
	register struct	buf	*bp;			/* buffer header */
{

	switch(bp->b_iotype) {
	case B_RAWIO:					/* RAW IO */
	case B_PTBIO:					/* Page-Table IO */
		/*
		 * Raw can start on any byte boundary so will add an
		 * extra iat to map an additional page when it is
		 * needed.  Page-Table IO similar (can start in middle
		 * of page), but more constrained.
		 *
		 * IATVARIANCE is used to tell the device drivers the 
		 * number of iat's used for data alignment so that the
		 * the drivers can calculate the maximum transfer count
		 * based on an iat count.
		 *
		 * Add an additional iat for garbage collection of the last block.
		 */

		return (howmany(bp->b_bcount, IATBYTES) + IATVARIANCE +1);

	case B_PTEIO:					/* swap/page IO */
	case B_FILIO:					/* file-sys IO */
		/*
		 * Pte-based IO -- already know pte of 1st page, which
		 * is cluster aligned, and b_count is a multiple of CLBYTES.
		 *
		 * Filesys/buffer-cache IO.  These always start cluster aligned
		 * both physically and virtually. 
		 */
		return (howmany(bp->b_bcount, IATBYTES));

	default:
		bad_b_iotype("buf_iatsz");
		/*NOTREACHED*/
	}
}

#endif	/* NBPG >= IATBYTES  */

/*
 * SEC_startio - start an operation by sending a command to the
 *		sec board via slic.
 *
 * This procedure may be used until the init command is sent
 * to turn on sec interrupts, which occurs after the boot procedure
 * routines for all device drivers have been called.
 *
 * Calls mIntr() to do the actual slic fussing to send the message.
 *
 * NOTE: It's critical that the status pointer live below 0x400000
 * because the sec can't talk above that address, hence all status
 * variable must *not* live on kernel stack.
 */

SEC_startio(cmd, statptr, bin, mesg, q, slicid)
	register int	*statptr;
	int		cmd;
	unsigned char	bin;
	unsigned char	mesg;
	unsigned char	slicid;
	struct	sec_cib	*q;
{
	register int	spin;

	*statptr = 0;
	q->cib_inst = cmd;
	q->cib_status = statptr;

	mIntr(slicid, bin, mesg);

	spin = calc_delay(10 * SEC_DELAY_TIME);
	while ((*statptr & SINST_INSDONE) == 0) {
		if (spin-- <= 0) {
			cmn_err(CE_WARN, "SEC_startio: timeout");
			/*
			 *+ A SCED startio command timed out.
			 */
			break;
		}
	}
}

/*
 * sec_startio - start an operation by sending a command to the
 *		sec board via slic.
 *
 * This procedure is used once interrupts have been enabled
 * in the kernel.
 *
 * Calls mIntr() to do the actual slic fussing to send the message.  Use
 * bin 3, since this helps avoid SLIC-bus saturation/lockup (since SCED
 * interrupts Dynix mostly on bins 4-7, using bin 3 to interrupt SCED gives
 * SCED -> Dynix priority over Dynix -> SCED, thus SCED won't deadlock
 * against Dynix).  Initialization-time mIntr()'s can use other bins since
 * SLIC-bus is not busy at that time.
 *
 * NOTE: It's critical that the status pointer live below 0x400000
 * because the sec can't talk above that address, hence all status
 * variable must *not* live on kernel stack.
 */

sec_startio(cmd, statptr, sd)
	int		cmd;
	register int	*statptr;
	register struct sec_dev *sd;
{
	register int	spin;

	*statptr = 0;
	sd->sd_cib->cib_inst = cmd;
	sd->sd_cib->cib_status = statptr;

	/*
	 * Must insure that the interrupt gets sent, so don't allow
	 * interrupts on this processor while sending the interrupt.
	 */

#ifdef	ns32000
	{ spl_t spl = splhi();
	mIntr(sd->sd_desc->sec_slicaddr, 3, sd->sd_chan);
	splx(spl);
	}
#endif	/* ns32000  */

#ifdef	i386
	DISABLE();
	mIntr(sd->sd_desc->sec_slicaddr, 3, sd->sd_chan);
	ENABLE();
#endif	/* i386  */

	spin = calc_delay(10 * SEC_DELAY_TIME);
	while ((*statptr & SINST_INSDONE) == 0) {
		if (spin-- <= 0) {
			cmn_err(CE_WARN, "sec_startio: timeout");
			/*
			 *+ A SCED startio command timed out.
			 */
			break;
		}
	}
}

/* 
 * unchar *			<= IATIFIED PTR!
 * SEC_rqinit(rqbuf, rqbufsz)
 *	unchar *rqbuf;
 *
 * SEC_rqinit - init a request sense iat chain to
 *	allow the SEC dma hardware to transfer complete
 *	request sense data buffers which are *not* 8 byte
 *	aligned. This procedure returns an SEC iat pointer
 *	that is used to point at a request sense buffer
 *	(ie. device_prog.dp_datap = rqbuf can be replaced with
 *	device_prog.dp_datap = returned pointer). Max size is 255.
 *
 *	NOTE: the larger the buffer size the more memory this uses! 
 *	Memory used =
 *		sz<8	16 bytes.
 *		sz<24	16+((sz-8)*8) 		; max 144 bytes
 *		sz>24	144+((sz-24)/2)*8+8	; max 1072 bytes (for 255 buf)
 *
 *	This hardware limititation is by-passed by
 *	creating a chain of iat's that point to the
 *	(passed in) buffer pointer in the following
 *	structure respectively:
 *	#iat's_used	length	bytes covered
 *		1	4	0-3	(standard)
 *		1	4	4-7	(extended)
 *		16	1	8-23	(additional)
 *		sz-24/2	2	24-sz	(additional)
 *	
 *	Note: this must be a generic buffer to handle
 *	target adapter interchange since the number of
 *	returned bytes from the target adapter will vary 
 *	based on vendor, error type and buffer size.
 *	Maximum buffer size allowed is 255.
 *	Assumes calloc gets buffer from an address <4Mbyte.
 *	For target adapters that transfer greater than 24 bytes
 *	it's possible to loose the last byte on an odd transfer.
 *	Returns type char * to please lint with straight replacement
 *	in existing drivers. Enforces a minimum of eight bytes to
 *	ease the iat fill out process.
 *
 *	This conforms to ansi X3T9.2/82-2 revision 14 specification.
 *
 */

unchar	*
SEC_rqinit(rqbuf, rqbufsize)
	register unchar	*rqbuf;
	register int	rqbufsize;
{
	register unchar	*ret_iatptr;
	register struct	sec_iat	*iat;		/* iat descriptor */
	register int	count;
	int	 	iat_count;
#ifdef DEBUG
	int		dcnt;
	unchar		*drqbuf = rqbuf;
#endif	/* DEBUG  */

	/*
	 * Calculate the iat chain size.
	 */

	rqbufsize = MAX(rqbufsize, 8);		/* enforce minimum of eight */
	count = rqbufsize-8;
	iat_count = 2;				/* first two */
	if (count>0) {
		iat_count += (count-16 > 0) ? 16 : count;	/* bytes 8-23 */
		count  -= 16;
	}
	if (count>0) {
		iat_count += count/2 + count%2;	/* bytes 24-rqbufsize */
		count	= 0;			/* for sanity */
	}
#ifdef DEBUG
	ASSERT(count<=0, "SEC_rqinit count");
	if (sec_debug>1)
		CPRINTF("SEC_rqinit: iat's needed=%d\n", iat_count);
	dcnt = iat_count;			/* debug */
#endif	/* DEBUG  */
	
	/*
	 * Allocate space for the iat's,
	 * and save the callers reference pointer.
	 */

	iat = (struct sec_iat *)calloc(iat_count*sizeof(struct sec_iat));
	ret_iatptr = (unchar *)SEC_IATIFY(iat);

	/*
	 * Fill out iat chain.
	 */

	count = 0;
	for (; count<2; count++, iat++, rqbuf = (unchar *)((int)rqbuf+4)) {
		iat->iat_count = 4;
		iat->iat_data = rqbuf;
	}
	iat_count -=2;

	/*
	 * Bytes 8-23.
	 */

	count = 0;
	for (; count<16 && iat_count>0; count++, iat++, iat_count--, rqbuf++) {
		iat->iat_count = 1;
		iat->iat_data = rqbuf;
	}

	/*
	 * Bytes 24-rqbufsize.
	 */

	for (; iat_count>0; iat++, iat_count--, rqbuf++, rqbuf++) {
		iat->iat_count = 2;
		iat->iat_data = rqbuf;
	}

	/*
	 * Adjust the last iat if the count is odd, the data ptr is ok.
	 */

	if (rqbufsize&1) {
		iat--;
		iat->iat_count = 1;
	}

#ifdef DEBUG
	if (sec_debug>1) {
		iat = (struct sec_iat *)((uint)ret_iatptr & ~SEC_IAT_FLAG);
		for (; dcnt; dcnt--, iat++)
			CPRINTF("buf 0x%x iat 0x%x count %d retiat 0x%x\n",
				drqbuf, iat->iat_data, iat->iat_count, ret_iatptr);
	}
#endif	/* DEBUG  */
			
	return (ret_iatptr);
}

/*
 * sec_boot()
 *	SEC dependent reboot the machine.
 *
 * Sets up a return of control to the SCED firmware.
 * If called by panic it returns specifying that the
 * alternate boot name is to be booted (normally the 
 * memory dumper).  Attempts to sync the disks
 * prior to returning to firmware.  
 *
 * Invoked with only one engine is alive.
 */
static
sec_boot(paniced, howto)
	int paniced;
	register int howto;
{
	register struct sec_gmode *cbdmptr = &cbdgsm;
	register struct sec_smode *wdtsmptr = &wdtsm;
	register spl_t s_ipl;
	extern etext;
	extern bool_t dblpanic;

	if ((!upyet) || ((paniced == RB_PANIC) && dblpanic))
		return_fw();

	/*
	 * Get powerup reboot structure.
	 */
	cbdmptr->gm_status = 0;
	bootdata.re_powerup = 1;	/* 0 booted data, 1 powerup values */
	cbdmptr->gm_un.gm_board.sec_reboot = &bootdata;
	cbdcib->cib_inst = SINST_GETMODE;
	cbdcib->cib_status = (int *)&cbdgsm;
	s_ipl = splhi();
	mIntr(cons_scsi, 7, SDEV_SCSIBOARD);
	splx(s_ipl);

	while ((cbdmptr->gm_status & SINST_INSDONE) == 0)
		continue;

	if (cbdmptr->gm_status != SINST_INSDONE) {
		cmn_err(CE_NOTE, "Cannot get Console Board modes\n");
		/*
		 *+ The SCED didn't respond to a request for information about
		 *+ reboot.
		 */
		return_fw();
	}

	/*
	 * Now tell FW how to reboot
	 */
	bootdata.re_powerup = 0;	/* 0 booted data, 1 powerup values */
	cbdmptr->gm_un.gm_board.sec_dopoll = 0;		/* no change */
	cbdmptr->gm_un.gm_board.sec_powerup = 0;	/* no change */
	cbdmptr->gm_un.gm_board.sec_errlight = SERR_LIGHT_SAME;

	if (paniced == RB_PANIC) {
		/*
		 * Do a dump, set cfg addr to end of kernel text,
		 * and turn on error light.
		 */
		bootdata.re_boot_flag |= RB_AUXBOOT;
		bootdata.re_cfg_addr[1] = (unsigned char *)&etext;
		cbdmptr->gm_un.gm_board.sec_errlight = SERR_LIGHT_ON;
	} else
		bootdata.re_boot_flag = howto;

	cbdmptr->gm_status = 0;
	cbdcib->cib_inst = SINST_SETMODE;
	cbdcib->cib_status = (int *)&cbdgsm;
	s_ipl = splhi();
	mIntr(cons_scsi, 7, SDEV_SCSIBOARD);
	splx(s_ipl);

	while ((cbdmptr->gm_status & SINST_INSDONE) == 0)
		continue;

	if (cbdmptr->gm_status != SINST_INSDONE) {
		cmn_err(CE_NOTE, "Cannot set Console Board modes\n");
		/*
		 *+ The SCED didn't respond to a request to change its
		 *+ information about reboot.
		 */
		return_fw();
	}

	if (paniced == RB_BOOT) {
		/*
		 * Set watchdog for 1 minute.
		 * Prevent ERROR light from going on...
		 */

		if (wd_tid) {
			untimeout(wd_tid);		/* Stop wdt reset */
			wd_tid = 0;
		}

		wdtsmptr->sm_status = 0;
		wdtsmptr->sm_un.sm_wdt_mode = 60;	/* Set for minute! */

		wdtcib->cib_inst = SINST_SETMODE;
		wdtcib->cib_status = (int *)&wdtsm;
		s_ipl = splhi();
		mIntr(cons_scsi, 7, SDEV_WATCHDOG);
		splx(s_ipl);

		while ((wdtsmptr->sm_status & SINST_INSDONE) == 0)
			continue;

		if (wdtsmptr->sm_status != SINST_INSDONE) {
			cmn_err(CE_NOTE, "Cannot Setmode Watchdog\n"); 
			/*
			 *+ The SCED didn't respond to a request
			 *+ to set watchdog timer modes.
			 */
			return_fw();
		}

		wdtsmptr->sm_status = 0;
		wdtcib->cib_inst = SINST_STARTIO;
		wdtcib->cib_status = (int *)&wdtsm;
		s_ipl = splhi();
		mIntr(cons_scsi, 7, SDEV_WATCHDOG);
		splx(s_ipl);

		while ((wdtsmptr->sm_status & SINST_INSDONE) == 0)
			continue;

		if (wdtsmptr->sm_status != SINST_INSDONE) {
			cmn_err(CE_NOTE, "Cannot Restart Watchdog\n"); 
			/*
			 *+ The SCED didn't respond to a request
			 *+ to restart the watchdog timer.
			 */
			return_fw();
		}
	}
	reboot_sync(howto);
	return_fw();
}

/* 
 * sec_return_fw()
 *	Return control to the SCED firmware.
 *
 * Invoke custom panic handlers (if panicing)
 * and turn off the light show prior to returning.
 */
static
sec_return_fw()
{
	extern	bool_t	conscsi_yet;

	shutdown_light_show();		/* Turn off proc lights, etc. */

	/*
	 * If the console scsi has not yet received its INIT command
	 * then use the powerup cib.
	 */
	if (!conscsi_yet) {
		struct sec_powerup *scp;
		scp = (struct sec_powerup *) (CD_LOC->c_cons->cd_sc_init_queue);
		scp->pu_cib.cib_inst = SINST_RETTODIAG;
		scp->pu_cib.cib_status = SRD_BREAK;
	} else {
		cbdcib->cib_inst = SINST_RETTODIAG;
		cbdcib->cib_status = (!upyet) ? SRD_BREAK : SRD_REBOOT;
	}
#if	defined(DEBUG) && defined(i386)
	/*
	 * Flush the cache to ensure a consistent memory image for a dump.
	 */
	flush_cache();
#endif	/* DEBUG&&i386 */
	mIntr(cons_scsi, 7, SDEV_SCSIBOARD);

	for (;;); 			/* SCED will take control.  */
	/*NOTREACHED*/
}

static
sec_wdtinit()
{
	register struct sec_smode *wdtsmptr = &wdtsm;
	spl_t s_ipl;

	if (sec_wdt_timeout <= 0)
		return;
	wdtsmptr->sm_status = 0;
	wdtsmptr->sm_un.sm_wdt_mode = sec_wdt_timeout;

	wdtcib->cib_inst = SINST_SETMODE;
	wdtcib->cib_status = (int *)&wdtsm;
	s_ipl = splhi();
	mIntr(cons_scsi, 7, SDEV_WATCHDOG);
	splx(s_ipl);

	while ((wdtsmptr->sm_status & SINST_INSDONE) == 0)
		continue;

	if (wdtsmptr->sm_status != SINST_INSDONE)
		cmn_err(CE_PANIC,"Initializing Watchdog");
		/*
		 *+ An error occurred when the kernel tried to initialize the
		 *+ watchdog timer.
		 */
	wd_tid = timeout(sec_wdtreset, (caddr_t)0, hz/2);
}

static
sec_wdtreset()
{
	register struct	sec_smode *wdtsmptr = &wdtsm;
	spl_t s_ipl;

	wdtsmptr->sm_status = 0;
	wdtcib->cib_inst = SINST_STARTIO;
	wdtcib->cib_status = (int *)&wdtsm;

	/*
	 * Tell SCED about the command.  Bin 3 is sufficient, helps avoid
	 * SLIC-bus saturation/lockup (since SCED interrupts Dynix mostly on
	 * bins 4-7, using bin 3 to interrupt SCED gives SCED -> Dynix priority
	 * over Dynix -> SCED, thus SCED won't deadlock against Dynix).
	 */

	s_ipl = splhi();
	mIntr(cons_scsi, 3, SDEV_WATCHDOG);
	splx(s_ipl);

	while ((wdtsmptr->sm_status & SINST_INSDONE) == 0)
		continue;

	if (wdtsmptr->sm_status != SINST_INSDONE)
		cmn_err(CE_PANIC, "Resetting Watchdog");
		/*
		 *+ An error occurred when the kernel tried to reset the
		 *+ watchdog timer.
		 */
	wd_tid = timeout(sec_wdtreset, (caddr_t)0, hz/2);
}

extern unchar	cons_scsi;		/* console scsi slic address */

/* For time-of-day handling */
struct	sec_cib *todcib;
struct	sec_gmode todgm;	/* getmodes command */
struct	sec_smode todsm;	/* setmodes command */

/*
 * Routines to manipulate the SCED based time-of-day register.
 * TOD clock interrupt handling done by todclock in kern_clock.c
 *
 *
 * Inittodr initializes the time-of-day hardware which provides
 * date functions. This starts the time-of-day clock.
 *
 * Base provides the time from the filesystem, and the time-of-day
 * clock provides the rest.
 */
static
sec_inittodr(base)
	time_t base;
{
	extern bool_t check_time();
	uint todr;
	register struct	sec_gmode *todgmptr = &todgm;
	register int i;
	spl_t s_ipl;

	/*
	 * Find console SCED and check if the TOD clock has
	 * failed powerup diagnostics.
	 */
	for (i = 0; i < NSEC; i++) {
		/* is SEC there? */
		if ((SECvec & (1 << i)) == 0)
			continue;

		if (SEC_desc[i].sec_is_cons)
			break;
	}
	if (SEC_desc[i].sec_diag_flags & CFG_S_TOD) {
		/*
		 * Clear todr if TOD failed powerup diagnostics.
		 */
		cmn_err(CE_WARN,
			"WARNING: TOD on SCED failed powerup diagnostics");
		/*
		 *+ The time of day clock on the SCED controlling 
		 *+ the system failed power-up diagnostics.  Check the
		 *+ system date (and reset if necessary) after
		 *+ the system is booted and the failure is repaired.
		 */
		todr = 0;
	} else {
		/*
		 * get the current time-of-day from the SCED tod clock.
		 */
		todgmptr->gm_status = 0;
		todcib->cib_inst = SINST_GETMODE;
		todcib->cib_status = (int *)&todgm;
		s_ipl = splhi();
		mIntr(cons_scsi, TODCLKBIN, SDEV_TOD);
		splx(s_ipl);

		while ((todgmptr->gm_status & SINST_INSDONE) == 0)
			continue;

		if (todgmptr->gm_status != SINST_INSDONE)
			cmn_err(CE_PANIC, "Cannot get TOD value");
			/*
			 *+ The SCED didn't respond to a request
			 *+ for the current time of day.
			 */

		todr = todgmptr->gm_un.gm_tod.tod_newtime;
	}

	(void) check_time((time_t) todr, base);

	/* 
	 * Start the tod clock.
	 */
	sec_resettodr();			
}

/*
 * sec_resettodr restores the time-of-day hardware after a time change.
 * Also, also called via inittodr to start todclock interrupts.
 *
 * Reset the TOD based on the time value; used when the TOD
 * has a preposterous value and also when the time is reset
 * by the stime system call.
 */
static
sec_resettodr()
{
	register struct sec_smode *todsmptr = &todsm;
	spl_t s_ipl;

	todsmptr->sm_status = 0;
	todsmptr->sm_un.sm_tod.tod_freq = TODFREQ;
	todcib->cib_inst = SINST_SETMODE;
	todcib->cib_status = (int *)&todsm;
	s_ipl = splhi();
	todsmptr->sm_un.sm_tod.tod_newtime = time.tv_sec;
	/*
	 * Bin 3 is sufficient, helps avoid SLIC-bus lockup.
	 */
	mIntr(cons_scsi, 3, SDEV_TOD);
	splx(s_ipl);

	while ((todsmptr->sm_status & SINST_INSDONE) == 0)
		continue;

	if (todsmptr->sm_status != SINST_INSDONE)
		cmn_err(CE_PANIC,"Cannot set TOD value");
		/*
		 *+ The time of day clock didn't accept the
		 *+ request to reset the time of day.
		 */
}

static void
bad_b_iotype(fname)
	char *fname;
{
	cmn_err(CE_PANIC, "%s: bad b_iotype", fname);
	/*
	 *+ The specified function was invoked with an invalid
	 *+ buf structure type.  It was invoked as part of the
	 *+ DMA mapping for the SCED controller.
	 */
}
