/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/sunndi.h>
#include <sys/ndi_impldefs.h>
#include <sys/obpdefs.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/ivintr.h>
#include <sys/autoconf.h>
#include <sys/intreg.h>
#include <sys/proc.h>
#include <sys/modctl.h>
#include <sys/callb.h>
#include <sys/file.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/fhc.h>
#include <sys/sysctrl.h>
#include <sys/jtag.h>
#include <sys/ac.h>
#include <sys/simmstat.h>
#include <sys/clock.h>
#include <sys/promif.h>
#include <sys/promimpl.h>
#include <sys/sunndi.h>
#include <sys/machsystm.h>

/* Useful debugging Stuff */
#ifdef DEBUG
int sysc_debug_info = 1;
int sysc_debug_print_level = 0;
#endif

/*
 * Function prototypes
 */
static int sysctrl_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
		void **result);

static int sysctrl_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);

static int sysctrl_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);

static int sysctrl_open(dev_t *, int, int, cred_t *);

static int sysctrl_close(dev_t, int, int, cred_t *);

static int sysctrl_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static uint_t system_high_handler(caddr_t arg);

static uint_t spur_delay(caddr_t arg);

static void spur_retry(void *);

static uint_t spur_reenable(caddr_t arg);

static void spur_long_timeout(void *);

static uint_t spur_clear_count(caddr_t arg);

static uint_t ac_fail_handler(caddr_t arg);

static void ac_fail_retry(void *);

static uint_t ac_fail_reenable(caddr_t arg);

static uint_t ps_fail_int_handler(caddr_t arg);

static uint_t ps_fail_poll_handler(caddr_t arg);

static uint_t ps_fail_handler(struct sysctrl_soft_state *softsp, int fromint);

enum power_state compute_power_state(struct sysctrl_soft_state *softsp,
					int plus_load);

static void ps_log_state_change(struct sysctrl_soft_state *softsp,
					int index, int present);

static void ps_log_pres_change(struct sysctrl_soft_state *softsp,
					int index, int present);

static void ps_fail_retry(void *);

static uint_t pps_fanfail_handler(caddr_t arg);

static void pps_fanfail_retry(void *);

static uint_t pps_fanfail_reenable(caddr_t arg);

static void pps_fan_poll(void *);

static void pps_fan_state_change(struct sysctrl_soft_state *softsp,
					int index, int fan_ok);

static uint_t bd_insert_handler(caddr_t arg);

static void bd_insert_timeout(void *);

static void bd_remove_timeout(void *);

static uint_t bd_insert_normal(caddr_t arg);

static void sysctrl_add_kstats(struct sysctrl_soft_state *softsp);

static int sysctrl_kstat_update(kstat_t *ksp, int rw);

static int psstat_kstat_update(kstat_t *, int);

static void init_remote_console_uart(struct sysctrl_soft_state *);

static void blink_led_timeout(void *);

static uint_t blink_led_handler(caddr_t arg);

static void sysctrl_thread_wakeup(void *type);

static void sysctrl_overtemp_poll(void);

static void sysctrl_keyswitch_poll(void);

static void update_key_state(struct sysctrl_soft_state *);

static void sysctrl_abort_seq_handler(char *msg);

static void nvram_update_powerfail(struct sysctrl_soft_state *softsp);

static void toggle_board_green_leds(int);

void bd_remove_poll(struct sysctrl_soft_state *);

static void sysc_slot_info(int nslots, int *start, int *limit, int *incr);

extern void sysc_board_connect_supported_init(void);

static void rcons_reinit(struct sysctrl_soft_state *softsp);

/*
 * Configuration data structures
 */
static struct cb_ops sysctrl_cb_ops = {
	sysctrl_open,		/* open */
	sysctrl_close,		/* close */
	nulldev,		/* strategy */
	nulldev,		/* print */
	nulldev,		/* dump */
	nulldev,		/* read */
	nulldev,		/* write */
	sysctrl_ioctl,		/* ioctl */
	nodev,			/* devmap */
	nodev,			/* mmap */
	nodev,			/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	0,			/* streamtab */
	D_MP|D_NEW,		/* Driver compatibility flag */
	CB_REV,			/* rev */
	nodev,			/* cb_aread */
	nodev			/* cb_awrite */
};

static struct dev_ops sysctrl_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* refcnt */
	sysctrl_info,		/* getinfo */
	nulldev,		/* identify */
	nulldev,		/* probe */
	sysctrl_attach,		/* attach */
	sysctrl_detach,		/* detach */
	nulldev,		/* reset */
	&sysctrl_cb_ops,	/* cb_ops */
	(struct bus_ops *)0,	/* bus_ops */
	nulldev			/* power */
};

void *sysctrlp;				/* sysctrl soft state hook */

/* # of ticks to silence spurious interrupts */
static clock_t spur_timeout_hz;

/* # of ticks to count spurious interrupts to print message */
static clock_t spur_long_timeout_hz;

/* # of ticks between AC failure polling */
static clock_t ac_timeout_hz;

/* # of ticks between Power Supply Failure polling */
static clock_t ps_fail_timeout_hz;

/*
 * # of ticks between Peripheral Power Supply failure polling
 * (used both for interrupt retry timeout and polling function)
 */
static clock_t pps_fan_timeout_hz;

/* # of ticks delay after board insert interrupt */
static clock_t bd_insert_delay_hz;

/* # of secs to wait before restarting poll if we cannot clear interrupts */
static clock_t bd_insert_retry_hz;

/* # of secs between Board Removal polling */
static clock_t bd_remove_timeout_hz;

/* # of secs between toggle of OS LED */
static clock_t blink_led_timeout_hz;

/* overtemp polling routine timeout delay */
static clock_t overtemp_timeout_hz;

/* key switch polling routine timeout delay */
static clock_t keyswitch_timeout_hz;

/* Specify which system interrupt condition to monitor */
int enable_sys_interrupt = SYS_AC_PWR_FAIL_EN | SYS_PPS_FAN_FAIL_EN |
			SYS_PS_FAIL_EN | SYS_SBRD_PRES_EN;

/* Should the overtemp_poll thread be running? */
static int sysctrl_do_overtemp_thread = 1;

/* Should the keyswitch_poll thread be running? */
static int sysctrl_do_keyswitch_thread = 1;

/*
 * This timeout ID is for board remove polling routine. It is
 * protected by the fhc_bdlist mutex.
 * XXX - This will not work for wildfire. A different scheme must be
 * used since there will be multiple sysctrl nodes, each with its
 * own list of hotplugged boards to scan.
 */
static timeout_id_t bd_remove_to_id = 0;

/*
 * If this is set, the system will not shutdown when insufficient power
 * condition persists.
 */
int disable_insufficient_power_reboot = 0;

/*
 * Set this to enable suspend/resume
 */
int sysctrl_enable_detach_suspend = 0;

/*
 * Set this to reflect the OBP initialized HOTPLUG_DISABLED_PROPERTY and
 * during dynamic detection
 */
int sysctrl_hotplug_disabled = FALSE;

/* Indicates whether or not the overtemp thread has been started */
static int sysctrl_overtemp_thread_started = 0;

/* Indicates whether or not the key switch thread has been started */
static int sysctrl_keyswitch_thread_started = 0;

/* *Mutex used to protect the soft state list */
static kmutex_t sslist_mutex;

/* The CV is used to wakeup the overtemp thread when needed. */
static kcondvar_t overtemp_cv;

/* The CV is used to wakeup the key switch thread when needed. */
static kcondvar_t keyswitch_cv;

/* This mutex is used to protect the sysctrl_ddi_branch_init variable */
static kmutex_t sysctrl_branch_mutex;

/*
 * This variable is set after all existing branches in the system have
 * been discovered and held via e_ddi_branch_hold(). This happens on
 * first open() of any sysctrl minor node.
 */
static int sysctrl_ddi_branch_init;

/*
 * Linked list of all syctrl soft state structures.
 * Used for polling sysctrl state changes, i.e. temperature.
 */
struct sysctrl_soft_state *sys_list = NULL;

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module.  This one is a driver */
	"Clock Board %I%",	/* name of module */
	&sysctrl_ops,		/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,		/* rev */
	(void *)&modldrv,
	NULL
};

#ifndef lint
char _depends_on[] = "drv/fhc";
#endif /* lint */

/*
 * These are the module initialization routines.
 */

int
_init(void)
{
	int error;

	if ((error = ddi_soft_state_init(&sysctrlp,
	    sizeof (struct sysctrl_soft_state), 1)) != 0)
		return (error);

	error = mod_install(&modlinkage);
	if (error != 0) {
		ddi_soft_state_fini(&sysctrlp);
		return (error);
	}

	mutex_init(&sysctrl_branch_mutex, NULL, MUTEX_DRIVER, NULL);

	return (0);
}

int
_fini(void)
{
	int error;

	if ((error = mod_remove(&modlinkage)) != 0)
		return (error);

	ddi_soft_state_fini(&sysctrlp);

	mutex_destroy(&sysctrl_branch_mutex);

	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/* ARGSUSED */
static int
sysctrl_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	dev_t	dev;
	int	instance;

	if (infocmd == DDI_INFO_DEVT2INSTANCE) {
		dev = (dev_t)arg;
		instance = GETINSTANCE(dev);
		*result = (void *)(uintptr_t)instance;
		return (DDI_SUCCESS);
	}
	return (DDI_FAILURE);
}

