/* $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: zdinit.c 1.38 90/10/22 $"

/*
 * zdinit.c
 *
 * Initialization portion ZDC SMD Disk Driver.
 *
 * Auto-configure ZDC controllers and drives. Initialize ZDC controllers.
 * Uses polled interface to communicate with ZDC.
 *
 * NOTE:
 * This driver, although apparently separate, is heavily dependent on the zd
 * driver. This is so much the case that there are functions in zd.c that
 * it could be argued belong here and vice versa.
 */

/* $Log:	zdinit.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/sysmacros.h"
#include "sys/vtoc.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/buf.h"
#include "sys/systm.h"
#include "sys/stats_disk.h"

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

#include "sys/intctl.h"

#include "sys/cmn_err.h"

#include "sys/zdc.h"
#include "sys/zdc_ioconf.h"
#include "sys/zdc_device.h"

#include "sys/autoconf.h"
#include "sys/zd_device.h"

#define	PLURAL(x) ((x) == 1 ? "" : "s")
#define	CHAN_A	0			/* Channel A */
#define	CHAN_B	1			/* Channel B */
#define	ERRLIGHTON { if (fp_lights) FP_IO_ERROR; }

extern	int	zdc_iovpercb;		/* no of iovecs per cb */
extern	int	zdc_err_bin;		/* bin for error interrupts */
extern	int	zdc_cb_bin;		/* bin for cb interrupts */
extern	unsigned	zdccmdtime;	/* polled command timeout value */
extern	unsigned	zdcinitime;	/* init ctrlr timeout value */
extern	int	zdc_C_throttle;		/* Throttle count for DMA channel C */
extern	short	zdcretry;		/* retry count on errors */
extern	unchar	zdctrl;			/* additional icb_ctrl bits */
extern	caddr_t	zd_compcodes[];		/* completion codes in english */
extern	int	zdncompcodes;		/* no. of entries in zd_compcodes */
extern	char	*zdpatrolbuf;		/* buffer for patrol reads */
void	zdc_error();
void	zd_intr();
extern	int	zd_b_major;

extern struct device_config zd_smde_config[];
extern struct device_config sb8000_config[];
extern struct zdc_device zdc_devices[];
extern struct zd_device zd_devices[];
extern struct devops zddevops;

int zd_max_unit = -1;

struct	zdc_ctlr *zdctrlr;		/* zdctrlr array - calloc'd */
struct	zd_unit	 *zdunit;		/* zdunit array - calloc'd */
unchar	base_cb_intr;			/* base interrupt for zdc driver */
unchar	base_err_intr;			/* base controller interrupt */
lock_t	zdcprlock;			/* lock for error message sync */

static	struct	cb	*basecb;	/* base cb */
static	struct	zdcdd	*chancfg;	/* ZDC_GET_CHANCFG */
static int nzdcs;			/* Number of ZDC controller in config */

zdc_probe(device)
	struct gen_device *device;
{
	register struct zdc_device *dev = (struct zdc_device *)device;
	struct ctlr_toc *toc = &CD_LOC->c_toc[SLB_ZDCBOARD];
	register struct	ctlr_desc *cd = &CD_LOC->c_ctlrs[toc->ct_start];
	int i;

	for (i = 0; i < toc->ct_count; i++, cd++) {
		if (dev->config->dev_addr == cd->cd_slic) {
			dev->cd = cd;	/* This is the matching ZDC */
			if (FAILED(cd)) 
				return 0;	/* Short circuit search */
			return 1;	/* Found */
		}
	}
	return 0;			/* Not found */
}

/*
 * zdc_init()
 *
 * Find all ZDCs and fill-in slic addresses in controller structure.
 */
void zdc_init(driver)
	struct driver_module *driver;
{
	struct ctlr_toc *toc = &CD_LOC->c_toc[SLB_ZDCBOARD];
	register struct	ctlr_desc *zdcfg;
	register struct zdc_ctlr *ctlrp;
	register struct zdc_device *dev = (struct zdc_device *)driver->devices;
	struct cb lcb, *cbp;
	int alivezdc = 0, i, j, val;
	extern long max_RAW_IO;

	if ((nzdcs = driver->numdev)== 0)
		return;

	callocrnd(sizeof(struct zdc_ctlr));
	zdctrlr = (struct zdc_ctlr *)calloc(nzdcs * sizeof(struct zdc_ctlr));

	for (ctlrp = zdctrlr; ctlrp < &zdctrlr[nzdcs]; ctlrp++, dev++) {
		if (zdcfg = dev->cd) {
			ctlrp->zdc_slicaddr = zdcfg->cd_slic;
			ctlrp->zdc_diagflag = zdcfg->cd_diag_flag;
			if (ctlrp->zdc_diagflag & (CFG_FAIL|CFG_DECONF)) {
				ctlrp->zdc_state = ZDC_DEAD;
				if (ctlrp->zdc_diagflag & CFG_FAIL) {
					ERRLIGHTON;
				}
			} else {
				ctlrp->zdc_state = ZDC_ALIVE;
				alivezdc++;
			}
		}
	}

