/* $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: ssm_scsi.c 1.21 90/05/04 $"

/*
 * ssm_scsi.c
 *	routines for manipulating SCSI specific pieces of the SSM.
 */

/* $Log:	ssm_scsi.c,v $
 */
#include "sys/types.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/buf.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/mc_vmparam.h"
#include "sys/mc_mutex.h"
#include "sys/pte.h"
#include "sys/vmmeter.h"
#include "sys/plocal.h"
#include "sys/intctl.h"
#include "sys/cfg.h"
#include "sys/slic.h"
#include "sys/scsi.h"
#include "sys/vmmac.h"
#include "sys/mftpr.h"
#include "sys/cmn_err.h"
#include "sys/iovec.h"
#include "sys/autoconf.h"
#include "sys/scsi_adapter.h"
#include "sys/ssm_scsi.h"
#include "sys/ssm_misc.h"
#include "sys/ssm.h"
#include "sys/ssm_device.h"

/* 
 * Forward references.
 */
static caddr_t ssm_scsi_alloc();
static void ssm_scsi_resource_init(); 	
static void ssm_scsi_handler(), ssm_scsi_bin1_isr(), ssm_scsi_bin2_isr();
static void ssm_scsi_bin3_isr(), ssm_scsi_bin4_isr(), ssm_scsi_bin5_isr();
static void ssm_scsi_bin6_isr(), ssm_scsi_bin7_isr();
static int ssm_scsi_status();

extern caddr_t calloc(), wmemall();
extern int ssm_wdt_timeout;
extern struct device_config sb8000_config[];

/*
 * The static array 'scsi_found' is a table 
 * of 64-bit vectors, one per SSM SCSI.  A set 
 * bit in the [ssm,devno] bit position of the 
 * table represents an existent SSM SCSI device
 * at that devno (SCSI device number made up of 
 * target adapter and logical unit number; 
 * between 0 and 63).  The macros SCSI_IN_USE, 
 * SET_SCSI_IN_USE, SCSI_NOPROBE, and 
 * SET_SCSI_NOPROBE are used to manipulate it.
 *
 * Allocated/used by ssm_ssm_scsi_probe_setup().
 *
 * Note: this scheme requires modification if/
 * when multiple SCSI interfaces appear on a 
 * single SSM.
 */
static unchar *scsi_found;

#define	SCSI_IN_USE(ssm_index,target,unit) \
	(scsi_found[((ssm_index) << 3) + (target)] & 1 << (unit))

#define	SET_SCSI_IN_USE(ssm_index,target,unit) \
	(scsi_found[((ssm_index) << 3) + (target)] |= 1 << (unit))

#define SCSI_NOPROBE(ssm_index,target) \
	(scsi_found[((ssm_index) << 3) + (target)] == 0xff)

#define SET_SCSI_NOPROBE(ssm_index,target) \
	(scsi_found[((ssm_index) << 3) + (target)] = 0xff)

static struct scsi_cb *probe_cbs;	/* SCSI CBs recycled during PROBE */
static scsidev_t **intr_dev_alloc, **intr_dev_limit; /* Interrupt redirection */

/*
 * ssm_scsi_intr is used to gather statics about 
 * SSM SCSI device interrupts mapped during system 
 * initialization, so that an indirect handler can 
 * be set up for them, responsible for managing
 * SSM SCSI resources, analyzing SCSI command
 * termination, and invoking the real driver
 * handler.
 *
 * ssm_scsi_isr addresses SSM SCSI handlers, one
 * per SLIC bin used by these devices.  Since a 
 * handler only is given a vector at interrupt 
 * time, it presumes to know its bin number to 
 * make this scheme feasible.
 */ 
static struct ssm_scsi_intr ssm_scsi_intr[SLICBINS];
void (*(ssm_scsi_isr)[SLICBINS])() = {
		(void (*)())0,
		ssm_scsi_bin1_isr,
		ssm_scsi_bin2_isr,
		ssm_scsi_bin3_isr,
		ssm_scsi_bin4_isr,
		ssm_scsi_bin5_isr,
		ssm_scsi_bin6_isr,
		ssm_scsi_bin7_isr 
};

/* 
 * ssm_ssm_scsi_probe_setup()
 *	Invoked by system configuration code to 
 *	probe the SSM SCSI device 'dev'.
 *	
 * 	Handles wildcarded SSM specification, 
 * 	finding an SSM with this device, if it 
 *	exists.  It optimizes wildcarding and
 *	excessive probing by ignoring target
 *	adapters and/or logical units based
 *	upon the results of previous probes.
 *
 *	SCSI device probe routines may return the 
 *	following values (see scsi_adapter.h):
 *		SPROBE_ONELUN, SPROBE_FOUND, 
 *		SPROBE_NOTARGET, SPROBE_NOLUN,
 *		SPROBE_NOTFOUND.
 */	