static int
sysctrl_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	struct sysctrl_soft_state *softsp;
	int instance;
	uchar_t tmp_reg;
	dev_info_t *dip;
	char *propval;
	int proplen;
	int slot_num;
	int start;		/* start index for scan loop */
	int limit;		/* board number limit for scan loop */
	int incr;		/* amount to incr each pass thru loop */
	void set_clockbrd_info(void);


	switch (cmd) {
	case DDI_ATTACH:
		break;

	case DDI_RESUME:
		/* XXX see sysctrl:DDI_SUSPEND for special h/w treatment */
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(devi);

	if (ddi_soft_state_zalloc(sysctrlp, instance) != DDI_SUCCESS)
		return (DDI_FAILURE);

	softsp = GETSOFTC(instance);

	/* Set the dip in the soft state */
	softsp->dip = devi;

	/* Set up the parent dip */
	softsp->pdip = ddi_get_parent(softsp->dip);

	DPRINTF(SYSCTRL_ATTACH_DEBUG, ("sysctrl: devi= 0x%p\n, softsp=0x%p\n",
		devi, softsp));

	/* First set all of the timeout values */
	spur_timeout_hz = drv_usectohz(SPUR_TIMEOUT_USEC);
	spur_long_timeout_hz = drv_usectohz(SPUR_LONG_TIMEOUT_USEC);
	ac_timeout_hz = drv_usectohz(AC_TIMEOUT_USEC);
	ps_fail_timeout_hz = drv_usectohz(PS_FAIL_TIMEOUT_USEC);
	pps_fan_timeout_hz = drv_usectohz(PPS_FAN_TIMEOUT_USEC);
	bd_insert_delay_hz = drv_usectohz(BRD_INSERT_DELAY_USEC);
	bd_insert_retry_hz = drv_usectohz(BRD_INSERT_RETRY_USEC);
	bd_remove_timeout_hz = drv_usectohz(BRD_REMOVE_TIMEOUT_USEC);
	blink_led_timeout_hz = drv_usectohz(BLINK_LED_TIMEOUT_USEC);
	overtemp_timeout_hz = drv_usectohz(OVERTEMP_TIMEOUT_SEC * MICROSEC);
	keyswitch_timeout_hz = drv_usectohz(KEYSWITCH_TIMEOUT_USEC);

	/*
	 * Map in the registers sets that OBP hands us. According
	 * to the sun4u device tree spec., the register sets are as
	 * follows:
	 *
	 *	0	Clock Frequency Registers (contains the bit
	 *		for enabling the remote console reset)
	 *	1	Misc (has all the registers that we need
	 *	2	Clock Version Register
	 */
	if (ddi_map_regs(softsp->dip, 0,
	    (caddr_t *)&softsp->clk_freq1, 0, 0)) {
		cmn_err(CE_WARN, "sysctrl%d: unable to map clock frequency "
			"registers", instance);
		goto bad0;
	}

	if (ddi_map_regs(softsp->dip, 1,
	    (caddr_t *)&softsp->csr, 0, 0)) {
		cmn_err(CE_WARN, "sysctrl%d: unable to map internal"
			"registers", instance);
		goto bad1;
	}

	/*
	 * There is a new register for newer vintage clock board nodes,
	 * OBP register set 2 in the clock board node.
	 *
	 */
	(void) ddi_map_regs(softsp->dip, 2, (caddr_t *)&softsp->clk_ver, 0, 0);

	/*
	 * Fill in the virtual addresses of the registers in the
	 * sysctrl_soft_state structure. We do not want to calculate
	 * them on the fly. This way we waste a little memory, but
	 * avoid bugs down the road.
	 */
	softsp->clk_freq2 = (uchar_t *)((caddr_t)softsp->clk_freq1 +
		SYS_OFF_CLK_FREQ2);

	softsp->status1 = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_STAT1);

	softsp->status2 = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_STAT2);

	softsp->ps_stat = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_PSSTAT);

	softsp->ps_pres = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_PSPRES);

	softsp->pppsr = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_PPPSR);

	softsp->temp_reg = (uchar_t *)((caddr_t)softsp->csr +
		SYS_OFF_TEMP);

	set_clockbrd_info();

	/*
	 * Enable the hardware watchdog gate on the clock board if
	 * map_wellknown has detected that watchdog timer is available
	 * and user wants it to be enabled.
	 */
	if (watchdog_available && watchdog_enable)
		*(softsp->clk_freq2) |= TOD_RESET_EN;
	else
		*(softsp->clk_freq2) &= ~TOD_RESET_EN;

	/* Check for inherited faults from the PROM. */
	if (*softsp->csr & SYS_LED_MID) {
		reg_fault(0, FT_PROM, FT_SYSTEM);
	}

	/*
	 * calculate and cache the number of slots on this system
	 */
	switch (SYS_TYPE(*softsp->status1)) {
	case SYS_16_SLOT:
		softsp->nslots = 16;
		break;

	case SYS_8_SLOT:
		softsp->nslots = 8;
		break;

	case SYS_4_SLOT:
		/* check the clk_version register - if the ptr is valid */
		if ((softsp->clk_ver != NULL) &&
		    (SYS_TYPE2(*softsp->clk_ver) == SYS_PLUS_SYSTEM)) {
			softsp->nslots = 5;
		} else {
			softsp->nslots = 4;
		}
		break;

	case SYS_TESTBED:
	default:
		softsp->nslots = 0;
		break;
	}


	/* create the fault list kstat */
	create_ft_kstats(instance);

	/*
	 * Do a priming read on the ADC, and throw away the first value
	 * read. This is a feature of the ADC hardware. After a power cycle
	 * it does not contains valid data until a read occurs.
	 */
	tmp_reg = *(softsp->temp_reg);

	/* Wait 30 usec for ADC hardware to stabilize. */
	DELAY(30);

	/* shut off all interrupt sources */
	*(softsp->csr) &= ~(SYS_PPS_FAN_FAIL_EN | SYS_PS_FAIL_EN |
				SYS_AC_PWR_FAIL_EN | SYS_SBRD_PRES_EN);
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif

	/*
	 * Now register our high interrupt with the system.
	 */
	if (ddi_add_intr(devi, 0, &softsp->iblock,
	    &softsp->idevice, (uint_t (*)(caddr_t))nulldev, NULL) !=
	    DDI_SUCCESS)
		goto bad2;

	mutex_init(&softsp->csr_mutex, NULL, MUTEX_DRIVER,
	    (void *)softsp->iblock);

	ddi_remove_intr(devi, 0, softsp->iblock);

	if (ddi_add_intr(devi, 0, &softsp->iblock,
	    &softsp->idevice, system_high_handler, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad3;

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->spur_id,
	    &softsp->spur_int_c, NULL, spur_delay, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad4;

	mutex_init(&softsp->spur_int_lock, NULL, MUTEX_DRIVER,
		(void *)softsp->spur_int_c);


	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->spur_high_id,
	    NULL, NULL, spur_reenable, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad5;

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->spur_long_to_id,
	    NULL, NULL, spur_clear_count, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad6;

	/*
	 * Now register low-level ac fail handler
	 */
	if (ddi_add_softintr(devi, DDI_SOFTINT_HIGH, &softsp->ac_fail_id,
	    NULL, NULL, ac_fail_handler, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad7;

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->ac_fail_high_id,
	    NULL, NULL, ac_fail_reenable, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad8;

	/*
	 * Now register low-level ps fail handler
	 */

	if (ddi_add_softintr(devi, DDI_SOFTINT_HIGH, &softsp->ps_fail_int_id,
	    &softsp->ps_fail_c, NULL, ps_fail_int_handler, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad9;

	mutex_init(&softsp->ps_fail_lock, NULL, MUTEX_DRIVER,
		(void *)softsp->ps_fail_c);

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->ps_fail_poll_id,
	    NULL, NULL, ps_fail_poll_handler, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad10;

	/*
	 * Now register low-level pps fan fail handler
	 */
	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->pps_fan_id,
	    NULL, NULL, pps_fanfail_handler, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad11;

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->pps_fan_high_id,
	    NULL, NULL, pps_fanfail_reenable, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad12;

	/*
	 * Based upon a check for a current share backplane, advise
	 * that system does not support hot plug
	 *
	 */
	if ((*(softsp->pppsr) & SYS_NOT_CURRENT_S) != 0) {
		cmn_err(CE_NOTE, "Hot Plug not supported in this system");
		sysctrl_hotplug_disabled = TRUE;
	}

	/*
	 * If the trigger circuit is busted or the NOT_BRD_PRES line
	 * is stuck then OBP will publish this property stating that
	 * hot plug is not available.  If this happens we will complain
	 * to the console and register a system fault.  We will also
	 * not enable the board insert interrupt for this session.
	 */
	if (ddi_prop_op(DDI_DEV_T_ANY, softsp->dip, PROP_LEN_AND_VAL_ALLOC,
	    DDI_PROP_DONTPASS, HOTPLUG_DISABLED_PROPERTY,
	    (caddr_t)&propval, &proplen) == DDI_PROP_SUCCESS) {
		cmn_err(CE_WARN, "Hot Plug Unavailable [%s]", propval);
		reg_fault(0, FT_HOT_PLUG, FT_SYSTEM);
		sysctrl_hotplug_disabled = TRUE;
		enable_sys_interrupt &= ~SYS_SBRD_PRES_EN;
		kmem_free(propval, proplen);
	}

	sysc_board_connect_supported_init();

	fhc_bd_sc_register(sysc_policy_update, softsp);

	sysc_slot_info(softsp->nslots, &start, &limit, &incr);

	/* Prime the board list. */
	fhc_bdlist_prime(start, limit, incr);

	/*
	 * Set up a board remove timeout call.
	 */
	(void) fhc_bdlist_lock(-1);

	DPRINTF(SYSCTRL_ATTACH_DEBUG,
		("attach: start bd_remove_poll()..."));

	bd_remove_poll(softsp);
	fhc_bdlist_unlock();

	/*
	 * Now register low-level board insert handler
	 */
	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->sbrd_pres_id,
	    NULL, NULL, bd_insert_handler, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad13;

	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->sbrd_gone_id,
	    NULL, NULL, bd_insert_normal, (caddr_t)softsp) != DDI_SUCCESS)
		goto bad14;

	/*
	 * Now register led blink handler (interrupt level)
	 */
	if (ddi_add_softintr(devi, DDI_SOFTINT_LOW, &softsp->blink_led_id,
	    &softsp->sys_led_c, NULL, blink_led_handler, (caddr_t)softsp) !=
	    DDI_SUCCESS)
		goto bad15;
	mutex_init(&softsp->sys_led_lock, NULL, MUTEX_DRIVER,
		(void *)softsp->sys_led_c);

	/* initialize the bit field for all pps fans to assumed good */
	softsp->pps_fan_saved = softsp->pps_fan_external_state =
		SYS_AC_FAN_OK | SYS_KEYSW_FAN_OK;

	/* prime the power supply state machines */
	if (enable_sys_interrupt & SYS_PS_FAIL_EN)
		ddi_trigger_softintr(softsp->ps_fail_poll_id);


	/* kick off the OS led blinker */
	softsp->sys_led = FALSE;
	ddi_trigger_softintr(softsp->blink_led_id);

	/* Now enable selected interrupt sources */
	mutex_enter(&softsp->csr_mutex);
	*(softsp->csr) |= enable_sys_interrupt &
		(SYS_AC_PWR_FAIL_EN | SYS_PS_FAIL_EN |
		SYS_PPS_FAN_FAIL_EN | SYS_SBRD_PRES_EN);
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif
	mutex_exit(&softsp->csr_mutex);

	/* Initialize the temperature */
	init_temp_arrays(&softsp->tempstat);

	/*
	 * initialize key switch shadow state
	 */
	softsp->key_shadow = KEY_BOOT;

	/*
	 * Now add this soft state structure to the front of the linked list
	 * of soft state structures.
	 */
	if (sys_list == (struct sysctrl_soft_state *)NULL) {
		mutex_init(&sslist_mutex, NULL, MUTEX_DEFAULT, NULL);
	}
	mutex_enter(&sslist_mutex);
	softsp->next = sys_list;
	sys_list = softsp;
	mutex_exit(&sslist_mutex);

	/* Setup the kstats for this device */
	sysctrl_add_kstats(softsp);

	/* kick off the PPS fan poll routine */
	pps_fan_poll(softsp);

	if (sysctrl_overtemp_thread_started == 0) {
		/*
		 * set up the overtemp condition variable before
		 * starting the thread.
		 */
		cv_init(&overtemp_cv, NULL, CV_DRIVER, NULL);

		/*
		 * start up the overtemp polling thread
		 */
		(void) thread_create(NULL, 0, (void (*)())sysctrl_overtemp_poll,
		    NULL, 0, &p0, TS_RUN, minclsyspri);
		sysctrl_overtemp_thread_started++;
	}

	if (sysctrl_keyswitch_thread_started == 0) {
		extern void (*abort_seq_handler)();

		/*
		 * interpose sysctrl's abort sequence handler
		 */
		abort_seq_handler = sysctrl_abort_seq_handler;

		/*
		 * set up the key switch condition variable before
		 * starting the thread
		 */
		cv_init(&keyswitch_cv, NULL, CV_DRIVER, NULL);

		/*
		 * start up the key switch polling thread
		 */
		(void) thread_create(NULL, 0,
		    (void (*)())sysctrl_keyswitch_poll, NULL, 0, &p0,
		    TS_RUN, minclsyspri);
		sysctrl_keyswitch_thread_started++;
	}

	/*
	 * perform initialization to allow setting of powerfail-time
	 */
	if ((dip = ddi_find_devinfo("options", -1, 0)) == NULL)
		softsp->options_nodeid = (pnode_t)NULL;
	else
		softsp->options_nodeid = (pnode_t)ddi_get_nodeid(dip);

	DPRINTF(SYSCTRL_ATTACH_DEBUG,
		("sysctrl: Creating devices start:%d, limit:%d, incr:%d\n",
		start, limit, incr));

	/*
	 * Create minor node for each system attachment points
	 */
	for (slot_num = start; slot_num < limit; slot_num = slot_num + incr) {
		char name[30];
		(void) sprintf(name, "slot%d", slot_num);
		if (ddi_create_minor_node(devi, name, S_IFCHR,
		    (PUTINSTANCE(instance) | slot_num),
		    DDI_NT_ATTACHMENT_POINT, 0) == DDI_FAILURE) {
			cmn_err(CE_WARN, "sysctrl%d: \"%s\" "
				"ddi_create_minor_node failed",
				instance, name);
			goto bad16;
		}
	}

	ddi_report_dev(devi);

	/*
	 * Remote console is inherited from POST
	 */
	if ((*(softsp->clk_freq2) & RCONS_UART_EN) == 0) {
		softsp->enable_rcons_atboot = FALSE;
		cmn_err(CE_WARN, "Remote console not active");
	} else
		softsp->enable_rcons_atboot = TRUE;

	return (DDI_SUCCESS);

bad16:
	cv_destroy(&keyswitch_cv);
	cv_destroy(&overtemp_cv);
	mutex_destroy(&sslist_mutex);
	mutex_destroy(&softsp->sys_led_lock);
	ddi_remove_softintr(softsp->blink_led_id);
bad15:
	ddi_remove_softintr(softsp->sbrd_gone_id);
bad14:
	ddi_remove_softintr(softsp->sbrd_pres_id);
bad13:
	ddi_remove_softintr(softsp->pps_fan_high_id);
bad12:
	ddi_remove_softintr(softsp->pps_fan_id);
bad11:
	ddi_remove_softintr(softsp->ps_fail_poll_id);
bad10:
	mutex_destroy(&softsp->ps_fail_lock);
	ddi_remove_softintr(softsp->ps_fail_int_id);
bad9:
	ddi_remove_softintr(softsp->ac_fail_high_id);
bad8:
	ddi_remove_softintr(softsp->ac_fail_id);
bad7:
	ddi_remove_softintr(softsp->spur_long_to_id);
bad6:
	ddi_remove_softintr(softsp->spur_high_id);
bad5:
	mutex_destroy(&softsp->spur_int_lock);
	ddi_remove_softintr(softsp->spur_id);
bad4:
	ddi_remove_intr(devi, 0, softsp->iblock);
bad3:
	mutex_destroy(&softsp->csr_mutex);
bad2:
	ddi_unmap_regs(softsp->dip, 1, (caddr_t *)&softsp->csr, 0, 0);
	if (softsp->clk_ver != NULL)
		ddi_unmap_regs(softsp->dip, 2, (caddr_t *)&softsp->clk_ver,
		    0, 0);
bad1:
	ddi_unmap_regs(softsp->dip, 0, (caddr_t *)&softsp->clk_freq1, 0, 0);

bad0:
	ddi_soft_state_free(sysctrlp, instance);
	ddi_remove_minor_node(dip, NULL);
	cmn_err(CE_WARN,
	    "sysctrl%d: Initialization failure. Some system level events,"
	    " {AC Fail, Fan Failure, PS Failure} not detected", instance);
	return (DDI_FAILURE);
}

struct sysc_hold {
	int start;
	int limit;
	int incr;
	int hold;
};

static int
sysctrl_hold_rele_branches(dev_info_t *dip, void *arg)
{
	int *rp, len, slot, i;
	struct sysc_hold *ap = (struct sysc_hold *)arg;

	/*
	 * For Sunfire, top nodes on board are always children of root dip
	 */
	ASSERT(ddi_get_parent(dip) == ddi_root_node());

	/*
	 * Skip non-PROM and "central" nodes
	 */
	if (!ndi_dev_is_prom_node(dip) ||
	    strcmp(ddi_node_name(dip), "central") == 0)
		return (DDI_WALK_PRUNECHILD);

	/*
	 * Extract board # from reg property.
	 */
	if (ddi_getlongprop(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "reg", (caddr_t)&rp, &len)
	    != DDI_SUCCESS) {
		DPRINTF(SYSC_DEBUG, ("devinfo node %s(%p) has no reg"
		    " property\n", ddi_node_name(dip), (void *)dip));
		return (DDI_WALK_PRUNECHILD);
	}

	slot = (*rp - 0x1c0) >> 2;
	kmem_free(rp, len);

	ASSERT(ap->start >= 0 && ap->start < ap->limit);

	for (i = ap->start; i < ap->limit; i = i + ap->incr) {
		if (i == slot)
			break;
	}

	if (i >= ap->limit) {
		DPRINTF(SYSC_DEBUG, ("sysctrl_hold_rele: Invalid board # (%d)"
		    " for node %s(%p)\n", slot, ddi_node_name(dip),
		    (void *)dip));
		return (DDI_WALK_PRUNECHILD);
	}

	if (ap->hold) {
		ASSERT(!e_ddi_branch_held(dip));
		e_ddi_branch_hold(dip);
	} else {
		ASSERT(e_ddi_branch_held(dip));
		e_ddi_branch_rele(dip);
	}

	return (DDI_WALK_PRUNECHILD);
}

/* ARGSUSED */
static int
sysctrl_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
#ifdef	SYSCTRL_SUPPORTS_DETACH
	dev_info_t			*rdip;
	struct sysc_hold		arg = {0};
	struct sysctrl_soft_state	*softsp;
#endif	/* SYSCTRL_SUPPORTS_DETACH */

	if (sysctrl_enable_detach_suspend == FALSE)
		return (DDI_FAILURE);

	switch (cmd) {
	case DDI_SUSPEND:
		/*
		 * XXX we don't presently save the state of the remote
		 * console because it is a constant function of POST.
		 * XXX we don't deal with the hardware watchdog here
		 * either.  It should be handled in hardclk.
		 */
		return (DDI_SUCCESS);

	case DDI_DETACH:
		break;
	default:
		return (DDI_FAILURE);
	}

#ifdef	SYSCTRL_SUPPORTS_DETACH

	/*
	 * XXX If sysctrl ever supports detach, this code should be enabled
	 * This is only the portion of the detach code dealing with
	 * the DDI branch routines. Other parts of detach will need
	 * to be added.
	 */

	/*
	 * Walk immediate children of root devinfo node, releasing holds
	 * on branches acquired in first sysctrl_open().
	 */

	instance = ddi_get_instance(dip);
	softsp = GETSOFTC(instance);

	if (softsp == NULL) {
		cmn_err(CE_WARN, "sysctrl%d device not attached", instance);
		return (DDI_FAILURE);
	}

	sysc_slot_info(softsp->nslots, &arg.start, &arg.limit, &arg.incr);

	arg.hold = 0;

	rdip = ddi_root_node();

	ndi_devi_enter(rdip, &circ);
	ddi_walk_devs(ddi_get_child(rdip), sysctrl_hold_rele_branches, &arg);
	ndi_devi_exit(rdip, circ);

	sysctrl_ddi_branch_init = 0;

	return (DDI_SUCCESS);
#endif	/* SYSCTRL_SUPPORTS_DETACH */

	return (DDI_FAILURE);
}

/* ARGSUSED */
static int
sysctrl_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
	int		instance;
	int		slot;
	dev_t		dev;
	int		circ;
	dev_info_t	*rdip;
	struct sysc_hold arg = {0};
	struct sysctrl_soft_state *softsp;

	dev = *devp;

	/*
	 * We checked against the instance softstate structure since there
	 * will only be one instance of sysctrl (clock board) in UEXX00
	 *
	 * Since we only create minor devices for existing slots on a
	 * particular system, we don't need to worry about non-exist slot.
	 */

	instance = GETINSTANCE(dev);
	slot = GETSLOT(dev);

	/* Is the instance attached? */
	if ((softsp = GETSOFTC(instance)) == NULL) {
		cmn_err(CE_WARN, "sysctrl%d device not attached", instance);
		return (ENXIO);
	}

	/* verify that otyp is appropriate */
	if (otyp != OTYP_CHR) {
		return (EINVAL);
	}

	if (!fhc_bd_valid(slot))
		return (ENXIO);

	/*
	 * On first open of a sysctrl minor walk immediate children of the
	 * devinfo root node and hold all branches of interest.
	 */
	mutex_enter(&sysctrl_branch_mutex);
	if (!sysctrl_ddi_branch_init) {

		sysctrl_ddi_branch_init = 1;

		sysc_slot_info(softsp->nslots, &arg.start, &arg.limit,
		    &arg.incr);
		arg.hold = 1;

		rdip = ddi_root_node();

		ndi_devi_enter(rdip, &circ);
		ddi_walk_devs(ddi_get_child(rdip), sysctrl_hold_rele_branches,
		    &arg);
		ndi_devi_exit(rdip, circ);
	}
	mutex_exit(&sysctrl_branch_mutex);

	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
sysctrl_close(dev_t devp, int flag, int otyp, cred_t *credp)
{
	return (DDI_SUCCESS);
}

/*
 * This function will acquire the lock and set the in_transition
 * bit for the specified slot.  If the slot is being used,
 * we return FALSE; else set in_transition and return TRUE.
 */
static int
sysc_enter_transition(int slot)
{
	fhc_bd_t	*list;
	sysc_cfga_stat_t *sysc_stat_lk;
	fhc_bd_t	*glist;
	sysc_cfga_stat_t *sysc_stat_gk;

	/* mutex lock the structure */
	list = fhc_bdlist_lock(slot);
	if ((slot != -1) && (list == NULL)) {
		fhc_bdlist_unlock();
		return (FALSE);
	}

	glist = fhc_bd_clock();
	if (slot == -1)
		list = glist;

	/* change the in_transition bit */
	sysc_stat_lk = &list->sc;
	sysc_stat_gk = &glist->sc;
	if ((sysc_stat_lk->in_transition == TRUE) ||
	    (sysc_stat_gk->in_transition == TRUE)) {
		fhc_bdlist_unlock();
		return (FALSE);
	} else {
		sysc_stat_lk->in_transition = TRUE;
		return (TRUE);
	}
}

/*
 * This function will release the lock and clear the in_transition
 * bit for the specified slot.
 */
static void
sysc_exit_transition(int slot)
{
	fhc_bd_t	*list;
	sysc_cfga_stat_t *sysc_stat_lk;

	ASSERT(fhc_bdlist_locked());

	if (slot == -1)
		list = fhc_bd_clock();
	else
		list = fhc_bd(slot);
	sysc_stat_lk = &list->sc;
	ASSERT(sysc_stat_lk->in_transition == TRUE);
	sysc_stat_lk->in_transition = FALSE;
	fhc_bdlist_unlock();
}

static int
sysc_pkt_init(sysc_cfga_pkt_t *pkt, intptr_t arg, int flag)
{
#ifdef _MULTI_DATAMODEL
	if (ddi_model_convert_from(flag & FMODELS) == DDI_MODEL_ILP32) {
		sysc_cfga_cmd32_t sysc_cmd32;

		if (ddi_copyin((void *)arg, &sysc_cmd32,
			sizeof (sysc_cfga_cmd32_t), flag) != 0) {
			return (EFAULT);
		}
		pkt->cmd_cfga.force = sysc_cmd32.force;
		pkt->cmd_cfga.test = sysc_cmd32.test;
		pkt->cmd_cfga.arg = sysc_cmd32.arg;
		pkt->cmd_cfga.errtype = sysc_cmd32.errtype;
		pkt->cmd_cfga.outputstr =
			(char *)(uintptr_t)sysc_cmd32.outputstr;
	} else
#endif /* _MULTI_DATAMODEL */
	if (ddi_copyin((void *)arg, &(pkt->cmd_cfga),
		sizeof (sysc_cfga_cmd_t), flag) != 0) {
		return (EFAULT);
	}
	pkt->errbuf = kmem_zalloc(SYSC_OUTPUT_LEN, KM_SLEEP);
	return (0);
}

static int
sysc_pkt_fini(sysc_cfga_pkt_t *pkt, intptr_t arg, int flag)
{
	int ret = TRUE;

#ifdef _MULTI_DATAMODEL
	if (ddi_model_convert_from(flag & FMODELS) == DDI_MODEL_ILP32) {

		if (ddi_copyout(&(pkt->cmd_cfga.errtype),
			(void *)&(((sysc_cfga_cmd32_t *)arg)->errtype),
			sizeof (sysc_err_t), flag) != 0) {
			ret = FALSE;
		}
	} else
#endif
	if (ddi_copyout(&(pkt->cmd_cfga.errtype),
		(void *)&(((sysc_cfga_cmd_t *)arg)->errtype),
		sizeof (sysc_err_t), flag) != 0) {
		ret = FALSE;
	}

	if ((ret != FALSE) && ((pkt->cmd_cfga.outputstr != NULL) &&
		(ddi_copyout(pkt->errbuf, pkt->cmd_cfga.outputstr,
			SYSC_OUTPUT_LEN, flag) != 0))) {
			ret = FALSE;
	}

	kmem_free(pkt->errbuf, SYSC_OUTPUT_LEN);
	return (ret);
}

/* ARGSUSED */
static int
sysctrl_ioctl(dev_t devt, int cmd, intptr_t arg, int flag, cred_t *cred_p,
		int *rval_p)
{
	struct sysctrl_soft_state *softsp;
	sysc_cfga_pkt_t sysc_pkt;
	fhc_bd_t *fhc_list = NULL;
	sysc_cfga_stat_t *sc_list = NULL;
	fhc_bd_t *bdp;
	sysc_cfga_stat_t *sc = NULL;
	int instance;
	int slot;
	int retval = 0;
	int i;

	instance = GETINSTANCE(devt);
	softsp = GETSOFTC(instance);
	if (softsp == NULL) {
		cmn_err(CE_CONT,
			"sysctrl_ioctl(%d): NULL softstate ptr!\n",
			(int)GETSLOT(devt));
		return (ENXIO);
	}

	slot = GETSLOT(devt);

	/*
	 * First switch is to do correct locking and do ddi_copyin()
	 */
	switch (cmd) {
	case SYSC_CFGA_CMD_GETSTATUS:
		/* mutex lock the whole list */
		if (sysc_enter_transition(-1) != TRUE) {
			retval = EBUSY;
			goto cleanup_exit;
		}

		/* allocate the memory before acquiring mutex */
		fhc_list = kmem_zalloc(sizeof (fhc_bd_t) * fhc_max_boards(),
		    KM_SLEEP);

		sc_list = kmem_zalloc(sizeof (sysc_cfga_stat_t) *
		    fhc_max_boards(), KM_SLEEP);

		break;

	case SYSC_CFGA_CMD_EJECT:
	case SYSC_CFGA_CMD_INSERT:
		retval = ENOTSUP;
		goto cleanup_exit;

	case SYSC_CFGA_CMD_CONNECT:
	case SYSC_CFGA_CMD_DISCONNECT:
	case SYSC_CFGA_CMD_UNCONFIGURE:
	case SYSC_CFGA_CMD_CONFIGURE:
	case SYSC_CFGA_CMD_TEST:
	case SYSC_CFGA_CMD_TEST_SET_COND:
	case SYSC_CFGA_CMD_QUIESCE_TEST:

		/* ioctls allowed if caller has write permission */
		if (!(flag & FWRITE)) {
			retval = EPERM;
			goto cleanup_exit;
		}

		retval = sysc_pkt_init(&sysc_pkt, arg, flag);
		if (retval != 0)
			goto cleanup_exit;

		/* grasp lock and set in_transition bit */
		if (sysc_enter_transition(cmd == SYSC_CFGA_CMD_QUIESCE_TEST
				? -1 : slot) != TRUE) {
			retval = EBUSY;
			SYSC_ERR_SET(&sysc_pkt, SYSC_ERR_INTRANS);
			goto cleanup_copyout;
		}

		/* get the status structure for the slot */
		bdp = fhc_bd(slot);
		sc = &bdp->sc;
		break;

	/* POSIX definition: return ENOTTY if unsupported command */
	default:
		retval = ENOTTY;
		goto cleanup_exit;
	}

	/*
	 * Second switch is to call the underlayer workhorse.
	 */
	switch (cmd) {
	case SYSC_CFGA_CMD_GETSTATUS:
		for (i = 0; i < fhc_max_boards(); i++) {
			if (fhc_bd_valid(i)) {
				bdp = fhc_bd(i);
				if (fhc_bd_is_jtag_master(i))
					bdp->sc.no_detach = 1;
				else
					bdp->sc.no_detach = 0;
				bcopy((caddr_t)&bdp->sc,
					&sc_list[i], sizeof (sysc_cfga_stat_t));
			} else {
				sc_list[i].board = -1;
				sc_list[i].rstate = SYSC_CFGA_RSTATE_EMPTY;
			}
		}

		sysc_exit_transition(-1);

		break;

	case SYSC_CFGA_CMD_EJECT:
	case SYSC_CFGA_CMD_INSERT:
		retval = ENOTSUP;
		goto cleanup_exit;

	case SYSC_CFGA_CMD_CONNECT:
		retval = sysc_policy_connect(softsp, &sysc_pkt, sc);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_DISCONNECT:
		retval = sysc_policy_disconnect(softsp, &sysc_pkt, sc);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_UNCONFIGURE:
		retval = sysc_policy_unconfigure(softsp, &sysc_pkt, sc);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_CONFIGURE:
		retval = sysc_policy_configure(softsp, &sysc_pkt, sc);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_TEST:
		retval = fhc_bd_test(slot, &sysc_pkt);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_TEST_SET_COND:
		retval = fhc_bd_test_set_cond(slot, &sysc_pkt);
		sysc_exit_transition(slot);
		break;

	case SYSC_CFGA_CMD_QUIESCE_TEST:
		sysctrl_suspend_prepare();
		fhc_bdlist_unlock();

		if (sysctrl_suspend(&sysc_pkt) == DDI_SUCCESS) {
			sysctrl_resume(&sysc_pkt);
		} else {
			retval = EBUSY;
		}

		(void) fhc_bdlist_lock(-1);
		sysc_exit_transition(-1);
		break;

	default:
		retval = ENOTTY;
		goto cleanup_exit;
	}

cleanup_copyout:
	/*
	 * 3rd switch is to do appropriate copyout and reset locks
	 */
	switch (cmd) {
	case SYSC_CFGA_CMD_GETSTATUS:
		if (ddi_copyout(sc_list, (void *)arg,
			sizeof (sysc_cfga_stat_t) * fhc_max_boards(),
			    flag) != 0) {
			retval = EFAULT;
		}

		/* cleanup memory */
		kmem_free(fhc_list, sizeof (fhc_bd_t) * fhc_max_boards());
		kmem_free(sc_list, sizeof (sysc_cfga_stat_t) *
		    fhc_max_boards());
		break;

	case SYSC_CFGA_CMD_EJECT:
	case SYSC_CFGA_CMD_INSERT:
		retval = ENOTSUP;
		break;

	case SYSC_CFGA_CMD_CONNECT:
	case SYSC_CFGA_CMD_DISCONNECT:
	case SYSC_CFGA_CMD_UNCONFIGURE:
	case SYSC_CFGA_CMD_CONFIGURE:
	case SYSC_CFGA_CMD_TEST:
	case SYSC_CFGA_CMD_TEST_SET_COND:
	case SYSC_CFGA_CMD_QUIESCE_TEST:
		if (sysc_pkt_fini(&sysc_pkt, arg, flag) != TRUE)
			return (EFAULT);
		break;

	default:
		retval = ENOTTY;
		break;
	}

cleanup_exit:
	return (retval);
}

/*
 * system_high_handler()
 * This routine handles system interrupts.
 *
 * This routine goes through all the interrupt sources and masks
 * off the enable bit if interrupting.  Because of the special
 * nature of the pps fan source bits, we also cache the state
 * of the fan bits for that special case.
 *
 * The rest of the work is done in the low level handlers
 */
static uint_t
system_high_handler(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t csr;
	uchar_t status2;
	uchar_t tmp_reg;
	int serviced = 0;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);

	/* read in the hardware registers */
	csr = *(softsp->csr);
	status2 = *(softsp->status2);

	if (csr & SYS_AC_PWR_FAIL_EN) {
		if (status2 & SYS_AC_FAIL) {

			/* save the powerfail state in nvram */
			nvram_update_powerfail(softsp);

			/* disable this interrupt source */
			csr &= ~SYS_AC_PWR_FAIL_EN;

			ddi_trigger_softintr(softsp->ac_fail_id);
			serviced++;
		}
	}

	if (csr & SYS_PS_FAIL_EN) {
		if ((*(softsp->ps_stat) != 0xff) ||
		    ((~status2) & (SYS_PPS0_OK | SYS_CLK_33_OK |
			SYS_CLK_50_OK)) ||
		    (~(*(softsp->pppsr)) & SYS_PPPSR_BITS)) {

			/* disable this interrupt source */
			csr &= ~SYS_PS_FAIL_EN;

			ddi_trigger_softintr(softsp->ps_fail_int_id);
			serviced++;
		}
	}

	if (csr & SYS_PPS_FAN_FAIL_EN) {
		if (status2 & SYS_RACK_FANFAIL ||
		    !(status2 & SYS_AC_FAN_OK) ||
		    !(status2 & SYS_KEYSW_FAN_OK)) {

			/*
			 * we must cache the fan status because it goes
			 * away when we disable interrupts !?!?!
			 */
			softsp->pps_fan_saved = status2;

			/* disable this interrupt source */
			csr &= ~SYS_PPS_FAN_FAIL_EN;

			ddi_trigger_softintr(softsp->pps_fan_id);
			serviced++;
		}
	}

	if (csr & SYS_SBRD_PRES_EN) {
		if (!(*(softsp->status1) & SYS_NOT_BRD_PRES)) {

			/* disable this interrupt source */
			csr &= ~SYS_SBRD_PRES_EN;

			ddi_trigger_softintr(softsp->sbrd_pres_id);
			serviced++;
		}
	}

	if (!serviced) {

		/*
		 * if we get here than it is likely that contact bounce
		 * is messing with us.  so, we need to shut this interrupt
		 * up for a while to let the contacts settle down.
		 * Then we will re-enable the interrupts that are enabled
		 * right now.  The trick is to disable the appropriate
		 * interrupts and then to re-enable them correctly, even
		 * though intervening handlers might have been working.
		 */

		/* remember all interrupts that could have caused it */
		softsp->saved_en_state |= csr &
		    (SYS_AC_PWR_FAIL_EN | SYS_PS_FAIL_EN |
		    SYS_PPS_FAN_FAIL_EN | SYS_SBRD_PRES_EN);

		/* and then turn them off */
		csr &= ~(SYS_AC_PWR_FAIL_EN | SYS_PS_FAIL_EN |
			SYS_PPS_FAN_FAIL_EN | SYS_SBRD_PRES_EN);

		/* and then bump the counter */
		softsp->spur_count++;

		/* and kick off the timeout */
		ddi_trigger_softintr(softsp->spur_id);
	}

	/* update the real csr */
	*(softsp->csr) = csr;
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif
	mutex_exit(&softsp->csr_mutex);

	return (DDI_INTR_CLAIMED);
}

/*
 * we've detected a spurious interrupt.
 * determine if we should log a message and if we need another timeout
 */
static uint_t
spur_delay(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;

	ASSERT(softsp);

	/* do we need to complain? */
	mutex_enter(&softsp->csr_mutex);

	/* NOTE: this is == because we want one message per long timeout */
	if (softsp->spur_count == MAX_SPUR_COUNT) {
		char buf[128];

		/* print out the candidates known at this time */
		/* XXX not perfect because of re-entrant nature but close */
		buf[0] = '\0';
		if (softsp->saved_en_state & SYS_AC_PWR_FAIL_EN)
			(void) strcat(buf, "AC FAIL");
		if (softsp->saved_en_state & SYS_PPS_FAN_FAIL_EN)
			(void) strcat(buf, buf[0] ? "|PPS FANS" : "PPS FANS");
		if (softsp->saved_en_state & SYS_PS_FAIL_EN)
			(void) strcat(buf, buf[0] ? "|PS FAIL" : "PS FAIL");
		if (softsp->saved_en_state & SYS_SBRD_PRES_EN)
			(void) strcat(buf,
				buf[0] ? "|BOARD INSERT" : "BOARD INSERT");

		/*
		 * This is a high level mutex, therefore it needs to be
		 * dropped before calling cmn_err.
		 */
		mutex_exit(&softsp->csr_mutex);

		cmn_err(CE_WARN, "sysctrl%d: unserviced interrupt."
				" possible sources [%s].",
				ddi_get_instance(softsp->dip), buf);
	} else
		mutex_exit(&softsp->csr_mutex);

	mutex_enter(&softsp->spur_int_lock);

	/* do we need to start the short timeout? */
	if (softsp->spur_timeout_id == 0) {
		softsp->spur_timeout_id = timeout(spur_retry, softsp,
		    spur_timeout_hz);
	}

	/* do we need to start the long timeout? */
	if (softsp->spur_long_timeout_id == 0) {
		softsp->spur_long_timeout_id = timeout(spur_long_timeout,
		    softsp, spur_long_timeout_hz);
	}

	mutex_exit(&softsp->spur_int_lock);

	return (DDI_INTR_CLAIMED);
}

/*
 * spur_retry
 *
 * this routine simply triggers the interrupt which will re-enable
 * the interrupts disabled by the spurious int detection.
 */
static void
spur_retry(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;

	ASSERT(softsp);

	ddi_trigger_softintr(softsp->spur_high_id);

	mutex_enter(&softsp->spur_int_lock);
	softsp->spur_timeout_id = 0;
	mutex_exit(&softsp->spur_int_lock);
}

/*
 * spur_reenable
 *
 * OK, we've been slient for a while.   Go ahead and re-enable the
 * interrupts that were enabled at the time of the spurious detection.
 */
static uint_t
spur_reenable(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t tmp_reg;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);

	/* reenable those who were spurious candidates */
	*(softsp->csr) |= softsp->saved_en_state &
		(SYS_AC_PWR_FAIL_EN | SYS_PS_FAIL_EN |
		SYS_PPS_FAN_FAIL_EN | SYS_SBRD_PRES_EN);
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif

	/* clear out the saved state */
	softsp->saved_en_state = 0;

	mutex_exit(&softsp->csr_mutex);

	return (DDI_INTR_CLAIMED);
}