	if (alivezdc < toc->ct_count) {
		cmn_err(CE_WARN, "Not using ZDC: slic");
		/*
		 *+ The DCCs listed did not pass power-up
		 *+ diagnostics or have been deconfigured.
		 *+ Corrective action:  call service.
		 */
		zdcfg = &CD_LOC->c_ctlrs[toc->ct_start];
		for (i = 0; i < toc->ct_count; i++, zdcfg++) {
			for (j = 0, dev = (struct zdc_device *)driver->devices;
			     j < nzdcs; j++, dev++)
				if (dev->cd == zdcfg) break; /* Match found */

			if (j == nzdcs || zdctrlr[j].zdc_state == ZDC_DEAD) 
				cmn_err(CE_CONT, " %d", zdcfg->cd_slic);
		}
		cmn_err(CE_CONT, ".\n");
	}

	/*
	 * Check to see if controller FW is initialized.
	 * Wait until ZDC is initialized. If error, mark ZDC as ZDC_DEAD.
	 */
	for (ctlrp = zdctrlr; ctlrp < &zdctrlr[nzdcs]; ctlrp++) {
		if (ctlrp->zdc_state != ZDC_ALIVE)
			continue;
		i = calc_delay(zdcinitime);
		for (;;) {
			val = rdslave(ctlrp->zdc_slicaddr, SL_Z_STATUS);
			if ((val & SLB_ZPARERR) != SLB_ZPARERR) {
				if ((val & ZDC_READY) == ZDC_READY)
					break;
				if ((val & ZDC_ERRMASK) == ZDC_SINIT)  {
					/* Still initializing */
					if (--i)
						continue;
				}
			}
			/*
			 * Error found
			 */
			cmn_err(CE_WARN,
				"zdc%d: controller failure - status = 0x%x.",
				ctlrp - zdctrlr, val);
			/*
			 *+ The specified DCC controller could not be
			 *+ initialized.  Corrective action:  call service.
			 */
			cmn_err(CE_CONT, "zdc%d at slic %d Deconfigured.\n",
				ctlrp - zdctrlr, ctlrp->zdc_slicaddr);
			ctlrp->zdc_state = ZDC_DEAD;
			ERRLIGHTON;
			break;
		}
	}

	/*
	 * Set up interrupt vectors
	 */
	base_err_intr = ivecpeek(zdc_err_bin);
	for (i = 0; i < nzdcs; i++)
		ivecinit(zdc_err_bin, ivecall((unchar)zdc_err_bin), zdc_error);

	base_cb_intr = ivecpeek(zdc_cb_bin);
	for (i = 0; i < NCBPERZDC * nzdcs; i++)
		ivecinit(zdc_cb_bin, ivecall((unchar)zdc_cb_bin), zd_intr);

	/*
	 * Allocate CBs
	 */
	callocrnd(sizeof(struct cb));
	basecb = (struct cb *)calloc(NCBPERZDC * sizeof(struct cb) * nzdcs);
	for (cbp = basecb; cbp < &basecb[NCBPERZDC * nzdcs]; cbp++) {
		cbp->cb_unit = -1;
	}

	/*
	 * If zdc_iovpercb not set or too big for max_RAW_IO then
	 * set for max_RAW_IO.
	 */
	if (zdc_iovpercb <= 0 || ((zdc_iovpercb * CLBYTES) > max_RAW_IO))
		zdc_iovpercb = (max_RAW_IO + CLOFSET) / CLBYTES;

	/*
	 * Adjust zdc_iovpercb size to safe size.
	 */
	zdc_iovpercb = roundup(zdc_iovpercb, IOVALIGN / sizeof(ulong *));

	/*
	 * init_lock to prevent error message garble.
	 */
	init_lock(&zdcprlock);

	/*
	 * Tell each ZDC where its CB array is located.
	 * Done via Bin1 - Bin4 SLIC interrupts, where Bin 1 is the
	 * least significant byte of the CB address and Bin 4 is
	 * the most significant byte.
	 */
	cbp = basecb;
	ctlrp = zdctrlr;
	for (; ctlrp < &zdctrlr[nzdcs]; ctlrp++, cbp += NCBPERZDC) {
		ctlrp->zdc_cbp = cbp;
		if (ctlrp->zdc_state == ZDC_ALIVE) {
			for (i = 0; i < NBPW; i++)
				mIntr(ctlrp->zdc_slicaddr, (unchar)i + 1,
					(unchar)((int)cbp >> (i * NBBY)));
			init_lock(&ctlrp->zdc_ctlrlock);
		}
	}