int
ssm_ssm_scsi_probe_setup(dev)
	register scsidev_t *dev;
{
	register int target, unit, target_end, unit_max;
	int ssm_index = (struct ssm_device *)dev->gen.bus->owner - ssm_devices;
	int target_start, unit_min, pstatus;
	struct device_config *dconf;
	struct ssm_scsi_adapt adapt;
	
	/*
	 * Verify that this SCSI's SSM exists
	 * and not already at its supportable
	 * device limit.
	 */
	if (ssm_index > ssm_max_idx || !SSM_EXISTS(ssm_index)
	|| dev->gen.bus->devcount >= NDEVSCSI) 
		return (0);

 	/*
	 * Allocate the 'scsi_found' bit-vectors if
	 * not already done.  Not done statically,
	 * since number of SSM's not known.  Then
	 * mark the SSM's target adapter as in use.
 	 */
	if (!scsi_found) 
		scsi_found = (unchar *) calloc((ssm_max_idx + 1) << 3);
	ASSERT(scsi_found, "SSM: No memory for scsi found.\n");
	/*
	 *+ ssm_ssm_scsi_probe_setup was unable to allocate memory
	 *+ to track the SSM controller and SCSI address
	 *+ combinations at which devices have been located.
	 *+ Another driver might be allocating
	 *+ memory excessively.
	 */
	SET_SCSI_NOPROBE(ssm_index, SSM_desc[ssm_index].ssm_target_no);
	
	if (!dev->gen.driver->probe) {
		/*
		 * The driver hasn't a probe routine. 
		 * Assume the device exists if its
		 * location is not being wildcarded
		 * and its ssm exists and there is
		 * not already an address conflict.
		 */
		if (SSM_SCSI_WILD(dev)) {
			CPRINTF("Illegal configuration: %s ", 
				dev->gen.driver->name);
			CPRINTF("device can't be probed for wildcards.\n");
			return (0);
		} 

		/* 
		 * Check for address conflict.
		 * Note that dev->gen.config->dev_len
		 * indicates the range of logical
		 * unit numbers that this device
		 * covers.
		 */
		for (target = dev->gen.config->space, 
		     unit = dev->gen.config->dev_addr,
		     unit_max = unit + dev->gen.config->dev_len - 1;
		     unit <= unit_max; unit++) 
			if (SCSI_IN_USE(ssm_index, target, unit))
				return (0); 

		/*
		 * No address conflict.  Mark device's
		 * address range 'in use' and initialize 
		 * its SSM SCSI data structures.
		 */ 
		if (dev->gen.config->dev_len > 1)
			SET_SCSI_NOPROBE(ssm_index, target);
		else
			SET_SCSI_IN_USE(ssm_index, target, 
				dev->gen.config->dev_addr);
		ssm_scsi_resource_init(dev);
		CPRINTF( "%s%d %s assumed %s%d at target %d ",
			dev->gen.bus->owner->driver->name, 
			DEVICEN(dev->gen.bus->owner),
			bus_type[dev->gen.bus->bustype], dev->gen.driver->name, 
			DEVICEN(dev), target);
		CPRINTF( "logical unit %d as unit %d\n", unit, 
			(dev->gen.config - dev->gen.on_bus->config_array) 
			/ dev->gen.nspaces);
		return (1);
	} 
	
	/*
	 * At this this point we know it has a
	 * probe routine and the SSM's to look
	 * for the device upon.   We must also
	 * determine which target adapters and
	 * logical unit numbers to try.
	 */
	if (SSM_WILD_TARG(dev)) {
		/* Wildcard target adapter numbers from low to high */
		target_start = 0; 
		target_end = 7; 
	} else 
		/* Try only the specified target adapter number */
		target_start = target_end = dev->gen.config->space;
	
	if (SSM_WILD_LUNIT(dev)) {
		/* Wildcard through the unit numbers from low to high */
		unit_min = 0; 
		unit_max = 7;
	} else 
		/* Try only the specified unit number */
		unit_min = unit_max = dev->gen.config->dev_addr;
	
	/*
	 * Valid SSM found ... for the rest
	 * of wildcarding we have to look at 
	 * all target adapters and unit numbers.
	 * But for non-wildcarded entries 
	 * target_start, etc., are set up just 
	 * to look at the right target/unit.
	 */
	for (target = target_start; target <= target_end; target++) {
		if (SCSI_NOPROBE(ssm_index,target)) 
			/*
			 * Loop short-circuit:  Skip the host
			 * target adapter and ones that have 
			 * timed out or at which a device has
			 * already been located.
			 */
			continue;
		
		for (unit = unit_min; unit <= unit_max; unit++) {
			/*
			 * Probe at this combination of SSM#,
			 * target#, and unit# for the device,
			 * if a device has not already been 
			 * located there.
			 */
			if (SCSI_IN_USE(ssm_index, target, unit)) 
				continue;
			
			/* 
			 * Verify that there is a config record 
			 * for this location.
			 */
			if (dev->gen.config->space != target
			||  dev->gen.config->dev_addr != unit) {
				for (dconf = dev->gen.on_bus->config_array;
				     dconf->dev_len != -1; dconf++)
					if (dconf->space == target
					&&  dconf->dev_addr == unit)
						break;	/* Match found */
				if (dconf->dev_len == -1)
					continue; /* No match, skip silently */
				dev->gen.config = dconf;
			}
#ifdef DEBUG
			if (ssm_debug)
				CPRINTF("%s: probe %s%d at TA %d, LUN %d\n",
				       "ssm_ssm_scsi_probe_setup",
				       dev->gen.driver->name,
				       DEVICEN(dev), target, unit);
#endif /* DEBUG */
			/*
			 * Initialize SSM SCSI portions of 
			 * 'dev' so attempted SCSI operations 
			 * allocate needed SSM SCSI resources 
			 * (transparent to the driver).  Then 
			 * invoke the driver probe function,
			 * performing cleanup afterwards.
			 */
			bzero(dev->adapt = (caddr_t)&adapt, sizeof adapt);
			dev->ident = SCA_PROBE_IDENT;
			dev->polled_cmd = ssm_scsi_polled_cmd;
			dev->alloc = ssm_scsi_alloc;
			pstatus = (*dev->gen.driver->probe)(dev);
			dev->adapt = (caddr_t)NULL;
			dev->polled_cmd = (int (*)())NULL;
			dev->alloc = (caddr_t (*)())NULL;
#ifdef DEBUG
			if (ssm_debug)
				CPRINTF("%s: probe status = 0x%x\n",
					"ssm_ssm_scsi_probe_setup", pstatus);
#endif /* DEBUG */
			/* 
			 * Decide which target/logical unit
			 * combinations should no longer be
			 * probed.
			 */
			switch (pstatus) {
			case SPROBE_ONELUN:
			case SPROBE_NOTARGET:
				/* No longer probe this target */
				SET_SCSI_NOPROBE(ssm_index, target);
				break;
			case SPROBE_NOLUN:
			case SPROBE_FOUND:
				/* No longer probe this logical unit */
				SET_SCSI_IN_USE(ssm_index, target, unit);
				break;
			case SPROBE_NOTFOUND:
				break;
			}

			/*
			 * If the probe was successful, initialize 
			 * SSM SCSI resources for it and acknowledge 
			 * that the device is alive.  Otherwise,
			 * try the next possible location.
			 */
			switch (pstatus) {
			case SPROBE_ONELUN:
			case SPROBE_FOUND:
				ssm_scsi_resource_init(dev);
				CPRINTF("# %s configuration unit %d on %s is ",
				       dev->gen.driver->name, (dev->gen.config -
				       dev->gen.on_bus->config_array)
				       /dev->gen.nspaces,
				       bus_type[dev->gen.bus->bustype]);
				CPRINTF("target adaptor %d, logical unit %d\n",
				       target, unit);
				CPRINTF("        ");
				return (1);	/* Found */
			default:
				break;		/* Unsuccessful; try again */
			}
		}			/* End unit for-loop */
	}				/* End target for-loop */
	
	/*
	 * At this point in the code all valid 
	 * combinations of SSM, target, and unit 
	 * have been probed without success.
	 * Short-circuit configuration unit 
	 * wildcarding; it was just done.
	 */
	while (dev->gen.config->dev_len != -1)
		dev->gen.config++;

	return (0);			/* Driver unit not found. */
}