/*
 * spur_long_timeout
 *
 * this routine merely resets the spurious interrupt counter thus ending
 * the interval of interest.  of course this is done by triggering a
 * softint because the counter is protected by an interrupt mutex.
 */
static void
spur_long_timeout(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;

	ASSERT(softsp);

	ddi_trigger_softintr(softsp->spur_long_to_id);

	mutex_enter(&softsp->spur_int_lock);
	softsp->spur_long_timeout_id = 0;
	mutex_exit(&softsp->spur_int_lock);
}

/*
 * spur_clear_count
 *
 * simply clear out the spurious interrupt counter.
 *
 * softint level only
 */
static uint_t
spur_clear_count(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);
	softsp->spur_count = 0;
	mutex_exit(&softsp->csr_mutex);

	return (DDI_INTR_CLAIMED);
}

/*
 * ac_fail_handler
 *
 * This routine polls the AC power failure bit in the system status2
 * register.  If we get to this routine, then we sensed an ac fail
 * condition.  Note the fact and check again in a few.
 *
 * Called as softint from high interrupt.
 */
static uint_t
ac_fail_handler(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;

	ASSERT(softsp);

	cmn_err(CE_WARN, "%s failure detected", ft_str_table[FT_AC_PWR]);
	reg_fault(0, FT_AC_PWR, FT_SYSTEM);
	(void) timeout(ac_fail_retry, softsp, ac_timeout_hz);

	return (DDI_INTR_CLAIMED);
}