	/*
	 * Probe devices on ZDC via ZDC_PROBE and ZDC_GET_CHANCFG.
	 * Store results in controller structure.
	 */
	callocrnd(sizeof(struct zdcdd));
	chancfg = (struct zdcdd *)calloc(sizeof(struct zdcdd));
	cbp = &lcb;
	for (ctlrp = zdctrlr; ctlrp < &zdctrlr[nzdcs]; ctlrp++) {
		if (ctlrp->zdc_state != ZDC_ALIVE)
			continue;
		cbp->cb_cmd = ZDC_PROBEDRIVE;
		if (pollzdcmd(ctlrp, cbp, CHAN_A) < 0) {
			cmn_err(CE_WARN, "zdc%d: Cannot probe zdc.",
				ctlrp - zdctrlr);
			/*
			 *+ The specified DCC controller did not respond
			 *+ to the probe command.  Corrective action:
			 *+ call service.
			 */
			cmn_err(CE_CONT, "zdc%d at slic %d Deconfigured.\n",
				ctlrp - zdctrlr, ctlrp->zdc_slicaddr);
			ctlrp->zdc_state = ZDC_DEAD;
			ERRLIGHTON;
			continue;
		}
		for (i = 0; i < ZDC_MAXDRIVES; i++)
			ctlrp->zdc_drivecfg[i] =
				  ((struct probe_cb *)cbp)->pcb_drivecfg[i];

		if (get_chancfg(ctlrp, CHAN_A) < 0)
			continue;
		if (get_chancfg(ctlrp, CHAN_B) < 0)
			continue;
	}

	callocrnd(sizeof(struct zd_unit));
	/*
	 * We need to find the "zd" driver entry to see how many drives are
	 * configured.  Must be last in function, as we are reusing "driver".
	 */
	for (driver=drivers; driver->numdev; driver++)
		if (strcmp(driver->name, "zd") == 0)
			break;
	if (driver->numdev == 0) {
		cmn_err(CE_WARN, "zdc_init: no zd drives configured.");
		/*
		 *+ When you configure one or more DCCs, you configure
		 *+ one or more zd drives.  
		 *+ Review the system configuration.
		 */
	}
	zdunit = (struct zd_unit *)calloc(driver->numdev
					  * sizeof(struct zd_unit));
	zd_max_unit = driver->numdev - 1;
}


/*
 * Find and bind drives to units.
 */
zd_probe(device)
	struct gen_device *device;
{
	struct zd_device *dev = (struct zd_device *)device;
	register int	i;
	register struct	cb	 *cbp;
	register struct	zdc_ctlr *ctlrp;
	register struct	zdc_dev  *zdv = dev->zd_dev;
	register struct	zd_unit	 *up;
	register struct dev	 *dp;
	int	ctrlr, drive, drive_limit, type;
	struct	zdcdd	*dd;
	static	int	found[ZDC_MAXCTRLR]; /* bit map - found drives */
	extern	long	max_RAW_IO;	/* Max size RAW IO request (bytes) */
	static	probed_zdc_smde = 0;

	if (!probed_zdc_smde) {
		probed_zdc_smde++;
		/* Once only code */
		for (i = 0; i < ZDC_MAXCTRLR; i++)
			found[i] = 0;		/* no drives yet */
	}

	if (!dev->zd_dev) {
		/*
		 * We haven't allocated a block of zdc_dev structures for this
		 * driver yet. Do it now, and allocate them to each device.
		 */
		zdv = (struct zdc_dev *)
			calloc(dev->driver->numdev * sizeof (struct zdc_dev));
		if (!zdv) {
			cmn_err(CE_PANIC,
				"%s: No memory for %s driver zdc_dev array",
				"zd_probe", dev->driver->name);
			/*
			 *+ On the first probe to any zd 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
			 *+ huge chunks of kernel memory.
			 */
		}
		/* Now, parcel out the array */
		for (dev = (struct zd_device *)dev->driver->devices, i=0;
		     i < device->driver->numdev;
		     i++, zdv++, dev++)
			dev->zd_dev = zdv;
		/* restore dev to its former glory */
		dev = (struct zd_device *) device;
	}

	ctrlr = (struct zdc_device *)dev->bus->owner - zdc_devices;

	if (dev->unit < 0)
		drive = ANY;
	else
		drive = dev->config->dev_addr;

	if (dev->busnum < 0)
		dev->zd_dev->zdv_idx = ANY;
	else
		dev->zd_dev->zdv_idx = ctrlr ;

	dev->zd_dev->zdv_drive = drive ;
	zdv = dev->zd_dev;