/*
 * ssm_ssm_scsi_intr_setup()
 *	Arrange for 'dev' to receive interrupts 
 *	at SLIC priority 'bin'.  This could 
 *	not be done by ssm_scsi_resource_init() 
 *	since it did not know which 'bin' dev's 
 * 	interrupts map to.  
 *	
 *	Allocate and initialize an interrupt 
 *	vector per CB such that it invokes
 *	an SSM SCSI interrupt service routine 
 *	(ISR) for the designated bin; it 
 *	performs adapter specific tasks prior 
 *	to invoking the driver's ISR.  
 *	init_ssm_scsi_dev() allocates CBs and 
 *	passes information to the SSM SCSI 
 *	firmware.  ssm_ssm_scsi_map_alloc() 
 * 	completes initialization by allocating/
 *	initializing indirection tables based 
 *	on information saved by this function.
 */
int
ssm_ssm_scsi_intr_setup(dev, bin)
	register scsidev_t *dev;
	unchar bin;
{
	register struct ssm_scsi_adapt *adapt = 
		(struct ssm_scsi_adapt *) dev->adapt;
	register struct ssm_scsi_intr *intr = ssm_scsi_intr + bin;
	struct scb_init sinit;
	int count;

	if (!intr_dev_alloc) {
		/* 
		 * Since memory can't be allocated in 
		 * ssm_ssm_scsi_alloc_maps() for the
		 * interrupt redirection tables, anticipate
		 * and allocate space for them now.  This may 
		 * overallocate, but isn't very wasteful.
		 */
		intr_dev_alloc = (scsidev_t **) calloc(NSSM * NDEVSCSI * 
			NCBPERSCSI * sizeof(scsidev_t *));
		ASSERT(intr_dev_alloc, 
			"SSM: No memory for interrupt tables.\n");
		/*
		 *+ The kernel is unable to allocate space for the 
		 *+ SSM SCSI interrupt vector
		 *+ table.  Another driver might be
		 *+ allocating memory excessively.
		 */
		intr_dev_limit = intr_dev_alloc + NSSM * NDEVSCSI * NCBPERSCSI;
	}
#ifdef DEBUG
	ASSERT(adapt, "ssm_ssm_scsi_intr_setup: NULL adapter structure");
	ASSERT(bin < SLICBINS, 
		"ssm_ssm_scsi_intr_setup: bin number out of range");
#else /* DEBUG */
	if (!adapt || bin >= SLICBINS) 
		return (0);
#endif /* DEBUG */

	/*
	 * Allocate an interrupt per CB from 
	 * the designated bin.  Assume ivecall() 
	 * panics if this fails.
	 */
	adapt->bin = bin;
	adapt->basevec = ivecpeek(bin);
	for (count = 0; count < NCBPERSCSI; count++)
		ivecinit(bin, ivecall(bin), ssm_scsi_isr[bin]);

	/*
	 * Store information about these vectors
	 * so they can be redirected to this 
	 * device's driver ISR later.  
	 *
	 * ssm_ssm_scsi_alloc_maps() builds an
	 * redirection table later when called
	 * to perform last minute bus initial-
	 * ization, after other device initial-
	 * ization and interrupt mapping has
	 * occurred.
	 *
	 * Mark 'bin' as 'in use', saving the
	 * range of vectors being directed
	 * to the SSM SCSI ISR (assume vectors 
	 * are sequentially allocated).
	 */
	if (!intr->in_use++) 
		intr->basevec = adapt->basevec;
	intr->topvec = adapt->basevec + NCBPERSCSI - 1;

	/*
	 * Notify SSM firmware of device
	 * characteristics it needs to know,
	 * allocate CBs, and save its SSM 
	 * SCSI i.d.
	 */
	sinit.si_mode = SCB_BOOT;
	sinit.si_flags = 0;
	sinit.si_ssm_slic = dev->gen.bus->owner->config->dev_addr;
	sinit.si_scsi = 0;
	sinit.si_target = dev->gen.config->space;
	sinit.si_lunit = dev->gen.config->dev_addr;
	sinit.si_host_bin = bin;
	sinit.si_host_basevec = adapt->basevec;
	sinit.si_control = 0;
	init_ssm_scsi_dev(&sinit);
	adapt->cb = sinit.si_cbs;
	dev->ident = sinit.si_id;
#ifdef DEBUG
	ASSERT(sinit.si_id >= 0, 
		"ssm_ssm_scsi_intr_setup: Invalid SSM SCSI i.d. returned");
	return (1);
#else /* DEBUG */
	return (sinit.si_cbs && sinit.si_id >= 0 ? 1 : 0);
#endif /* DEBUG */
}