/*
 * The timeout from ac_fail_handler() that checks to see if the
 * condition persists.
 */
static void
ac_fail_retry(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;

	ASSERT(softsp);

	if (*softsp->status2 & SYS_AC_FAIL) {	/* still bad? */
		(void) timeout(ac_fail_retry, softsp, ac_timeout_hz);
	} else {
		cmn_err(CE_NOTE, "%s failure no longer detected",
			ft_str_table[FT_AC_PWR]);
		clear_fault(0, FT_AC_PWR, FT_SYSTEM);
		ddi_trigger_softintr(softsp->ac_fail_high_id);
	}
}

/*
 * The interrupt routine that we use to re-enable the interrupt.
 * Called from ddi_trigger_softint() in the ac_fail_retry() when
 * the AC is better.
 */
static uint_t
ac_fail_reenable(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t tmp_reg;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);
	*(softsp->csr) |= SYS_AC_PWR_FAIL_EN;
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif
	mutex_exit(&softsp->csr_mutex);

	return (DDI_INTR_CLAIMED);
}

/*
 * ps_fail_int_handler
 *
 * Handle power supply failure interrupt.
 *
 * This wrapper is called as softint from hardware interrupt routine.
 */
static uint_t
ps_fail_int_handler(caddr_t arg)
{
	return (ps_fail_handler((struct sysctrl_soft_state *)arg, 1));
}