	up = &zdunit[ dev - zd_devices];
	up->zu_state = ZU_NOTFOUND;
	ctlrp = &zdctrlr[ctrlr];
	if (ctlrp->zdc_state != ZDC_ALIVE) {
		if (dev->busnum >= 0) {
			/* bus not wild! */
			cmn_err(CE_WARN, "zd%d on dead controller zdc%d.",
				up - zdunit, ctrlr);
			/*
			 *+ The specified drive is on a controller that
			 *+ has either failed diagnostics or has been declared
			 *+ dead during initialization.  Corrective action:
			 *+ call service.
			 */
			up->zu_ctrlr = ctrlr;
			up->zu_state = ZU_BAD;
			disk_offline();
		}
		return (0);
	}

	up->zu_ctrlr = ctrlr;
	drive_limit = drive + 1;
	if (drive_limit > ZDC_MAXDRIVES)
		drive_limit = ZDC_MAXDRIVES;
	if (drive == ANY || dev->unit < 0) {
		/*  drive wildcard */
		drive = 0;
		drive_limit = ZDC_MAXDRIVES;
	}

	for (; drive < drive_limit; drive++) {
		if (found[ctrlr] & (1 << drive)) {
			/*
			 * Already bound to another unit
			 */
			continue;
		}
		if (ctlrp->zdc_drivecfg[drive] == ZD_NOTFOUND){
			/*
			 * If drive not found and wildcarding
			 * drive, then continue search on this
			 * ZDC. If drive not found and bound
			 * drive but wildcarding ZDC, continue
			 * search on another controller.
			 */
			if (zdv->zdv_drive == ANY)
				continue;
			if (zdv->zdv_idx == ANY)
				break;
			/* No - use this drive */
		}
		dd = (drive & 1) ? &ctlrp->zdc_chanB : &ctlrp->zdc_chanA;
		
		/*
		 * Drive Matched.
		 */
		found[ctrlr] |= 1 << drive;
		type = dd->zdd_drive_type;
		/* Now, find a valid config */
		if (dev->config->dev_len == 0) {
			for (dev->config = zd_smde_config;
			     dev->config->dev_len != -1;
			     dev->config++)
				if (dev->config->dev_addr == drive)
					break;
			if (dev->config->dev_len == -1) {
				cmn_err(CE_WARN, "zd_smde_config malformed.");
				/*
				 *+ The table zd_smde_config needs to have an
				 *+ entry for each possible drive number.
				 *+ This entry must also have dev_len > 0.  
				 *+ At least one such entry is missing.  
				 *+ Check zd_space.c for completeness.
				 */
				dev->config = &zd_smde_config[drive];
			}
		}
		CPRINTF("# %s configuration unit %d on %s is drive %d type %d",
			device->driver->name,
			(dev->config - dev->on_bus->config_array)
			/ dev->on_bus->nspaces, bus_type[dev->bus->bustype],
			drive, type);
#ifdef notyet
		if (0)		       /* ****** insert test for unformatted */
			CPRINTF(" unformatted.\n        ");
		else
#endif /* notyet */
			CPRINTF("\n        ");
		up->zu_drive = drive;
		if (dd->zdd_sectors == 0) {
			/*
			 * If no channel cfg, set state to
			 * ZU_NO_RW. Since zdsize() will be
			 * called before open, the size routine
			 * will not allow swap I/O to drives
			 * that mismatch the configuration.
			 */
			up->zu_state = ZU_NO_RW;
		} else {
			/*
			 *  OK match set drive type.
			 */
			up->zu_state = ZU_GOOD;
		}
		up->zu_cfg = ctlrp->zdc_drivecfg[drive];
		up->zu_zdbad = NULL;
		bufinit(&up->zu_ioctl);
		init_lock(&up->zu_lock);
		init_sema(&up->zu_ocsema, 1);
		/*
		 * Set up disk i/o statistics structure
		 */
		up->zu_dstats = ds_alloc_stats("zd", (ulong)dd->zdd_cyls,
			up - zdunit, DS_CYL_HIST);
		/*
		 * Set up software cb fields for this unit.
		 */
		cbp = &ctlrp->zdc_cbp[drive * NCBPERDRIVE];
		up->zu_cbptr = cbp;
		cbp->cb_unit = up - zdunit;
		callocrnd(IOVALIGN);
		cbp->cb_iovstart = (ulong *)calloc(zdc_iovpercb
						   * sizeof(ulong *));
		++cbp;
		cbp->cb_unit = up - zdunit;
		cbp->cb_iovstart = (ulong *)calloc(zdc_iovpercb
							* sizeof(ulong *));

		if (up->zu_cfg == ZD_NOTFOUND ||
		    (up->zu_cfg & ZD_ONLINE) != ZD_ONLINE) {
			/*
			 * Even though drive not present, the
			 * unit is bound to the drive. It
			 * could be that the drive is not
			 * spun up and will be later...
			 */
			cmn_err(CE_WARN, "zd%d: drive not present.",
				up - zdunit);
			/*
			 *+ The specified zd drive is not responding.  It
			 *+ might not be online or be powered up.
			 *+ Corrective action:  make sure the drive is powered
			 *+ up and online.  If the drive is online, powered up,
			 *+ and still does not respond, call service.
			 */
			disk_offline();
		}
		/*
		 * allocate space for the dev and initialize it.
		 */
		dp = (struct dev *)calloc(sizeof(struct dev));
		dp->d_dev =
			makedev(zd_b_major,
				unittominor(up - zdunit) | V_RAW_MINOR);
		dp->d_refcnt = 1;
		dp->d_flags = D_ASYNC;
		dp->d_ops = &zddevops;
		if (devsw_add(dp) < 0) {
			cmn_err(CE_WARN,
				"zd%d: problem adding dev 0x%x to devsw",
				up - zdunit, dp->d_dev);
			/*
			 *+ The system could not add a dev structure for the 
			 *+ specified drive.  A device with the same
			 *+ major/minor number has already been added.
			 *+ Corrective action:  check for a conflict in
			 *+ major/minor numbers.
			 */
			return (0);
		}
		dev_deref(dp);
		up->zu_physdev = dp;
		/*
		 * Establish an idea of the drive's size from the disk
		 * geometry.  This is convenient to use in zdread/zdwrite
		 * for the call to physck(), and for use in zdsize().
		 */
		up->zu_drive_size = dd->zdd_sectors * dd->zdd_tracks
				    * dd->zdd_cyls;
		up->zu_firstsect = dd->zdd_sectors * dd->zdd_tracks;
		up->zu_flags = 0;
		/* set an arbitary starting point for patrol seeks */
		up->zu_lastcyl = dd->zdd_cyls / 2;
		/* initialize some stuff for patrol seeks */
		up->zu_seek.b_flags = B_IOCTL | B_ASYNC;
		up->zu_seek.b_bcount = DEV_BSIZE;	/* data in CB */
		up->zu_seek.b_blkno = 0;
		up->zu_seek.b_error = 0;
		up->zu_seek.b_iotype = B_FILIO;
		/* allocate buffer for patrol reads */
		if (zdpatrolbuf == (char *)NULL) {
			callocrnd(CLBYTES);
			zdpatrolbuf = calloc(DEV_BSIZE);
		}
		up->zu_seek.b_un.b_addr = zdpatrolbuf;
		BIODONE(&up->zu_seek) = 0;
		/* Fill out CB */
		up->zu_seekcb.cb_cmd = ZDC_READ;
		up->zu_seekcb.cb_head = 0;
		up->zu_seekcb.cb_sect = 0;
		up->zu_seekcb.cb_count = DEV_BSIZE;
		return (1);
	}
	