/*
 * ssm_ssm_scsi_alloc_maps()
 *	Invoked by the system's configuration
 *	code to perform final SSM SCSI bus
 *	initialization, after other device 
 *	initialization and interrupt mapping
 *	has occurred and memory management 
 *	turned on.  As such, calloc() cannot 
 *	be executed, although wmemall() can.
 *
 *	Allocate/initialize SSM SCSI interrupt 
 *	redirection tables entries for all 
 *	interrupts/devices mapped on this bus.  
 *	Each table entry addresses the 'dev' 
 *	to which the interrupt pertains.  An 
 *	interrupt vector, relative to its 
 *	device' base vector indicates which CB 
 *	terminated for that device, when the
 *	SSM SCSI ISR is invoked.  CB addresses
 *	are used as the magic cookie returned
 *	by ssm_scsi_start_cmd().
 */
void 
ssm_ssm_scsi_alloc_maps(bus)
	struct bus *bus;
{
	static char ssm_scsi_bins_built;
	register scsidev_t *dev;
	register struct ssm_scsi_adapt *adapt;
	register struct ssm_scsi_intr *intr;
	scsidev_t **vdev;
	int nvecs, ndevs;
	unchar bin;

 	/*
	 * All SSM SCSIs use the same redirection 
	 * table; build it if not already done.
	 */
	if (!ssm_scsi_bins_built) {
		for (bin = 1, intr = ssm_scsi_intr+1; bin++ < SLICBINS; intr++)
			if (intr->in_use) {
				nvecs = intr->topvec - intr->basevec + 1;
				intr->dev = intr_dev_alloc;
				intr_dev_alloc += nvecs;
				ASSERT(intr_dev_alloc <= intr_dev_limit, 
			       "SSM: memory not allocated for vector table.\n");
				/*
				 *+ The SSM SCSI initialization code assumed
				 *+ that it had allocated enough interrupt
				 *+ vectors for all of its devices.  Evidently,
				 *+ it hadn't.  There is an internal problem
				 *+ in the SSM SCSI initialization code.
				 */
			}
		ssm_scsi_bins_built++;
	}

	/*
	 * For live devices on this SSM SCSI
	 * bus locate, its redirection table 
	 * bin and vector entries.  Then fill 
	 * it in with 'dev' addresses for the
	 * ISR to use and mark the device as
	 * interruptable and booted.
	 */
	for (ndevs = bus->devcount, dev = (scsidev_t *)bus->devices; 
	    ndevs--; dev = (scsidev_t *)dev->gen.next) {
		adapt = (struct ssm_scsi_adapt *) dev->adapt;
		/*
		 * Filter out devices not alive and/or
		 * not having interrupts mapped by new 
		 * ssm_scsi initialization code.  
		 */
		if (adapt->bin != 0) {
			intr = ssm_scsi_intr + adapt->bin;
#ifdef DEBUG
			ASSERT(adapt->bin < SLICBINS,
				"ssm_ssm_scsi_alloc_maps: bin is invalid");
			ASSERT(intr->in_use && 
				intr->basevec <= adapt->basevec &&
		       		adapt->basevec + NCBPERSCSI - 1 <= intr->topvec,
				"ssm_ssm_scsi_alloc_maps: vectors are invalid");
#endif /* DEBUG */
			for(vdev = intr->dev + (adapt->basevec - intr->basevec),
		     	    nvecs = NCBPERSCSI; nvecs--; vdev++)
				*vdev = dev;
			adapt->booted++;
		}
	}
}

/*
 * ssm_scsi_polled_cmd()
 *	Probe/boot time execution of the SCSI 
 *	command described by 'scac' for 'dev' 
 *	on the SSM SCSI interface.  Referenced 
 *	indirectly via '(*dev->polled_cmd)()',
 *	set up by adapter specific code.
 *	
 *	Assume locking is not required.  Start
 *	the requested command, poll for completion, 
 *	then returns status about its termination. 
 *	Sense data is retreived automatically by 
 *	SSM firmware if a SCSI check condition 
 *	occurs. The return value is one of SSTAT_OK, 
 *	SSTAT_NODEV, SSTAT_BERR, SSTAT_NOTARGET, 
 *	SSTAT_BUSYTARGET, SSTAT_BUSYLUN, SSTAT_CCHECK, 
 *	SSTAT_DCHECK, or SSTAT_UCHECK (see scsi.h).
 */
int
ssm_scsi_polled_cmd(dev, scac)
	register scsidev_t *dev;
	register scac_t *scac;
{
	register struct scsi_cb *cb;
	struct scb_init sinit;

	if (dev->ident == SCA_PROBE_IDENT) {
		/*
		 * Notify the SSM firmware of device
		 * needed characteristics, allocate 
		 * CBs, and save its SSM SCSI i.d.
		 */
		bzero((caddr_t) &sinit, sizeof(struct scb_init));
		sinit.si_mode = SCB_PROBE;
		sinit.si_flags = 0;
		sinit.si_ssm_slic = dev->gen.bus->owner->config->dev_addr;
		sinit.si_scsi = 0;
		sinit.si_target = dev->gen.config->space;
		sinit.si_lunit = dev->gen.config->dev_addr;
		sinit.si_control = 0;
		init_ssm_scsi_dev(&sinit);
		((struct ssm_scsi_adapt *)dev->adapt)->cb = sinit.si_cbs;
		dev->ident = sinit.si_id;
	}
	
	/*
	 * Use this device's first CB, since
	 * operations are automic.  Construct 
	 * the SCSI request for the SSM, based 
	 * on 'scac' passed in.
	 */
	cb = ((struct ssm_scsi_adapt *)dev->adapt)->cb;
	bzero(SWBZERO(cb), SWBZERO_SIZE);
	cb->sh.cb_sense = scac->sense;
	cb->sh.cb_slen = scac->slen;
	cb->sh.cb_cmd = (scac->scmd.dir == SDIR_HTOD) ? SCB_WRITE : SCB_READ;
	bcopy((caddr_t)scac->scmd.cmd, (caddr_t)cb->sh.cb_scmd, 
		cb->sh.cb_clen = scac->scmd.clen);
	if (scac->scmd.dir != SDIR_NONE) {
		cb->sh.cb_addr = scac->data;
		cb->sh.cb_iovec = (ulong *)scac->iov;
		cb->sh.cb_count = scac->dlen;
	}
	cb->sh.cb_compcode = SCB_BUSY;

	/*
	 * Start the SSM processing the CB,
	 * polling for completion.
	 */
	mIntr((unchar)dev->gen.bus->owner->config->dev_addr, SCSI_BIN, 
		(unchar)SCVEC(dev->ident, 0));
	while (cb->sh.cb_compcode == SCB_BUSY)
		;

	/*
	 * Translate CB and SCSI termination
	 * status into a generic SCSI command 
	 * termination code, then return it.
	 */
	return (ssm_scsi_status(cb));
}