/*
 * ps_fail_poll_handler
 *
 * Handle power supply failure interrupt.
 *
 * This wrapper is called as softint from power supply poll routine.
 */
static uint_t
ps_fail_poll_handler(caddr_t arg)
{
	return (ps_fail_handler((struct sysctrl_soft_state *)arg, 0));
}

/*
 * ps_fail_handler
 *
 * This routine checks all eight of the board power supplies that are
 * installed plus the Peripheral power supply and the two DC OK. Since the
 * hardware bits are not enough to indicate Power Supply failure
 * vs. being turned off via software, the driver must maintain a
 * shadow state for the Power Supply status and monitor all changes.
 *
 * Called as a softint only.
 */
static uint_t
ps_fail_handler(struct sysctrl_soft_state *softsp, int fromint)
{
	int i;
	struct ps_state *pstatp;
	int poll_needed = 0;
	uchar_t ps_stat, ps_pres, status1, status2, pppsr;
	uchar_t tmp_reg;
	enum power_state current_power_state;

	ASSERT(softsp);

	/* pre-read the hardware state */
	ps_stat = *softsp->ps_stat;
	ps_pres = *softsp->ps_pres;
	status1 = *softsp->status1;
	status2 = *softsp->status2;
	pppsr	= *softsp->pppsr;

	(void) fhc_bdlist_lock(-1);

	mutex_enter(&softsp->ps_fail_lock);

	for (i = 0, pstatp = &softsp->ps_stats[0]; i < SYS_PS_COUNT;
	    i++, pstatp++) {
		int	temp_psok;
		int	temp_pres;
		int	is_precharge = FALSE;
		int	is_fan_assy = FALSE;

		/*
		 * pre-compute the presence and ok bits for this
		 * power supply from the hardware registers.
		 * NOTE: 4-slot pps1 is the same as core ps 7...
		 */
		switch (i) {
		/* the core power supplies */
		case 0: case 1: case 2: case 3:
		case 4: case 5: case 6: case 7:
			temp_pres = !((ps_pres >> i) & 0x1);
			temp_psok = (ps_stat >> i) & 0x1;
			break;

		/* the first peripheral power supply */
		case SYS_PPS0_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = status2 & SYS_PPS0_OK;
			break;

		/* shared 3.3v clock power */
		case SYS_CLK_33_INDEX:
			temp_pres = TRUE;
			temp_psok = status2 & SYS_CLK_33_OK;
			break;

		/* shared 5.0v clock power */
		case SYS_CLK_50_INDEX:
			temp_pres = TRUE;
			temp_psok = status2 & SYS_CLK_50_OK;
			break;

		/* peripheral 5v */
		case SYS_V5_P_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES) ||
				((IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) &&
				!(ps_pres & SYS_NOT_PPS1_PRES));
			temp_psok = pppsr & SYS_V5_P_OK;
			break;

		/* peripheral 12v */
		case SYS_V12_P_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES) ||
				((IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) &&
				!(ps_pres & SYS_NOT_PPS1_PRES));
			temp_psok = pppsr & SYS_V12_P_OK;
			break;

		/* aux 5v */
		case SYS_V5_AUX_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = pppsr & SYS_V5_AUX_OK;
			break;

		/* peripheral 5v precharge */
		case SYS_V5_P_PCH_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = pppsr & SYS_V5_P_PCH_OK;
			is_precharge = TRUE;
			break;

		/* peripheral 12v precharge */
		case SYS_V12_P_PCH_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = pppsr & SYS_V12_P_PCH_OK;
			is_precharge = TRUE;
			break;

		/* 3.3v precharge */
		case SYS_V3_PCH_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = pppsr & SYS_V3_PCH_OK;
			is_precharge = TRUE;
			break;

		/* 5v precharge */
		case SYS_V5_PCH_INDEX:
			temp_pres = !(status1 & SYS_NOT_PPS0_PRES);
			temp_psok = pppsr & SYS_V5_PCH_OK;
			is_precharge = TRUE;
			break;

		/* peripheral fan assy */
		case SYS_P_FAN_INDEX:
			temp_pres = (IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) &&
				!(status1 & SYS_NOT_P_FAN_PRES);
			temp_psok = softsp->pps_fan_saved &
				SYS_AC_FAN_OK;
			is_fan_assy = TRUE;
			break;
		}

		/* *** Phase 1 -- power supply presence tests *** */

		/* do we know the presence status for this power supply? */
		if (pstatp->pshadow == PRES_UNKNOWN) {
			pstatp->pshadow = temp_pres ? PRES_IN : PRES_OUT;
			pstatp->dcshadow = temp_pres ? PS_BOOT : PS_OUT;
		} else {
			/* has the ps presence state changed? */
			if (!temp_pres ^ (pstatp->pshadow == PRES_IN)) {
				pstatp->pctr = 0;
			} else {
				/* a change! are we counting? */
				if (pstatp->pctr == 0) {
					pstatp->pctr = PS_PRES_CHANGE_TICKS;
				} else if (--pstatp->pctr == 0) {
					pstatp->pshadow = temp_pres ?
						PRES_IN : PRES_OUT;
					pstatp->dcshadow = temp_pres ?
						PS_UNKNOWN : PS_OUT;

					/*
					 * Now we know the state has
					 * changed, so we should log it.
					 */
					ps_log_pres_change(softsp,
						i, temp_pres);
				}
			}
		}

		/* *** Phase 2 -- power supply status tests *** */

		/* check if the Power Supply is removed or same as before */
		if ((pstatp->dcshadow == PS_OUT) ||
		    ((pstatp->dcshadow == PS_OK) && temp_psok) ||
		    ((pstatp->dcshadow == PS_FAIL) && !temp_psok)) {
			pstatp->dcctr = 0;
		} else {

			/* OK, a change, do we start the timer? */
			if (pstatp->dcctr == 0) {
				switch (pstatp->dcshadow) {
				case PS_BOOT:
					pstatp->dcctr = PS_FROM_BOOT_TICKS;
					break;

				case PS_UNKNOWN:
					pstatp->dcctr = is_fan_assy ?
						PS_P_FAN_FROM_UNKNOWN_TICKS :
						PS_FROM_UNKNOWN_TICKS;
					break;

				case PS_OK:
					pstatp->dcctr = is_precharge ?
						PS_PCH_FROM_OK_TICKS :
						PS_FROM_OK_TICKS;
					break;

				case PS_FAIL:
					pstatp->dcctr = PS_FROM_FAIL_TICKS;
					break;

				default:
					panic("sysctrl%d: Unknown Power "
					    "Supply State %d", pstatp->dcshadow,
					    ddi_get_instance(softsp->dip));
				}
			}

			/* has the ticker expired? */
			if (--pstatp->dcctr == 0) {

				/* we'll skip OK messages during boot */
				if (!((pstatp->dcshadow == PS_BOOT) &&
				    temp_psok)) {
					ps_log_state_change(softsp,
						i, temp_psok);
				}

				/*
				 * remote console interface has to be
				 * reinitialized on the rising edge V5_AUX
				 * when it is NOT boot. At the boot time an
				 * an error condition exists if it was not
				 * enabled before.
				 */
				if ((i == SYS_V5_AUX_INDEX) &&
				    (pstatp->dcshadow != PS_BOOT) &&
				    (softsp->enable_rcons_atboot)) {
					if (temp_psok)
						rcons_reinit(softsp);
					else
						/* disable rconsole */
						*(softsp->clk_freq2) &=
						    ~RCONS_UART_EN;
					tmp_reg = *(softsp->csr);
#ifdef lint
					tmp_reg = tmp_reg;
#endif

				}

				/* regardless, update the shadow state */
				pstatp->dcshadow = temp_psok ? PS_OK : PS_FAIL;

				/* always update board condition */
				sysc_policy_update(softsp, NULL,
					SYSC_EVT_BD_PS_CHANGE);

			}
		}

		/*
		 * We will need to continue polling for three reasons:
		 * - a failing power supply is detected and we haven't yet
		 *   determined the power supplies existence.
		 * - the power supply is just installed and we're waiting
		 *   to give it a change to power up,
		 * - a failed power supply state is recognized
		 *
		 * NOTE: PS_FAIL shadow state is not the same as !temp_psok
		 * because of the persistence of PS_FAIL->PS_OK.
		 */
		if (!temp_psok ||
		    (pstatp->dcshadow == PS_UNKNOWN) ||
		    (pstatp->dcshadow == PS_FAIL)) {
			poll_needed++;
		}
	}

	/*
	 * Now, get the current power state for this instance.
	 * If the current state is different than what was known, complain.
	 */
	current_power_state = compute_power_state(softsp, 0);

	if (softsp->power_state != current_power_state) {
		switch (current_power_state) {
		case BELOW_MINIMUM:
			cmn_err(CE_WARN,
				"Insufficient power available to system");
			if (!disable_insufficient_power_reboot) {
				cmn_err(CE_WARN, "System reboot in %d seconds",
					PS_INSUFFICIENT_COUNTDOWN_SEC);
			}
			reg_fault(1, FT_INSUFFICIENT_POWER, FT_SYSTEM);
			softsp->power_countdown = PS_POWER_COUNTDOWN_TICKS;
			break;

		case MINIMUM:
			/* If we came from REDUNDANT, complain */
			if (softsp->power_state == REDUNDANT) {
				cmn_err(CE_WARN, "Redundant power lost");
			/* If we came from BELOW_MINIMUM, hurrah! */
			} else if (softsp->power_state == BELOW_MINIMUM) {
				cmn_err(CE_NOTE, "Minimum power available");
				clear_fault(1, FT_INSUFFICIENT_POWER,
					FT_SYSTEM);
			}
			break;

		case REDUNDANT:
			/* If we aren't from boot, spread the good news */
			if (softsp->power_state != BOOT) {
				cmn_err(CE_NOTE, "Redundant power available");
				clear_fault(1, FT_INSUFFICIENT_POWER,
					FT_SYSTEM);
			}
			break;

		default:
			break;
		}
		softsp->power_state = current_power_state;
		sysc_policy_update(softsp, NULL, SYSC_EVT_BD_PS_CHANGE);
	}

	mutex_exit(&softsp->ps_fail_lock);

	fhc_bdlist_unlock();

	/*
	 * Are we in insufficient powerstate?
	 * If so, is it time to take action?
	 */
	if (softsp->power_state == BELOW_MINIMUM &&
	    softsp->power_countdown > 0 && --(softsp->power_countdown) == 0 &&
	    !disable_insufficient_power_reboot) {
		cmn_err(CE_WARN,
		    "Insufficient power. System Reboot Started...");

		fhc_reboot();
	}

	/*
	 * If we don't have ps problems that need to be polled for, then
	 * enable interrupts.
	 */
	if (!poll_needed) {
		mutex_enter(&softsp->csr_mutex);
		*(softsp->csr) |= SYS_PS_FAIL_EN;
		tmp_reg = *(softsp->csr);
#ifdef lint
		tmp_reg = tmp_reg;
#endif
		mutex_exit(&softsp->csr_mutex);
	}

	/*
	 * Only the polling loop re-triggers the polling loop timeout
	 */
	if (!fromint) {
		(void) timeout(ps_fail_retry, softsp, ps_fail_timeout_hz);
	}

	return (DDI_INTR_CLAIMED);
}