	/* If drive not found AND wildcarded--short circuit */
	if (dev->unit < 0) 
		while (dev->config->dev_len != -1)
			dev->config++;
	
	/*
	 * If unit not found fill out unit for post-mortem/debug.
	 */
	if (up->zu_state == ZU_NOTFOUND) {
		up->zu_ctrlr = zdv->zdv_idx;
		up->zu_drive = zdv->zdv_drive;
	}
	return (0);
}

void
zd_init(driver)
	struct driver_module *driver;
{
	/*
	 * Send ZDC_INIT command to all alive ZDCs.
	 */
	init_zdcs((driver->numdev < nzdcs) ? driver->numdev : nzdcs);
}


/*
 * get_chancfg
 *	get channel configuration.
 * Return:
 *	0  - If command completes successfully. The controller structure
 *	     is filled with the channel configuration data.
 *	-1 - If error processing ZDC_GET_CHANCFG.
 *
 */
static int
get_chancfg(ctlrp, channel)
	register struct zdc_ctlr *ctlrp;
	int	channel;
{
	register struct	cb *cbp;
	struct	cb lcb;

	cbp = &lcb;
	bzero((caddr_t)cbp, sizeof(struct cb));
	cbp->cb_cmd = ZDC_GET_CHANCFG;		/* command */
	cbp->cb_addr = (ulong)chancfg;		/* where to put it */
	cbp->cb_count = sizeof(struct zdcdd);
	if (pollzdcmd(ctlrp, cbp, channel) < 0) {
		cmn_err(CE_WARN, "zdc%d: Cannot get channel cfg.",
			ctlrp - zdctrlr);
		/*
		 *+ The specified DCC controller is not responding
		 *+ to commands.  Corrective action:  call service.
		 */
		cmn_err(CE_CONT, "zdc%d at slic %d Deconfigured.\n",
				ctlrp - zdctrlr, ctlrp->zdc_slicaddr);
		ctlrp->zdc_state = ZDC_DEAD;
		ERRLIGHTON;
		return (-1);
	}
	if (cbp->cb_compcode == ZDC_NOCFG)
		return (0);

	if (channel == CHAN_A)
		ctlrp->zdc_chanA = *chancfg;
	else
		ctlrp->zdc_chanB = *chancfg;
	return (0);
}