/*
 * ssm_scsi_start_cmd()
 *	Initiate execution of a SCSI command 
 *	described by 'scac' for the device 
 *	'dev' on the SSM SCSI interface.
 *	Referenced via '(*dev->start_cmd)()',
 *	set up by adapter specific code.
 *
 *	Returns NULL when the request is
 *	rejected because the device is not 
 *	fully initialized for interrupts or 
 *	the simultaneous command limit has 
 *	been exceeded (the caller must allow 
 *	an earlier request to terminate).  
 *	Otherwise, returns a magic 'cookie', 
 *	identifying this particular request 
 *	to the caller upon termination.
 *	
 *	When successful (return other than 
 *	NULL), the SCSI request is executed.  
 *	Upon termination, 'dev's ISR is 
 *	invoked as follows:  
 *		(*dev->gen.driver->intr)(dev, cookie, status);
 *	where the arguments are:
 *		 'dev' 	   'dev' value provided originally by the driver,
 *		 'cookie'   i.d. previously returned to the driver,
 *	 	 'status'   one of SSTAT_OK, SSTAT_NODEV, SSTAT_BERR, 
 *			    SSTAT_NOTARGET, SSTAT_BUSYTARGET, SSTAT_BUSYLUN, 
 *			    SSTAT_CCHECK, SSTAT_DCHECK, or SSTAT_UCHECK, 
 *			    as described in scsi.h for command termination.
 */
ulong
ssm_scsi_start_cmd(dev, scac)
	scsidev_t *dev;
	register scac_t *scac;
{
	register struct scsi_cb *cb;
	register struct ssm_scsi_adapt *adapt = 
		(struct ssm_scsi_adapt *) dev->adapt;
	spl_t spl = p_lock(&adapt->lock, SPL5);
	int index;

	if (!adapt->booted || !adapt->avail) {
		v_lock(&adapt->lock, spl);
		return (NULL);		/* Reject request */
	} else {
		/* Fetch a CB to execute the request */
		adapt->avail--;
		cb = adapt->cb + (index = adapt->next++);
		if (adapt->next >= NCBPERSCSI)
			adapt->next = 0; /* Wrap around CB list */
	}

	/*
	 * Construct the SCSI request for 
	 * the SSM, based on 'scac' passed 
	 * in.  Use the CB just located.
	 */
	bzero(SWBZERO(cb), SWBZERO_SIZE);
	cb->sh.cb_sense = scac->sense;
	cb->sh.cb_slen = scac->slen;
	cb->sh.cb_cmd = (scac->scmd.dir == SDIR_HTOD) ? 
			 SCB_WRITE | SCB_IENABLE : SCB_READ | SCB_IENABLE;
	bcopy((caddr_t)scac->scmd.cmd, (caddr_t)cb->sh.cb_scmd, 
		cb->sh.cb_clen = scac->scmd.clen);
	if (scac->scmd.dir != SDIR_NONE) {
		cb->sh.cb_addr = scac->data;
		cb->sh.cb_iovec = (ulong *)scac->iov;
		cb->sh.cb_count = scac->dlen;
	}
	cb->sh.cb_compcode = SCB_BUSY;

	/*
	 * Initiate SSM execution of the CB.
	 * Then return CB address as its magic 
	 * cookie to identify its termination.
	 */
	spl = splhi();
	mIntr((unchar)dev->gen.bus->owner->config->dev_addr, SCSI_BIN, 
		(unchar)SCVEC(dev->ident, index));
	v_lock(&adapt->lock, spl);
	return ((ulong)cb);
}

/*
 * ssm_ssm_scsi_dma_setup()
 *	Map a dma transfer for the SSM SCSI.
 *
 *	Its functionality is different than
 *	specified in the autoconfiguration 
 *	document; it was not generic enough 
 *	to map both buf-structures and regular
 *	buffers.
 *	
 *	'dev' contains iovec resources info. 
 *	Also passed in are:
 *		'pte'   address of the page table entry 
 *			pertaining to memory cluster base 
 *			where the transfer begins.
 *		'offset'  tranfer start location relative
 *			  to its memory cluster base.
 *		'length'  number of bytes to transfer.
 *	
 *	'id' is provided by the device driver
 *	to identify maps used for this transfer.
 *	The driver must make a corresponding
 *	call to (*dev->gen.bus->dma_done)(), 
 *	provide this same value to release these 
 *	maps.
 *	
 *	Returns the number of bytes actually 
 *	mapped, or -1 if there is an error 
 *	aside from being out of maps.
 */