/*
 * Compute the current power configuration for this system.
 * Disk boards and Clock boards are not counted.
 *
 * This function must be called with the ps_fail_lock held.
 */
enum power_state
compute_power_state(struct sysctrl_soft_state *softsp, int plus_load)
{
	int i;
	int ok_supply_count = 0;
	int load_count = 0;
	int minimum_power_count;
	int pps_ok;
	fhc_bd_t *list;

	ASSERT(mutex_owned(&softsp->ps_fail_lock));

	/*
	 * Walk down the interesting power supplies and
	 * count the operational power units
	 */
	for (i = 0; i < 8; i++) {
		/*
		 * power supply id 7 on a 4 or 5 slot system is PPS1.
		 * don't include it in the redundant core power calculation.
		 */
		if (i == 7 &&
		    (IS4SLOT(softsp->nslots) || IS5SLOT(softsp->nslots)))
			continue;

		if (softsp->ps_stats[i].dcshadow == PS_OK)
			ok_supply_count++;
	}

	/* Note the state of the PPS... */
	pps_ok = (softsp->ps_stats[SYS_PPS0_INDEX].dcshadow == PS_OK);

	/*
	 * Dynamically compute the load count in the system.
	 * Don't count disk boards or boards in low power state.
	 */
	for (list = fhc_bd_first(); list; list = fhc_bd_next(list)) {
		ASSERT(list->sc.type != CLOCK_BOARD);
		if (list->sc.rstate == SYSC_CFGA_RSTATE_CONNECTED) {
			load_count++;
		}
	}

	load_count += plus_load;
	/*
	 * If we are 8 slot and we have 7 or 8 boards, then the PPS
	 * can count as a power supply...
	 */
	if (IS8SLOT(softsp->nslots) && load_count >= 7 && pps_ok)
		ok_supply_count++;

	/*
	 * This is to cover the corner case of a UE3500 having 5
	 * boards installed and still giving it N+1 power status.
	 */
	if (IS5SLOT(softsp->nslots) && (load_count >= 5))
		ok_supply_count++;

	/*
	 * Determine our power situation.  This is a simple step
	 * function right now:
	 *
	 * minimum power count = min(7, floor((board count + 1) / 2))
	 */
	minimum_power_count = (load_count + 1) / 2;
	if (minimum_power_count > 7)
		minimum_power_count = 7;

	if (ok_supply_count > minimum_power_count)
		return (REDUNDANT);
	else if (ok_supply_count == minimum_power_count)
		return (MINIMUM);
	else
		return (BELOW_MINIMUM);
}

/*
 * log the change of power supply presence
 */
static void
ps_log_pres_change(struct sysctrl_soft_state *softsp, int index, int present)
{
	char	*trans = present ? "Installed" : "Removed";

	switch (index) {
	/* the core power supplies (except for 7) */
	case 0: case 1: case 2: case 3:
	case 4: case 5: case 6:
		cmn_err(CE_NOTE, "%s %d %s", ft_str_table[FT_CORE_PS], index,
		    trans);
		if (!present) {
		    clear_fault(index, FT_CORE_PS, FT_SYSTEM);
		    sysc_policy_update(softsp, NULL, SYSC_EVT_BD_PS_CHANGE);
		}
		break;

	/* power supply 7 / pps 1 */
	case 7:
		if (IS4SLOT(softsp->nslots) || IS5SLOT(softsp->nslots)) {
		    cmn_err(CE_NOTE, "%s 1 %s", ft_str_table[FT_PPS], trans);
		    if (!present) {
			clear_fault(1, FT_PPS, FT_SYSTEM);
		    }
		} else {
		    cmn_err(CE_NOTE, "%s %d %s", ft_str_table[FT_CORE_PS],
			index, trans);
		    if (!present) {
			clear_fault(7, FT_CORE_PS, FT_SYSTEM);
			sysc_policy_update(softsp, NULL, SYSC_EVT_BD_PS_CHANGE);
		    }
		}
		break;

	/* the peripheral power supply 0 */
	case SYS_PPS0_INDEX:
		cmn_err(CE_NOTE, "%s 0 %s", ft_str_table[FT_PPS], trans);
		if (!present) {
			clear_fault(0, FT_PPS, FT_SYSTEM);
			sysc_policy_update(softsp, NULL, SYSC_EVT_BD_PS_CHANGE);
		}
		break;

	/* the peripheral rack fan assy */
	case SYS_P_FAN_INDEX:
		cmn_err(CE_NOTE, "%s %s", ft_str_table[FT_PPS_FAN], trans);
		if (!present) {
			clear_fault(0, FT_PPS_FAN, FT_SYSTEM);
		}
		break;

	/* we don't mention a change of presence state for any other power */
	}
}

/*
 * log the change of power supply status
 */
static void
ps_log_state_change(struct sysctrl_soft_state *softsp, int index, int ps_ok)
{
	int level = ps_ok ? CE_NOTE : CE_WARN;
	char *s = ps_ok ? "OK" : "Failing";

	switch (index) {
	/* the core power supplies (except 7) */
	case 0: case 1: case 2: case 3:
	case 4: case 5: case 6:
		cmn_err(level, "%s %d %s", ft_str_table[FT_CORE_PS], index, s);
		if (ps_ok) {
			clear_fault(index, FT_CORE_PS, FT_SYSTEM);
		} else {
			reg_fault(index, FT_CORE_PS, FT_SYSTEM);
		}
		break;

	/* power supply 7 / pps 1 */
	case 7:
		if (IS4SLOT(softsp->nslots) || IS5SLOT(softsp->nslots)) {
			cmn_err(level, "%s 1 %s", ft_str_table[FT_PPS], s);
			if (ps_ok) {
				clear_fault(1, FT_PPS, FT_SYSTEM);
			} else {
				reg_fault(1, FT_PPS, FT_SYSTEM);
			}
		} else {
			cmn_err(level, "%s %d %s", ft_str_table[FT_CORE_PS],
				index, s);
			if (ps_ok) {
				clear_fault(index, FT_CORE_PS, FT_SYSTEM);
			} else {
				reg_fault(index, FT_CORE_PS, FT_SYSTEM);
			}
		}
		break;

	/* the peripheral power supply */
	case SYS_PPS0_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_PPS], s);
		if (ps_ok) {
			clear_fault(0, FT_PPS, FT_SYSTEM);
		} else {
			reg_fault(0, FT_PPS, FT_SYSTEM);
		}
		break;

	/* shared 3.3v clock power */
	case SYS_CLK_33_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_CLK_33], s);
		if (ps_ok) {
			clear_fault(0, FT_CLK_33, FT_SYSTEM);
		} else {
			reg_fault(0, FT_CLK_33, FT_SYSTEM);
		}
		break;

	/* shared 5.0v clock power */
	case SYS_CLK_50_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_CLK_50], s);
		if (ps_ok) {
			clear_fault(0, FT_CLK_50, FT_SYSTEM);
		} else {
			reg_fault(0, FT_CLK_50, FT_SYSTEM);
		}
		break;

	/* peripheral 5v */
	case SYS_V5_P_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V5_P], s);
		if (ps_ok) {
			clear_fault(0, FT_V5_P, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V5_P, FT_SYSTEM);
		}
		break;

	/* peripheral 12v */
	case SYS_V12_P_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V12_P], s);
		if (ps_ok) {
			clear_fault(0, FT_V12_P, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V12_P, FT_SYSTEM);
		}
		break;

	/* aux 5v */
	case SYS_V5_AUX_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V5_AUX], s);
		if (ps_ok) {
			clear_fault(0, FT_V5_AUX, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V5_AUX, FT_SYSTEM);
		}
		break;

	/* peripheral 5v precharge */
	case SYS_V5_P_PCH_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V5_P_PCH], s);
		if (ps_ok) {
			clear_fault(0, FT_V5_P_PCH, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V5_P_PCH, FT_SYSTEM);
		}
		break;

	/* peripheral 12v precharge */
	case SYS_V12_P_PCH_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V12_P_PCH], s);
		if (ps_ok) {
			clear_fault(0, FT_V12_P_PCH, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V12_P_PCH, FT_SYSTEM);
		}
		break;

	/* 3.3v precharge */
	case SYS_V3_PCH_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V3_PCH], s);
		if (ps_ok) {
			clear_fault(0, FT_V3_PCH, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V3_PCH, FT_SYSTEM);
		}
		break;

	/* 5v precharge */
	case SYS_V5_PCH_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_V5_PCH], s);
		if (ps_ok) {
			clear_fault(0, FT_V5_PCH, FT_SYSTEM);
		} else {
			reg_fault(0, FT_V5_PCH, FT_SYSTEM);
		}
		break;

	/* peripheral power supply fans */
	case SYS_P_FAN_INDEX:
		cmn_err(level, "%s %s", ft_str_table[FT_PPS_FAN], s);
		if (ps_ok) {
			clear_fault(0, FT_PPS_FAN, FT_SYSTEM);
		} else {
			reg_fault(0, FT_PPS_FAN, FT_SYSTEM);
		}
		break;
	}
}

/*
 * The timeout from ps_fail_handler() that simply re-triggers a check
 * of the ps condition.
 */
static void
ps_fail_retry(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;

	ASSERT(softsp);

	ddi_trigger_softintr(softsp->ps_fail_poll_id);
}

/*
 * pps_fanfail_handler
 *
 * This routine is called from the high level handler.
 */
static uint_t
pps_fanfail_handler(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;

	ASSERT(softsp);

	/* always check again in a bit by re-enabling the fan interrupt */
	(void) timeout(pps_fanfail_retry, softsp, pps_fan_timeout_hz);

	return (DDI_INTR_CLAIMED);
}

/*
 * After a bit of waiting, we simply re-enable the interrupt to
 * see if we get another one.  The softintr triggered routine does
 * the dirty work for us since it runs in the interrupt context.
 */
static void
pps_fanfail_retry(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;

	ASSERT(softsp);

	ddi_trigger_softintr(softsp->pps_fan_high_id);
}

/*
 * The other half of the retry handler run from the interrupt context
 */
static uint_t
pps_fanfail_reenable(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t tmp_reg;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);

	/*
	 * re-initialize the bit field for all pps fans to assumed good.
	 * If the fans are still bad, we're going to get an immediate system
	 * interrupt which will put the correct state back anyway.
	 *
	 * NOTE: the polling routines that use this state understand the
	 * pulse resulting from above...
	 */
	softsp->pps_fan_saved = SYS_AC_FAN_OK | SYS_KEYSW_FAN_OK;

	*(softsp->csr) |= SYS_PPS_FAN_FAIL_EN;
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif
	mutex_exit(&softsp->csr_mutex);

	return (DDI_INTR_CLAIMED);
}

/*
 *
 * Poll the hardware shadow state to determine the pps fan status.
 * The shadow state is maintained by the system_high handler and its
 * associated pps_* functions (above).
 *
 * There is a short time interval where the shadow state is pulsed to
 * the OK state even when the fans are bad.  However, this polling
 * routine has some built in hysteresis to filter out those _normal_
 * events.
 */