/*
 * bad_zdc
 *	- mark all units on dead zdc as unusable.
 * Called only during configuration/initialization.
 */
static
bad_zdc(ctlr)
	register int ctlr;
{
	register struct zd_unit *up;
	register int any;

	cmn_err(CE_WARN, "zdc%d at slic %d Deconfigured.", ctlr,
					zdctrlr[ctlr].zdc_slicaddr);
	/*
	 *+ The specified DCC controller has failed configuration or
	 *+ initialization.  Corrective action:  call service.
	 */
	any = 0;
	for (up = zdunit; up < &zdunit[zd_max_unit+1]; up++) {
		if (up->zu_ctrlr == ctlr && up->zu_state == ZU_GOOD)  {
			++any;
			up->zu_state = ZU_BAD;		/* on dead ctrlr */
			disk_offline();
			cmn_err(CE_CONT, "zd%d, ", up - zdunit);
		}
	}
	if (any)
		cmn_err(CE_CONT, "%s unusable.\n", (any == 1) ? "is" : "are");
}

/*
 * init_zdc
 *	init all zdc controllers via ZDC_INIT command
 * Initialize interrupts. On successive commands, the ZDC will
 * generate interrupts to signal command completion.
 */
static
init_zdcs(numzdc)
	int	numzdc;
{
	register struct init_cb	 *icbp;
	register struct zdc_ctlr *ctlrp;
	unchar	cbvec, errvec;
	struct	init_cb lcb;

	icbp = &lcb;
	bzero((caddr_t)icbp, sizeof(struct init_cb));
	if (zdc_C_throttle > SLB_TVAL)
		zdc_C_throttle = SLB_TVAL;	/* Set to MAX */

	ctlrp = zdctrlr;
	errvec = base_err_intr;
	cbvec = base_cb_intr;
	for (;ctlrp < &zdctrlr[numzdc]; ctlrp++, errvec++, cbvec += NCBPERZDC){
		if (ctlrp->zdc_state != ZDC_ALIVE)
			continue;

		icbp->icb_cmd = ZDC_INIT;
		icbp->icb_pagesize = CLBYTES;
		icbp->icb_dest = SL_GROUP | TMPOS_GROUP;
		icbp->icb_bin = zdc_cb_bin;
		icbp->icb_vecbase = cbvec;
		icbp->icb_errdest = SL_GROUP | TMPOS_GROUP;
		icbp->icb_errbin = zdc_err_bin;
		icbp->icb_errvector = errvec;
		icbp->icb_ctrl = ZDC_ENABLE_INTR;
		icbp->icb_ctrl |= zdctrl;
		/*
		 * Enable 16 byte transfers if the bus mode is one of
		 * the BUS_EXTENDED bus modes.  Do this after above
		 * "or" of configurable so it can't accidently turn on
		 * 16 byte mode when illegal
		 */
		if (CD_LOC->c_sys_mode.sm_bus_mode == CD_BUS_EXTENDED_NARR ||
		    CD_LOC->c_sys_mode.sm_bus_mode == CD_BUS_EXTENDED_WIDE)
			icbp->icb_ctrl |= ZDC_SIXTEEN;
		else
			icbp->icb_ctrl &= ~ZDC_SIXTEEN;

		if (zdctrl & ZDC_DUMPONPANIC) {
			/*
			 * Give FW a place to dump its LRAM.
			 */
			callocrnd(CLBYTES);	/* align at 1K boundary */
			ctlrp->zdc_dumpaddr = calloc(ZDC_LRAMSZ);
			icbp->icb_dumpaddr = ctlrp->zdc_dumpaddr;
		}
		if (pollzdcmd(ctlrp, (struct cb *)icbp, CHAN_A) < 0) {
			cmn_err(CE_WARN, "zdc%d: Cannot init zdc.",
				ctlrp - zdctrlr);
			/*
			 *+ The specified DCC controller did not respond to
			 *+ an initialization command.  Corrective action:
			 *+ call service.
			 */
			ctlrp->zdc_state = ZDC_DEAD;
			ERRLIGHTON;
			/* Mark all units on this controller as BAD */
			bad_zdc(ctlrp - zdctrlr);
		}
		/*
		 * Set throttle value for DMA channel C.
		 */
		wrslave(ctlrp->zdc_slicaddr, SL_G_CHAN2,
				(unchar)(SLB_TH_ENB | zdc_C_throttle));
	}
}