ssm_ssm_scsi_dma_setup(dev, pte, offset, length, id)
	scsidev_t *dev;
	struct pte *pte;
	off_t offset;
	long length;
	ulong id;
{
	register int count = 0, tail = 1;
	int head = 0, nmaps = (offset + length + CLOFSET) / CLBYTES;
	int start_limit = dev->gen.n_maps - nmaps;
	struct ssm_scsi_adapt *adapt = 
		(struct ssm_scsi_adapt *) dev->adapt;
	struct ssm_scsi_dma *dma = 
		(struct ssm_scsi_dma *) dev->gen.map_origin;
	register ulong *map_id = dma->id;
	spl_t spl = p_lock(&adapt->lock, SPL5);
	register ulong *map;
	
	if (start_limit < 0) {
		v_lock(&adapt->lock, spl);
		return (-1);		/* Requested size exceeds total maps */
	}

	/* 
	 * Scan the dma->id list looking for
	 * 'nmaps' sequential NULL entries.  
	 */
	for (; count < nmaps && head <= start_limit; tail++) 
		if (*map_id++) {
			/* Map in use.  Reset count and try next map */
			head = tail;
			count = 0;
		} else 
			/* Available map found.  Continue... */
			count++;
	if (count < nmaps) {
		v_lock(&adapt->lock, spl);
		return (0);		/* Too few available sequential maps */
	}

	/*
	 * Enough maps have been found.
	 * Compute the address that must
	 * be specified to the SSM for 
	 * them to be used.
	 */
	dev->gen.bus_map_addr = (caddr_t) (map =  dma->map + head);

	/* 
	 * Generate the scatter-gather table 
	 * containing the appropriate indirect 
	 * addresses.  i.d. table entries
 	 * corresponding to these map table 
	 * entries are set to 'id', indicating
	 * they are in use.  They can only be 
	 * released with this 'id'.
	 */
	for (map_id = dma->id + head; count--; pte += CLSIZE) {
		*map++ = PTETOPHYS(*pte);
		*map_id++ = id;
	}

	v_lock(&adapt->lock, spl);
	return (length);		/* Successful mapping */
}

/*
 * ssm_ssm_scsi_dma_done()
 *	Releases dma map entries for 'dev' 
 *	previously allocated with the same
 *	'id' value.  This makes them again 
 *	available to 'dev'.
 */	
void
ssm_ssm_scsi_dma_done(dev, id)
	scsidev_t *dev;
	register ulong id;
{
	register ulong *map_id = 
		((struct ssm_scsi_dma *)dev->gen.map_origin)->id;
	struct ssm_scsi_adapt *adapt = 
		(struct ssm_scsi_adapt *) dev->adapt;
	spl_t spl = p_lock(&adapt->lock, SPL5);
	register int i;

	for (i = dev->gen.n_maps; i--; map_id++)
		if (*map_id == id)
			*map_id = 0;
	
	v_lock(&adapt->lock, spl);
}

/*
 * init_ssm_scsi_dev() 
 * 	Called while probing or booting a SCSI 
 *	device; provides initialization information 
 *	about it to the SSM board.  Returns in
 *	the sinfo structure an SSM specific device 
 *	id and location of CBs for this device.  A
 *	negative device id indicates that an error 
 *	occurred.  NOT to be called after booting.
 *
 *	Provides information to the SSM by filling 
 *	out an initialization CB and passing its 
 *	address to the SSM via a sequence of 5 SLIC 
 *	messages.  The address is also the location 
 *	of the SCSI CBs to be used thereafter.
 *
 *	If the function argument's si_mode field is 
 *	SCB_PROBE a static set of CBs is used to 
 *	probe the device and then recycled.  Likewise,
 *	a special SLIC message tells the SSM it may
 *	recycle its device i.d.  Otherwise, a new 
 *	device i.d. and set of CBs is allocated.
 */
void
init_ssm_scsi_dev(sinfo)
	register struct scb_init *sinfo;
{
	register struct scsi_init_cb *icb;
	uint nbytes = sizeof(struct scsi_cb) * NCBPERSCSI;

	if (sinfo->si_mode == SCB_PROBE) {
		if (!probe_cbs) 
			probe_cbs = (struct scsi_cb *) ssm_scsi_alloc(nbytes);
		icb = (struct scsi_init_cb *)probe_cbs;
	} else {
		ASSERT(sinfo->si_mode == SCB_BOOT, 
		       "ssm_scsi: invalid init mode.\n");
		/*
		 *+ init_ssm_scsi_dev() was called after device probing
		 *+ was complete.  A device might have been
		  *+ corrupted.
		 */

		icb = (struct scsi_init_cb *) ssm_scsi_alloc(nbytes);
	}

	/*
	 * Fill in the initialization CB.
	 */
	bzero((char *)icb, SCB_SHSIZ);
	icb->icb_pagesize = CLBYTES;
	icb->icb_flags = sinfo->si_flags;
	icb->icb_sdest = SL_GROUP | TMPOS_GROUP;
	icb->icb_svec = sinfo->si_host_basevec;
	icb->icb_scmd = SL_MINTR | sinfo->si_host_bin;
	icb->icb_scsi = sinfo->si_scsi;
	icb->icb_target = sinfo->si_target;
	icb->icb_lunit = sinfo->si_lunit;
	icb->icb_control = sinfo->si_control;
	icb->icb_cmd = SCB_INITDEV;
	icb->icb_compcode = SCB_BUSY;

#ifdef	SSMDEBUG
	printf("init_ssm_scsi_dev: scsi_init_cb\n");
	ssm_dump(icb, sizeof(struct scsi_init_cb));
#endif	/* SSMDEBUG */

	/* 
	 * Use a special sequence of SLIC messages to
	 * to notify the SSM to process an initialization 
	 * CB located at the CB table address (address 
	 * sent low byte first, high byte last).
	 */
	mIntr(sinfo->si_ssm_slic, SCSI_BIN, (unsigned char)
		((sinfo->si_mode == SCB_PROBE)? SCB_PROBEVEC : SCB_BOOTVEC));
	mIntr(sinfo->si_ssm_slic, SCSI_BIN, (unchar)((ulong)icb)); /* low byte first */
	mIntr(sinfo->si_ssm_slic, SCSI_BIN, (unchar)((ulong)icb >> 8));
	mIntr(sinfo->si_ssm_slic, SCSI_BIN, (unchar)((ulong)icb >> 16));
	mIntr(sinfo->si_ssm_slic, SCSI_BIN, (unchar)((ulong)icb >> 24)); /* high byte last */
	
	while (icb->icb_compcode == SCB_BUSY)	
		;			/* Poll for command completion */

	sinfo->si_cbs = (struct scsi_cb *)icb;	/* CB address to return */
	sinfo->si_id = icb->icb_id;	/* SSM device i.d. to return */
}