static void
pps_fan_poll(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;
	int i;

	ASSERT(softsp);

	for (i = 0; i < SYS_PPS_FAN_COUNT; i++) {
		int fanfail = FALSE;

		/* determine fan status */
		switch (i) {
		case RACK:
			fanfail = softsp->pps_fan_saved & SYS_RACK_FANFAIL;
			break;

		case AC:
			/*
			 * Don't bother polling the AC fan on 4 and 5 slot
			 * systems.
			 * Rather, it is handled by the power supply loop.
			 */
			fanfail = !(IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) &&
				!(softsp->pps_fan_saved & SYS_AC_FAN_OK);
			break;

		case KEYSW:
			/*
			 * This signal is not usable if aux5v is missing
			 * so we will synthesize a failed fan when aux5v
			 * fails or when pps0 is out.
			 * The 4 and 5 slot systems behave the same.
			 */
			fanfail = (!(IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) &&
			    (softsp->ps_stats[SYS_V5_AUX_INDEX].dcshadow !=
				PS_OK)) ||
			    !(softsp->pps_fan_saved & SYS_KEYSW_FAN_OK);
			break;

		}

		/* is the fan bad? */
		if (fanfail) {

			/* is this condition different than we know? */
			if (softsp->pps_fan_state_count[i] == 0) {

				/* log the change to failed */
				pps_fan_state_change(softsp, i, FALSE);
			}

			/* always restart the fan OK counter */
			softsp->pps_fan_state_count[i] = PPS_FROM_FAIL_TICKS;
		} else {

			/* do we currently know the fan is bad? */
			if (softsp->pps_fan_state_count[i]) {

				/* yes, but has it been stable? */
				if (--softsp->pps_fan_state_count[i] == 0) {

					/* log the change to OK */
					pps_fan_state_change(softsp, i, TRUE);
				}
			}
		}
	}

	/* always check again in a bit by re-enabling the fan interrupt */
	(void) timeout(pps_fan_poll, softsp, pps_fan_timeout_hz);
}

/*
 * pps_fan_state_change()
 *
 * Log the changed fan condition and update the external status.
 */
static void
pps_fan_state_change(struct sysctrl_soft_state *softsp, int index, int fan_ok)
{
	char *fan_type;
	char *state = fan_ok ? "fans OK" : "fan failure detected";

	switch (index) {
	case RACK:
		/* 4 and 5 slot systems behave the same */
		fan_type = (IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) ?
				"Disk Drive" : "Rack Exhaust";
		if (fan_ok) {
			softsp->pps_fan_external_state &= ~SYS_RACK_FANFAIL;
			clear_fault(0, (IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) ? FT_DSK_FAN :
				FT_RACK_EXH, FT_SYSTEM);
		} else {
			softsp->pps_fan_external_state |= SYS_RACK_FANFAIL;
			reg_fault(0, (IS4SLOT(softsp->nslots) ||
				IS5SLOT(softsp->nslots)) ? FT_DSK_FAN :
				FT_RACK_EXH, FT_SYSTEM);
		}
		break;

	case AC:
		fan_type = "AC Box";
		if (fan_ok) {
			softsp->pps_fan_external_state |= SYS_AC_FAN_OK;
			clear_fault(0, FT_AC_FAN, FT_SYSTEM);
		} else {
			softsp->pps_fan_external_state &= ~SYS_AC_FAN_OK;
			reg_fault(0, FT_AC_FAN, FT_SYSTEM);
		}
		break;

	case KEYSW:
		fan_type = "Keyswitch";
		if (fan_ok) {
			softsp->pps_fan_external_state |= SYS_KEYSW_FAN_OK;
			clear_fault(0, FT_KEYSW_FAN, FT_SYSTEM);
		} else {
			softsp->pps_fan_external_state &= ~SYS_KEYSW_FAN_OK;
			reg_fault(0, FT_KEYSW_FAN, FT_SYSTEM);
		}
		break;
	default:
		fan_type = "[invalid fan id]";
		break;
	}

	/* now log the state change */
	cmn_err(fan_ok ? CE_NOTE : CE_WARN, "%s %s", fan_type, state);
}

static uint_t
bd_insert_handler(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;

	ASSERT(softsp);

	DPRINTF(SYSCTRL_ATTACH_DEBUG, ("bd_insert_handler()"));

	(void) timeout(bd_insert_timeout, softsp, bd_insert_delay_hz);

	return (DDI_INTR_CLAIMED);
}

void
bd_remove_poll(struct sysctrl_soft_state *softsp)
{
	ASSERT(fhc_bdlist_locked());

	if (!bd_remove_to_id) {
		bd_remove_to_id = timeout(bd_remove_timeout, softsp,
						bd_remove_timeout_hz);
	} else {
		DPRINTF(SYSCTRL_ATTACH_DEBUG,
			("bd_remove_poll ignoring start request"));
	}
}

/*
 * bd_insert_timeout()
 *
 * This routine handles the board insert interrupt. It is called from a
 * timeout so that it does not run at interrupt level. The main job
 * of this routine is to find hotplugged boards and de-assert the
 * board insert interrupt coming from the board. For hotplug phase I,
 * the routine also powers down the board.
 * JTAG scan is used to find boards which have been inserted.
 * All other control of the boards is also done by JTAG scan.
 */
static void
bd_insert_timeout(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;
	int found;

	ASSERT(softsp);

	if (sysctrl_hotplug_disabled) {
		sysc_policy_update(softsp, NULL, SYSC_EVT_BD_HP_DISABLED);
	} else {
		/*
		 * Lock the board list mutex. Keep it locked until all work
		 * is done.
		 */
		(void) fhc_bdlist_lock(-1);

		found = fhc_bd_insert_scan();

		if (found) {
			DPRINTF(SYSCTRL_ATTACH_DEBUG,
			    ("bd_insert_timeout starting bd_remove_poll()"));
			bd_remove_poll(softsp);
		}

		fhc_bdlist_unlock();
	}

	/*
	 * Enable interrupts.
	 */
	ddi_trigger_softintr(softsp->sbrd_gone_id);
}

static void
bd_remove_timeout(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;
	int keep_polling;

	ASSERT(softsp);

	/*
	 * Lock the board list mutex. Keep it locked until all work
	 * is done.
	 */
	(void) fhc_bdlist_lock(-1);

	bd_remove_to_id = 0;	/* delete our timeout ID */

	keep_polling = fhc_bd_remove_scan();

	if (keep_polling) {
		bd_remove_poll(softsp);
	} else {
		DPRINTF(SYSCTRL_ATTACH_DEBUG, ("exiting bd_remove_poll."));
	}

	fhc_bdlist_unlock();
}

static uint_t
bd_insert_normal(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t tmp_reg;

	ASSERT(softsp);

	/* has the condition been removed? */
	/* XXX add deglitch state machine here */
	if (!(*(softsp->status1) & SYS_NOT_BRD_PRES)) {
		/* check again in a few */
		(void) timeout(bd_insert_timeout, softsp, bd_insert_retry_hz);
	} else {
		/* Turn on the enable bit for this interrupt */
		mutex_enter(&softsp->csr_mutex);
		*(softsp->csr) |= SYS_SBRD_PRES_EN;
		/* flush the hardware store buffer */
		tmp_reg = *(softsp->csr);
#ifdef lint
		tmp_reg = tmp_reg;
#endif
		mutex_exit(&softsp->csr_mutex);
	}

	return (DDI_INTR_CLAIMED);
}

/*
 * blink LED handler.
 *
 * The actual bit manipulation needs to occur at interrupt level
 * because we need access to the CSR with its CSR mutex
 */
static uint_t
blink_led_handler(caddr_t arg)
{
	struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)arg;
	uchar_t tmp_reg;

	ASSERT(softsp);

	mutex_enter(&softsp->csr_mutex);

	/*
	 * XXX - The lock for the sys_led is not held here. If more
	 * complicated tasks are done with the System LED, then
	 * locking should be done here.
	 */

	/* read the hardware register. */
	tmp_reg = *(softsp->csr);

	/* Only turn on the OS System LED bit if the softsp state is on. */
	if (softsp->sys_led) {
		tmp_reg |= SYS_LED_RIGHT;
	} else {
		tmp_reg &= ~SYS_LED_RIGHT;
	}

	/* Turn on the yellow LED if system fault status is set. */
	if (softsp->sys_fault) {
		tmp_reg |= SYS_LED_MID;
	} else {
		tmp_reg &= ~SYS_LED_MID;
	}

	/* write to the hardware register */
	*(softsp->csr) = tmp_reg;

	/* flush the hardware store buffer */
	tmp_reg = *(softsp->csr);
#ifdef lint
	tmp_reg = tmp_reg;
#endif
	mutex_exit(&softsp->csr_mutex);

	(void) timeout(blink_led_timeout, softsp, blink_led_timeout_hz);

	return (DDI_INTR_CLAIMED);
}

/*
 * simply re-trigger the interrupt handler on led timeout
 */
static void
blink_led_timeout(void *arg)
{
	struct sysctrl_soft_state *softsp = arg;
	int led_state;

	ASSERT(softsp);

	/*
	 * Process the system fault list here. This is where the driver
	 * must decide what yellow LEDs to turn on if any. The fault
	 * list is walked and each fhc_list entry is updated with it's
	 * yellow LED status. This info is used later by the routine
	 * toggle_board_green_leds().
	 *
	 * The variable system_fault is non-zero if any non-
	 * suppressed faults are found in the system.
	 */
	softsp->sys_fault = process_fault_list();

	/* blink the system board OS LED */
	mutex_enter(&softsp->sys_led_lock);
	softsp->sys_led = !softsp->sys_led;
	led_state = softsp->sys_led;
	mutex_exit(&softsp->sys_led_lock);

	toggle_board_green_leds(led_state);

	ddi_trigger_softintr(softsp->blink_led_id);
}

void
toggle_board_green_leds(int led_state)
{
	fhc_bd_t *list;

	(void) fhc_bdlist_lock(-1);
	for (list = fhc_bd_first(); list; list = fhc_bd_next(list)) {
		uint_t value = 0;

		if (list->sc.in_transition ||
		    (list->sc.rstate != SYSC_CFGA_RSTATE_CONNECTED))
			continue;

		ASSERT(list->sc.type != CLOCK_BOARD);
		ASSERT(list->sc.type != DISK_BOARD);
		ASSERT(list->softsp);

		if ((list->sc.ostate == SYSC_CFGA_OSTATE_CONFIGURED) &&
		    led_state)
			value |= FHC_LED_RIGHT;

		if (list->fault)
			value |= FHC_LED_MID;
		else
			value &= ~FHC_LED_MID;

		update_board_leds(list, FHC_LED_RIGHT|FHC_LED_MID, value);
	}
	fhc_bdlist_unlock();
}

/*
 * timestamp an AC power failure in nvram
 */
static void
nvram_update_powerfail(struct sysctrl_soft_state *softsp)
{
	char buf[80];
	int len = 0;

	numtos(gethrestime_sec(), buf);

	if (softsp->options_nodeid) {
		len = prom_setprop(softsp->options_nodeid, "powerfail-time",
			buf, strlen(buf)+1);
	}

	if (len <= 0) {
		cmn_err(CE_WARN, "sysctrl%d: failed to set powerfail-time "
			"to %s\n", ddi_get_instance(softsp->dip), buf);
	}
}