/*
 * pollzdcmd
 *
 * Issue a command to the ZDC. 
 * Poll for completion.
 * Retry, when necessary.
 *
 * Return: transfer count
 *	 0 - Success
 * 	-1 - error.
 * NOTE: If controller error ctlrp->zdc_state is set to ZDC_DEAD.
 *	 The caller may choose to set ctlrp->zdc_state to ZDC_DEAD
 *	 upon error whether or not the error was a controller error.
 */
static
pollzdcmd(ctlrp, acbp, drive)
	register struct zdc_ctlr *ctlrp;
	struct cb	*acbp;
	register int	drive;
{
	register struct cb *cbp;
	register int	i;
	register int	val;
	struct cb lcb;		/* ZDC_RESET during retries */

	cbp = &ctlrp->zdc_cbp[drive * NCBPERDRIVE];
	acbp->cb_errcnt = 0;

retry:
	/*
	 * Fill in appropriate CB
	 */
	bcopy((caddr_t)acbp, (caddr_t)cbp, FWCBSIZE);

	/*
	 * Signal ZDC to do command. Poll until completion or timeout.
	 */
	cbp->cb_compcode = ZDC_BUSY;
	mIntr(ctlrp->zdc_slicaddr, CBBIN, (unchar)(drive * NCBPERDRIVE));

	i = calc_delay(zdccmdtime);
	while (cbp->cb_compcode == ZDC_BUSY) {
		if (--i == 0) {
			/*
			 * Timed out - check for controller error
			 */
			cmn_err(CE_WARN, "zdc%d: Cmd %x timeout.",
				ctlrp - zdctrlr, cbp->cb_cmd);
			/*
			 *+ The specified DCC controller timed out waiting
			 *+ for a command to complete.  Corrective action:
			 *+ call service.
			 */
			val = rdslave(ctlrp->zdc_slicaddr, SL_Z_STATUS);
			if ((val & SLB_ZPARERR) ||
					((val & ZDC_READY) != ZDC_READY)) {
				/*
				 * Found controller error.
				 */
				cmn_err(CE_WARN,
					"zdc%d: Ctrlr error status 0x%x.",
					ctlrp - zdctrlr, val);
				/*
				 *+ The specified DCC controller gave an
				 *+ unexpected error condition while
				 *+ processing a command.  Corrective
				 *+ action:  call service.
				 */

				/*
				 * controller bad!
				 */
				ctlrp->zdc_state = ZDC_DEAD;
			}
			return (-1);
		}
	}

	switch (cbp->cb_compcode) {

	case ZDC_SOFTECC:
	case ZDC_CORRECC:
		/*
		 * Corrected or Soft ECC error.
		 */
		cmn_err(CE_WARN, "zd%d: %s at (%d, %d, %d).",
			cbp->cb_unit,
			zd_compcodes[cbp->cb_compcode],
			cbp->cb_cyl, cbp->cb_head, cbp->cb_sect);
		/*
		 *+ The DCC driver received the specified error
		 *+ completion status from the DCC firmware for the
		 *+ indicated drive partition.  The '%s' translates
		 *+ to the error code received.  Information about
		 *+ that error appears in this chapter.
		 *+ The final '(%d, %d, %d)' indicates the cylinder,
		 *+ head, and sector in the failing command block.
		 */
		zddumpstatus(cbp);
		/* Fall into... */
	case ZDC_DONE:
		/*
		 * Normal completion
		 */
		*acbp = *cbp;
		return (0);

	case ZDC_DRVPROT:
	case ZDC_ECC:
	case ZDC_BADDRV:
	case ZDC_DDC_STAT:
		goto hard;

	case ZDC_DMA_TO:
	case ZDC_REVECT:
	case ZDC_ILLCMD:
	case ZDC_ILLMOD:
	case ZDC_ILLALIGN:
	case ZDC_ILLCNT:
	case ZDC_ILLIOV:
	case ZDC_ILLVECIO:
	case ZDC_ILLPGSZ:
	case ZDC_ILLDUMPADR:
	case ZDC_ILLCHS:
	case ZDC_CBREUSE:
		/*
		 * Hard error. This set indicates controller error.
		 */
		ctlrp->zdc_state = ZDC_DEAD;
		goto hard;

	case ZDC_CH_RESET:
		/*
		 * Drive has failed in the middle of the command!
		 */
		cmn_err(CE_WARN, "zd%d: Drive failed during cmd 0x%x.",
					cbp->cb_unit, cbp->cb_cmd);
		/*
		 *+ The specified drive failed to complete a command.
		 *+ Corrective action:  call service.
		 */
		goto hard;

	case ZDC_ACCERR:
		/*
		 * Most likely controller problem.
		 * Clear access error and notify firmware.
		 */
		val = rdslave(ctlrp->zdc_slicaddr,
				(unchar)((drive & 1) ? SL_G_ACCERR1
						     : SL_G_ACCERR0));
		cmn_err(CE_WARN,
			"zd%d: Access error 0x%x on transfer at 0x%x.",
					drive, val, cbp->cb_addr);
		/*
		 *+ The specified zd drive had an access error.  This is
		 *+ probably a controller problem.  Corrective action:
		 *+ call service.
		 */
		wrslave(ctlrp->zdc_slicaddr,
			(unchar)((drive & 1) ? SL_G_ACCERR1 : SL_G_ACCERR0),
			(unchar)0xbb);
		ctlrp->zdc_state = ZDC_DEAD;
		goto hard;

	case ZDC_NOCFG:
		*acbp = *cbp;
		if (cbp->cb_cmd == ZDC_GET_CHANCFG)
			return (0);
		return (-1);

	case ZDC_HDR_ECC:
	case ZDC_SNF:
	case ZDC_SO:
	case ZDC_NDS:
	case ZDC_DRVFLT:
	case ZDC_SEEKERR:
	case ZDC_SEEK_TO:
	case ZDC_CH_TO:
	case ZDC_FDL:
		/*
		 * Retry these errors.
		 */
		break;

	default:
		/*
		 * Unknown completion code - controller bad?
		 */
		cmn_err(CE_WARN, "zdc%d: Bad compcode 0x%x.",
			ctlrp - zdctrlr, cbp->cb_compcode);
		/*
		 *+ The specified DCC controller returned a completion code
		 *+ that is unknown to the driver.  Corrective action: call
		 *+ service.
		 */
		cbp->cb_compcode = zdncompcodes - 1;	/* Get nice message */
		ctlrp->zdc_state = ZDC_DEAD;
		goto hard;
	}

	if (cbp->cb_cmd == ZDC_RESET)
		return (-1);

	cmn_err(CE_WARN, "zd%d: Error (%s); cmd = 0x%x at (%d, %d, %d).",
		cbp->cb_unit, zd_compcodes[cbp->cb_compcode], cbp->cb_cmd,
		cbp->cb_cyl, cbp->cb_head, cbp->cb_sect);
	/*
	 *+ The DCC driver received the specified error
	 *+ completion status from the DCC firmware for the
	 *+ indicated drive partition.  The '%s' translates
	 *+ to the error code received.  Information about
	 *+ that error appears in this chapter.
	 *+ The final '(%d, %d, %d)' indicates the cylinder,
	 *+ head, and sector in the failing command block.
	 */
	zddumpstatus(cbp);
	/*
	 * Retry. Reset the drive each time.
	 */
	if (acbp->cb_errcnt++ < zdcretry) {
		lcb.cb_cmd = ZDC_RESET;
		if (pollzdcmd(ctlrp, &lcb, drive) < 0) {
			cmn_err(CE_WARN, "zd%d: RESET failed.  [1]",
				cbp->cb_unit);
			/*
			 *+ The specified zd drive failed to complete the
			 *+ RESET command, which was issued after another error
			 *+ occurred.  Corrective action:  call service.
			 */
			goto hard;
		}
		goto retry;
	} else {
		/*
		 * save cb contents to return.
		 */
		*acbp = *cbp;
		lcb.cb_cmd = ZDC_RESET;
		if (pollzdcmd(ctlrp, &lcb, drive) < 0)
			cmn_err(CE_WARN, "zd%d: RESET failed.  [2]",
				cbp->cb_unit);
			/*
			 *+ The specified zd drive failed to complete the
			 *+ RESET command, which was issued after another error
			 *+ occurred.  Corrective action:  call service.
			 */
		bcopy((caddr_t)acbp, (caddr_t)cbp, FWCBSIZE);
	}

hard:
	/*
	 * Update argument cb and return
	 */
	cmn_err(CE_WARN, "zd%d: Hard error (%s); cmd = 0x%x at (%d, %d, %d).",
		cbp->cb_unit, zd_compcodes[cbp->cb_compcode], cbp->cb_cmd,
		cbp->cb_cyl, cbp->cb_head, cbp->cb_sect);
	/*
	 *+ The specified drive had a hard error on the specified command
	 *+ at (cylinder, head, sector).  Corrective action:  if the location
	 *+ where the hard error occurs is constant, use the format
	 *+ command to add the sector to the bad block list.  If multiple
	 *+ locations are getting hard errors, call service.
	 */
	zddumpstatus(cbp);
	*acbp = *cbp;
	return (-1);
}