/*
 * ssm_scsi_resource_init()
 *	Performs initial allocation of resources
 *	for communicating with this device on 
 *	the SSM SCSI interface.  It will allocate
 * 	an adapter specific structure, dma maps,
 *	and initialize indirect functions stored
 *	in the device' dev structure.
 *
 *	Only some of the fields of the adapter
 *	structure can be initialized at this
 *	point. Since the device' interrupt bin
 *	is not yet known, init_ssm_scsi_dev()
 *	cannot be invoked to allocate permanent
 *	CBs and perform interrupt mapping.
 */
static void
ssm_scsi_resource_init(dev)
	register scsidev_t *dev;
{
	register struct ssm_scsi_adapt *adapt;
	register struct ssm_scsi_dma *dma;
	int nmaps;
	
	/* 
	 * Determine from configuration information
	 * the number of dma maps required by this
	 * device, if any.  Attempt to allocate the
	 * a list of that many maps and resources
	 * to manage them with.  If successful,
	 * note dma information in 'dev'.
	 *
	 * Note that the dma maps must meet special
	 * alignment and boundary restrictions for
	 * the SSM to access them.  The other 
	 * resources don't.
	 */
	if (nmaps = ((dev->gen.segsize + CLOFSET) / CLBYTES + 
		(dev->gen.segalign % CLBYTES ? 1 : 0)) * dev->gen.nsegs) {
		dma = (struct ssm_scsi_dma *) 
			calloc(sizeof(struct ssm_scsi_dma));
		ASSERT(dma, "ssm_scsi: out of memory for dma structures");
		/*
		 *+ There does not appear to be enough physical
		 *+ memory available to allocate structures for
		 *+ SSM SCSI devices.  
		 *+ Another portion of system initialization
		 *+ might have allocated excessive amounts of memory.
		 */
		dma->id = (ulong *)calloc(nmaps * sizeof (ulong));
		dma->map = (ulong *)ssm_scsi_alloc(nmaps * sizeof (ulong));
		ASSERT(dma->id && dma->map, 
			"ssm_scsi: out of memory for dma map");
		/*
		 *+ There does not appear to be enough physical
		 *+ memory available to allocate structures for
		 *+ SSM SCSI devices.  
		 *+ Another portion of system initialization
		 *+ might have allocated excessive amounts of memory.
		 */

		if (dma && dma->id && dma->map) {
			dev->gen.n_maps = nmaps;
			dev->gen.map_origin = (caddr_t)dma;
			dev->gen.map_extent = CLBYTES;
		}
	}

	/*
	 * Initialize SSM SCSI portions of 
	 * 'dev' so an SCSI operation can 
	 * transparently allocate temporary 
	 * CBs and SSM specific device i.d. 
	 * until initialization is complete
	 * and permanent resources assigned.
	 *
	 * The value put into dev->ident tells
	 * ssm_scsi_polled_cmd() to provide 
	 * temporary resources and a 
	 * dev->adapt->booted value of zero 
	 * tells ssm_scsi_start_command() to 
	 * reject requests until initialization
	 * is complete.
	 */ 
	dev->ident = SCA_PROBE_IDENT;
	adapt = (struct ssm_scsi_adapt *) 
		(dev->adapt = calloc(sizeof(struct ssm_scsi_adapt)));
	ASSERT(adapt, "ssm_scsi_resource_init: couldn't allocate of memory");
	/*
	 *+ The SSM initialization code could not
	 *+ allocate the memory needed for one of its
	 *+ device management structures.  Either
	 *+ another driver has exhausted memory
	 *+ erroneously, or your machine needs more
	 *+ physical memory.
	 */
	init_lock(&adapt->lock);
	adapt->avail = NCBPERSCSI;
	adapt->next = 0;

	/*
	 * Initialize indirect references to
	 * SSM SCSI specific functions located
	 * in 'dev' since struct bus did not
	 * have placeholders for them.
	 */
	dev->polled_cmd = ssm_scsi_polled_cmd;
	dev->start_cmd = ssm_scsi_start_cmd;
	dev->alloc = ssm_scsi_alloc;
}

/*
 * ssm_scsi_alloc()
 *	Allocate nbytes of memory that meets
 *	the alignment and boundary criteria
 *	for accessability by the SSM SCSI adapter.
 *	Will be referenced by scsidev_t.alloc.
 */
static caddr_t
ssm_scsi_alloc(nbytes)
	unsigned nbytes;
{
	return(ssm_alloc(nbytes, SSM_ALIGN_XFER, SSM_BAD_BOUND));
}

/*
 * The following 7 functions are entry points
 * for SSM SCSI device interrupts.  Their sole
 * purpose is to invoke the adapter specific
 * interrupt service routine, ssm_scsi_handler(), 
 * passing along their associated interrupt bin 
 * number and the vector passed to them.
 * 
 * This is necessary since the SSM SCSI code
 * must intercept device interrupts to manage
 * resources and provide the information defined
 * for its adapter independent 'black box' interfaces.
 * Since it manages resources for devices with 
 * potentially different host interrupt bins, it
 * must use have a means of determining which bin
 * its interrupts came in on to locate the device
 * for which it is intended.
 */