void
sysctrl_add_kstats(struct sysctrl_soft_state *softsp)
{
	struct kstat	*ksp;		/* Generic sysctrl kstats */
	struct kstat	*pksp;		/* Power Supply kstat */
	struct kstat	*tksp;		/* Sysctrl temperatrure kstat */
	struct kstat	*ttsp;		/* Sysctrl temperature test kstat */

	if ((ksp = kstat_create("unix", ddi_get_instance(softsp->dip),
	    SYSCTRL_KSTAT_NAME, "misc", KSTAT_TYPE_NAMED,
	    sizeof (struct sysctrl_kstat) / sizeof (kstat_named_t),
	    KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "sysctrl%d: kstat_create failed",
			ddi_get_instance(softsp->dip));
	} else {
		struct sysctrl_kstat *sysksp;

		sysksp = (struct sysctrl_kstat *)(ksp->ks_data);

		/* now init the named kstats */
		kstat_named_init(&sysksp->csr, CSR_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->status1, STAT1_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->status2, STAT2_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->clk_freq2, CLK_FREQ2_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->fan_status, FAN_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->key_status, KEY_KSTAT_NAMED,
			KSTAT_DATA_CHAR);

		kstat_named_init(&sysksp->power_state, POWER_KSTAT_NAMED,
			KSTAT_DATA_INT32);

		kstat_named_init(&sysksp->clk_ver, CLK_VER_KSTAT_NAME,
			KSTAT_DATA_CHAR);

		ksp->ks_update = sysctrl_kstat_update;
		ksp->ks_private = (void *)softsp;
		kstat_install(ksp);
	}

	if ((tksp = kstat_create("unix", CLOCK_BOARD_INDEX,
	    OVERTEMP_KSTAT_NAME, "misc", KSTAT_TYPE_RAW,
	    sizeof (struct temp_stats), KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "sysctrl%d: kstat_create failed",
			ddi_get_instance(softsp->dip));
	} else {
		tksp->ks_update = overtemp_kstat_update;
		tksp->ks_private = (void *)&softsp->tempstat;
		kstat_install(tksp);
	}

	if ((ttsp = kstat_create("unix", CLOCK_BOARD_INDEX,
	    TEMP_OVERRIDE_KSTAT_NAME, "misc", KSTAT_TYPE_RAW, sizeof (short),
		KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_WRITABLE)) == NULL) {
		cmn_err(CE_WARN, "sysctrl%d: kstat_create failed",
			ddi_get_instance(softsp->dip));
	} else {
		ttsp->ks_update = temp_override_kstat_update;
		ttsp->ks_private = (void *)&softsp->tempstat.override;
		kstat_install(ttsp);
	}

	if ((pksp = kstat_create("unix", ddi_get_instance(softsp->dip),
	    PSSHAD_KSTAT_NAME, "misc", KSTAT_TYPE_RAW,
	    SYS_PS_COUNT, KSTAT_FLAG_PERSISTENT)) == NULL) {
		cmn_err(CE_WARN, "sysctrl%d: kstat_create failed",
			ddi_get_instance(softsp->dip));
	} else {
		pksp->ks_update = psstat_kstat_update;
		pksp->ks_private = (void *)softsp;
		kstat_install(pksp);
	}
}

static int
sysctrl_kstat_update(kstat_t *ksp, int rw)
{
	struct sysctrl_kstat *sysksp;
	struct sysctrl_soft_state *softsp;

	sysksp = (struct sysctrl_kstat *)(ksp->ks_data);
	softsp = (struct sysctrl_soft_state *)(ksp->ks_private);

	/* this is a read-only kstat. Exit on a write */

	if (rw == KSTAT_WRITE) {
		return (EACCES);
	} else {
		/*
		 * copy the current state of the hardware into the
		 * kstat structure.
		 */
		sysksp->csr.value.c[0] = *(softsp->csr);
		sysksp->status1.value.c[0] = *(softsp->status1);
		sysksp->status2.value.c[0] = *(softsp->status2);
		sysksp->clk_freq2.value.c[0] = *(softsp->clk_freq2);

		sysksp->fan_status.value.c[0] = softsp->pps_fan_external_state;
		sysksp->key_status.value.c[0] = softsp->key_shadow;
		sysksp->power_state.value.i32 = softsp->power_state;

		/*
		 * non-existence of the clock version register returns the
		 * value 0xff when the hardware register location is read
		 */
		if (softsp->clk_ver != NULL)
			sysksp->clk_ver.value.c[0] = *(softsp->clk_ver);
		else
			sysksp->clk_ver.value.c[0] = (char)0xff;
	}
	return (0);
}

static int
psstat_kstat_update(kstat_t *ksp, int rw)
{
	struct sysctrl_soft_state *softsp;
	uchar_t *ptr = (uchar_t *)(ksp->ks_data);
	int ps;

	softsp = (struct sysctrl_soft_state *)(ksp->ks_private);

	if (rw == KSTAT_WRITE) {
		return (EACCES);
	} else {
		for (ps = 0; ps < SYS_PS_COUNT; ps++) {
			*ptr++ = softsp->ps_stats[ps].dcshadow;
		}
	}
	return (0);
}

static void
sysctrl_thread_wakeup(void *arg)
{
	int type = (int)(uintptr_t)arg;

	/*
	 * grab mutex to guarantee that our wakeup call
	 * arrives after we go to sleep -- so we can't sleep forever.
	 */
	mutex_enter(&sslist_mutex);
	switch (type) {
	case OVERTEMP_POLL:
		cv_signal(&overtemp_cv);
		break;
	case KEYSWITCH_POLL:
		cv_signal(&keyswitch_cv);
		break;
	default:
		cmn_err(CE_WARN, "sysctrl: invalid type %d to wakeup\n", type);
		break;
	}
	mutex_exit(&sslist_mutex);
}

static void
sysctrl_overtemp_poll(void)
{
	struct sysctrl_soft_state *list;
	callb_cpr_t cprinfo;

	CALLB_CPR_INIT(&cprinfo, &sslist_mutex, callb_generic_cpr, "overtemp");

	/* The overtemp data structures are protected by a mutex. */
	mutex_enter(&sslist_mutex);

	while (sysctrl_do_overtemp_thread) {

		for (list = sys_list; list != NULL; list = list->next) {
			if (list->temp_reg != NULL) {
				update_temp(list->pdip, &list->tempstat,
					*(list->temp_reg));
			}
		}

		CALLB_CPR_SAFE_BEGIN(&cprinfo);

		/* now have this thread sleep for a while */
		(void) timeout(sysctrl_thread_wakeup, (void *)OVERTEMP_POLL,
			overtemp_timeout_hz);

		cv_wait(&overtemp_cv, &sslist_mutex);

		CALLB_CPR_SAFE_END(&cprinfo, &sslist_mutex);
	}
	CALLB_CPR_EXIT(&cprinfo);
	thread_exit();
	/* NOTREACHED */
}

static void
sysctrl_keyswitch_poll(void)
{
	struct sysctrl_soft_state *list;
	callb_cpr_t cprinfo;

	CALLB_CPR_INIT(&cprinfo, &sslist_mutex, callb_generic_cpr, "keyswitch");

	/* The keyswitch data strcutures are protected by a mutex. */
	mutex_enter(&sslist_mutex);

	while (sysctrl_do_keyswitch_thread) {

		for (list = sys_list; list != NULL; list = list->next) {
			if (list->status1 != NULL)
				update_key_state(list);
		}

		CALLB_CPR_SAFE_BEGIN(&cprinfo);

		/* now have this thread sleep for a while */
		(void) timeout(sysctrl_thread_wakeup, (void *)KEYSWITCH_POLL,
			keyswitch_timeout_hz);

		cv_wait(&keyswitch_cv, &sslist_mutex);

		CALLB_CPR_SAFE_END(&cprinfo, &sslist_mutex);
	}
	CALLB_CPR_EXIT(&cprinfo);
	thread_exit();
	/* NOTREACHED */
}

/*
 * check the key switch position for state changes
 */
static void
update_key_state(struct sysctrl_soft_state *list)
{
	enum keyswitch_state key;

	/*
	 * snapshot current hardware key position
	 */
	if (*(list->status1) & SYS_NOT_SECURE)
		key = KEY_NOT_SECURE;
	else
		key = KEY_SECURE;

	/*
	 * check for state transition
	 */
	if (key != list->key_shadow) {

		/*
		 * handle state transition
		 */
		switch (list->key_shadow) {
		case KEY_BOOT:
			cmn_err(CE_CONT, "?sysctrl%d: Key switch is%sin the "
			    "secure position\n", ddi_get_instance(list->dip),
			    (key == KEY_SECURE) ? " " : " not ");
			list->key_shadow = key;
			break;
		case KEY_SECURE:
		case KEY_NOT_SECURE:
			cmn_err(CE_NOTE, "sysctrl%d: Key switch has changed"
			    " to the %s position",
			    ddi_get_instance(list->dip),
			    (key == KEY_SECURE) ? "secure" : "not-secure");
			list->key_shadow = key;
			break;
		default:
			cmn_err(CE_CONT,
			    "?sysctrl%d: Key switch is in an unknown position,"
			    "treated as being in the %s position\n",
			    ddi_get_instance(list->dip),
			    (list->key_shadow == KEY_SECURE) ?
			    "secure" : "not-secure");
			break;
		}
	}
}

/*
 * consider key switch position when handling an abort sequence
 */
static void
sysctrl_abort_seq_handler(char *msg)
{
	struct sysctrl_soft_state *list;
	uint_t secure = 0;
	char buf[64], inst[4];


	/*
	 * if any of the key switch positions are secure,
	 * then disallow entry to the prom/debugger
	 */
	mutex_enter(&sslist_mutex);
	buf[0] = (char)0;
	for (list = sys_list; list != NULL; list = list->next) {
		if (!(*(list->status1) & SYS_NOT_SECURE)) {
			if (secure++)
				(void) strcat(buf, ",");
			/*
			 * XXX: later, replace instance number with nodeid
			 */
			(void) sprintf(inst, "%d", ddi_get_instance(list->dip));
			(void) strcat(buf, inst);
		}
	}
	mutex_exit(&sslist_mutex);

	if (secure) {
		cmn_err(CE_CONT,
			"!sysctrl(%s): ignoring debug enter sequence\n", buf);
	} else {
		cmn_err(CE_CONT, "!sysctrl: allowing debug enter\n");
		debug_enter(msg);
	}
}

#define	TABLE_END	0xFF

struct uart_cmd {
	uchar_t reg;
	uchar_t data;
};

/*
 * Time constant defined by this formula:
 *	((4915200/32)/(baud) -2)
 */

struct uart_cmd uart_table[] = {
	{ 0x09, 0xc0 },	/* Force hardware reset */
	{ 0x04, 0x46 },	/* X16 clock mode, 1 stop bit/char, no parity */
	{ 0x03, 0xc0 },	/* Rx is 8 bits/char */
	{ 0x05, 0xe2 },	/* DTR, Tx is 8 bits/char, RTS */
	{ 0x09, 0x02 },	/* No vector returned on interrupt */
	{ 0x0b, 0x55 },	/* Rx Clock = Tx Clock = BR generator = ~TRxC OUT */
	{ 0x0c, 0x0e },	/* Time Constant = 0x000e for 9600 baud */
	{ 0x0d, 0x00 },	/* High byte of time constant */
	{ 0x0e, 0x02 },	/* BR generator comes from Z-SCC's PCLK input */
	{ 0x03, 0xc1 },	/* Rx is 8 bits/char, Rx is enabled */
	{ 0x05, 0xea },	/* DTR, Tx is 8 bits/char, Tx is enabled, RTS */
	{ 0x0e, 0x03 },	/* BR comes from PCLK, BR generator is enabled */
	{ 0x00, 0x30 },	/* Error reset */
	{ 0x00, 0x30 },	/* Error reset */
	{ 0x00, 0x10 },	/* external status reset */
	{ 0x03, 0xc1 },	/* Rx is 8 bits/char, Rx is enabled */
	{ TABLE_END, 0x0 }
};

static void
init_remote_console_uart(struct sysctrl_soft_state *softsp)
{
	int i = 0;

	/*
	 * Serial chip expects software to write to the control
	 * register first with the desired register number. Then
	 * write to the control register with the desired data.
	 * So walk thru table writing the register/data pairs to
	 * the serial port chip.
	 */
	while (uart_table[i].reg != TABLE_END) {
		*(softsp->rcons_ctl) = uart_table[i].reg;
		*(softsp->rcons_ctl) = uart_table[i].data;
		i++;
	}
}

/*
 * return the slot information of the system
 *
 * function take a sysctrl_soft_state, so it's ready for sunfire+
 * change which requires 2 registers to decide the system type.
 */
static void
sysc_slot_info(int nslots, int *start, int *limit, int *incr)
{
	switch (nslots) {
	case 8:
		*start = 0;
		*limit = 8;
		*incr = 1;
		break;
	case 5:
		*start = 1;
		*limit = 10;
		*incr = 2;
		break;
	case 4:
		*start = 1;
		*limit = 8;
		*incr = 2;
		break;
	case 0:
	case 16:
	default:
		*start = 0;
		*limit = 16;
		*incr = 1;
		break;
	}
}

/*
 * reinitialize the Remote Console on the clock board
 *
 * with V5_AUX power outage the Remote Console ends up in
 * unknown state and has to be reinitilized if it was enabled
 * initially.
 */
static void
rcons_reinit(struct sysctrl_soft_state *softsp)
{
	uchar_t tmp_reg;

	if (!(softsp->rcons_ctl))
		/*
		 * There is no OBP register set for the remote console UART,
		 * so offset from the last register set, the misc register
		 * set, in order to map in the remote console UART.
		 */
		if (ddi_map_regs(softsp->dip, 1, (caddr_t *)&softsp->rcons_ctl,
		    RMT_CONS_OFFSET, RMT_CONS_LEN)) {
			cmn_err(CE_WARN, "Unable to reinitialize "
				"remote console.");
			return;
		}


	/* Disable the remote console reset control bits. */
	*(softsp->clk_freq2) &= ~RCONS_UART_EN;

	/* flush the hardware buffers */
	tmp_reg = *(softsp->csr);

	/*
	 * Program the UART to watch ttya console.
	 */
	init_remote_console_uart(softsp);

	/* Now enable the remote console reset control bits. */
	*(softsp->clk_freq2) |= RCONS_UART_EN;

	/* flush the hardware buffers */
	tmp_reg = *(softsp->csr);

	/* print some info for user to watch */
	cmn_err(CE_NOTE, "Remote console reinitialized");
#ifdef lint
	tmp_reg = tmp_reg;
#endif
}