static void 
ssm_scsi_bin1_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(1, vec);
}

static void 
ssm_scsi_bin2_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(2, vec);
}

static void 
ssm_scsi_bin3_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(3, vec);
}

static void 
ssm_scsi_bin4_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(4, vec);
}

static void 
ssm_scsi_bin5_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(5, vec);
}

static void 
ssm_scsi_bin6_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(6, vec);
}

static void 
ssm_scsi_bin7_isr(vec)
	unchar vec;
{
	ssm_scsi_handler(7, vec);
}

static void 
ssm_scsi_handler(bin, vec)
	unchar bin;
	unchar vec;
{
#ifdef DEBUG
	struct ssm_scsi_intr *ip = ssm_scsi_intr + bin;
	register scsidev_t *dev;
	register struct ssm_scsi_adapt *adapt;
	int index;
	register struct scsi_cb *cb;
	spl_t spl;
	int status;

	ASSERT(ip->basevec <= vec && vec <= ip->topvec,
		"ssm_scsi_handler:  Invalid interrupt vector received");

	dev = ip->dev[vec - ip->basevec];
	ASSERT(dev, "ssm_scsi_handler:  NULL device pointer");

	adapt = (struct ssm_scsi_adapt *) dev->adapt;
	ASSERT(adapt, "ssm_scsi_handler:  NULL ssm_scsi_adapt pointer");

	index = vec - adapt->basevec;
	cb = adapt->cb + index;
	ASSERT(adapt->basevec <= vec && index < NCBPERSCSI, 
		"ssm_scsi_handler:  Unmapped interrupt vector received");

	spl = p_lock(&adapt->lock, SPL5);
#else /* DEBUG */
	struct ssm_scsi_intr *ip = ssm_scsi_intr + bin;
	register scsidev_t *dev = ip->dev[vec - ip->basevec];
	register struct ssm_scsi_adapt *adapt = 
		(struct ssm_scsi_adapt *) dev->adapt;
	int index = vec - adapt->basevec;
	register struct scsi_cb *cb = adapt->cb + index;
	spl_t spl = p_lock(&adapt->lock, SPL5);
	int status;
#endif /* DEBUG */

	/*
	 * Convert the CB and, if necessary, SCSI 
	 * termination status into a generic SCSI
	 * command termination code.
	 */
	status = ssm_scsi_status(cb);

	/*
	 * Make the CB available for another
	 * SCSI command request.
	 */
	adapt->avail++;
	v_lock(&adapt->lock, spl);

	/*
	 * Invoke the device driver's interrupt 
	 * service routine with the generic
	 * SCSI interrupt information.
	 */
	(*dev->gen.driver->intr)(dev, cb, status);
}

/*
 * ssm_scsi_status()
 * 	Translate the completion code and SCSI
 * 	termination status from an executed
 *	SSM SCSI command block into one of the
 *	generic SCSI command termination codes
 *	defined in 'sys/scsi.h'.  
 * 
 *	If the CB did not terminate with SCB_OK, 
 *	this function determine's the return
 *	code.  Otherwise, invoke a generic SCSI 
 *	function to translate based upon the
 *	SCSI command termination byte and/or 
 *	available sense data.
 */
static int
ssm_scsi_status(cb) 
	struct scsi_cb *cb;
{
	switch(cb->sh.cb_compcode) {
	case SCB_OK:
		return(scsi_status(cb->sh.cb_status, 
			(struct scrsense *)cb->sh.cb_sense));
	case SCB_NO_TARGET:
		return (SSTAT_NOTARGET);
	case SCB_SCSI_ERR:
		return (SSTAT_BUSERR);
	case SCB_BUSY:
		return (SSTAT_BUSYTARGET);
	case SCB_BAD_CB:
		cmn_err(CE_PANIC, "ssm_scsi_status: Invalid CB reported");
		/*
		 *+ The SSM firmware rejected a SCSI command
		 *+ block as being invalid and nonexecutable.
		 *+ The possible cause is defective SSM firmware
		 *+ or memory corruption from an unknown source.
		 */
		/*NOTREACHED*/
	}
		/*NOTREACHED*/
}

/*
 * scb_buf_iovec()
 *	Fill out an indirect address table for a
 *	SCSI I/O request, given a buf structure
 *	and the address of the indirect address
 *	table.  This function should be replaced 
 *	by calls to bp_iovec_info(), ssm_scsi_dma_map(), 
 *	and ssm_scsi_dma_map() once the SSM scsi 
 *	driver reorganization is complete.
 *
 * 	Returns physical address of transfer start.
 */
ulong
scb_buf_iovects(bp, iovp)
	struct buf *bp;
	register ulong *iovp;
{
	register int count;
	struct pte *pte;
	int length;
	ulong	retval;
	off_t offset;

	bp_iovec_info(bp, &pte, &offset, &length);
	retval = PTETOPHYS(*pte) + offset;
	count = (offset + length + CLOFSET) / CLBYTES;

	for (; count--; pte += CLSIZE)
		*iovp++ = PTETOPHYS(*pte);
	return (retval);
}

#ifdef notyet		/* unused - get rid of for lint */
/*
 * scb_num_iovects()
 *	Takes a "bp" (struct buf *) and returns	the worst
 *	case number of iovects needed for this transfer.
 *
 * 	Does *no* locking.
 */
scb_num_iovects(bp)
	register struct	buf *bp;	/* buffer header */
{
	return (howmany(bp->b_bcount, CLBYTES) + 1);
}
#endif /* notyet */

#ifdef	SSMDEBUG
ssm_dump(ptr, len)
	int len;
	char * ptr;
{
	int	x;
	
	for (x = 0; x < len; x++) {
		printf("%x ", (unchar) ptr[x]);
		if ((x % 30 == 0) && (x != 0))
			printf("\n");
	}
	printf("\n");
}
#endif	/* SSMDEBUG */
