/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * MonteCarlo HotSwap Controller functionality
 */

#include	<sys/types.h>
#include	<sys/stropts.h>
#include	<sys/stream.h>
#include	<sys/strsun.h>
#include	<sys/kmem.h>
#include	<sys/cmn_err.h>
#include	<sys/errno.h>
#include	<sys/cpuvar.h>
#include	<sys/open.h>
#include	<sys/stat.h>
#include	<sys/conf.h>
#include	<sys/ddi.h>
#include	<sys/sunddi.h>
#include	<sys/modctl.h>
#include	<sys/promif.h>
#include	<sys/hotplug/hpcsvc.h>

#include	<sys/hscimpl.h>
#include	<sys/hsc.h>

#include	<sys/mct_topology.h>
#include	<sys/scsbioctl.h>
#include	<sys/scsb.h>

#define	HOTSWAP_MODE_PROP	"hotswap-mode"
#define	ALARM_CARD_ON_SLOT	1
#define	SCSB_HSC_FORCE_REMOVE	1	/* force remove enum intr handler */

/* TUNABLE PARAMETERS. Some are Debug Only. Please take care when using. */

/*
 * Set this flag to 1, to enable full hotswap mode at boot time.
 * Since HPS is threaded, it is not recommended that we set this flag
 * to 1 because enabling full hotswap interrupt can invoke the ENUM
 * event handler accessing the slot data structure which may have not
 * been initialized in the hotplug framework since the HPS may not yet
 * have called the slot registration function with the bus nexus.
 */
static int	scsb_hsc_enable_fhs = 0;

/*
 * Every time  a slot is registered with the hotswap framework, the
 * framework calls back. This variable keeps a count on how many
 * callbacks are done.
 */
static int scsb_hsc_numReg = 0;
/*
 * When this flag is set, the board is taken offline (put in reset) after
 * a unconfigure operation, in Basic Hotswap mode.
 */
static int	scsb_hsc_bhs_slot_reset = 1;
/*
 * When this flag is set, we take the board to reset after unconfigure
 * operation when operating in full hotswap mode.
 */
static int	scsb_hsc_fhs_slot_reset = 1;
/*
 * Implementation of this counter will work only on Montecarlo since
 * the ENUM# Interrupt line is not shared with other interrupts.
 * When the hardware routing changes, then there may be need to remove
 * or change this functionality.
 * This functionality is provided so that a bad or non friendly full hotswap
 * board does not hang the system in full hotswap mode. Atleast the
 * intent is that! Eventually Solaris kernel will provide similar support
 * for recovering from a stuck interrupt line. Till then, lets do this.
 */
static int	scsb_hsc_max_intr_count = 8;
/*
 * Since the hardware does not support enabling/disabling ENUM#, the
 * following flag can be used for imitating that behaviour.
 * Currently we can set this flag and use the remove op to remove the
 * interrupt handler from the system. Care must be taken when using this
 * function since trying to remove the interrupt handler when the interrupts
 * are pending may hang the system permanently.
 * Since the hardware does not support this functionality, we adopt this
 * approach for debugs.
 */
static int	scsb_hsc_enum_switch = 0;

/*
 * When the board loses Healthy# at runtime (with the board being configured),
 * cPCI specs states that a Reset has to be asserted immediately.
 * We dont do this currently, until satellite processor support is given
 * and the implications of such a act is fully understood.
 * To adopt the cPCI specs recommendation, set this flag to 1.
 */
static	int	scsb_hsc_healthy_reset = 0;

/*
 * According to PCI 2.2 specification, once a board comes out of PCI_RST#,
 * it may take upto 2^25 clock cycles to respond to config cycles. For
 * montecarlo using a 33MHz cPCI bus, it's around 1.024 s. The variable
 * will specify the time in ms to wait before attempting config access.
 */
static	int scsb_connect_delay = 1025;

/*
 * slot map property for MC should be
 *
 *	hsc-slot-map="/pci@1f,0/pci@1/pci@1","15","2",
 *               "/pci@1f,0/pci@1/pci@1","14","3",
 *               "/pci@1f,0/pci@1/pci@1","13","4",
 *               "/pci@1f,0/pci@1/pci@1","12","5"
 *               "/pci@1f,0/pci@1/pci@1","11","6"
 *               "/pci@1f,0/pci@1/pci@1","10","7"
 *               "/pci@1f,0/pci@1/pci@1","8","8";
 *
 * slot map property for Tonga should be
 *	hsc-slot-map="/pci@1f,0/pci@1/pci@1","8","1"
 *		"/pci@1f,0/pci@1/pci@1", "15", "2"
 *		"/pci@1f,0/pci@1/pci@1", "14", "4"
 *		"/pci@1f,0/pci@1/pci@1", "13", "5"
 *
 * Please note that the CPU slot number is 3 for Tonga.
 */

/*
 * Services we require from the SCSB
 */
extern int	scsb_get_slot_state(void *, int, int *);
extern int	scsb_read_bhealthy(scsb_state_t *scsb);
extern int	scsb_read_slot_health(scsb_state_t *scsb, int pslotnum);
extern int	scsb_connect_slot(void *, int, int);
extern int	scsb_disconnect_slot(void *, int, int);

static void	*hsc_state;

static uint_t	hsc_enum_intr(char *);
static hsc_slot_t *hsc_get_slot_info(hsc_state_t *, int);
static int	scsb_enable_enum(hsc_state_t *);
static int	scsb_disable_enum(hsc_state_t *, int);
static int	atoi(const char *);
static int	isdigit(int);
static hsc_slot_t *hsc_find_slot(int);
static void	hsc_led_op(hsc_slot_t *, int, hpc_led_t, hpc_led_state_t);
static int	hsc_led_state(hsc_slot_t *, int, hpc_led_info_t *);
static int	scsb_hsc_disable_slot(hsc_slot_t *);
static int	scsb_hsc_enable_slot(hsc_slot_t *);
#ifndef	lint
static int hsc_clear_all_enum(hsc_state_t *);
#endif
static int	hsc_slot_register(hsc_state_t *, char *, uint16_t, uint_t,
					boolean_t);
static int	hsc_slot_unregister(int);
static int	scsb_hsc_init_slot_state(hsc_state_t *, hsc_slot_t *);
static int	hsc_slot_autoconnect(hsc_slot_t *);

static hpc_slot_ops_t	*hsc_slotops;
static hsc_slot_t	*hsc_slot_list;		/* linked list of slots */

/*
 * This mutex protects the following variables:
 *	hsc_slot_list
 */
static kmutex_t		hsc_mutex;


/* ARGSUSED */
static int
hsc_connect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags)
{
	hsc_slot_t *hsp = (hsc_slot_t *)ops_arg;
	int rc, rstate;
	hsc_state_t	*hsc;

	DEBUG2("hsc_connect: slot %d, healthy %d", hsp->hs_slot_number,
						hsp->hs_board_healthy);

	if (!(hsp->hs_flags & (HSC_ENABLED|HSC_SLOT_ENABLED)))
		return (HPC_ERR_FAILED);
	/* if SCB hotswapped, do not allow connect operations */
	if (hsp->hs_flags & HSC_SCB_HOTSWAPPED)
		return (HPC_ERR_FAILED);
	/*
	 * if previous occupant stayed configured, do not allow another
	 * occupant to be connected.
	 * This behaviour is an indication that the slot state
	 * is not clean.
	 */
	if (hsp->hs_flags & HSC_SLOT_BAD_STATE) {
		/*
		 * In the current implementation, we turn both fault
		 * and active LEDs to ON state in this situation.
		 */
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
							HPC_LED_ON);
		return (HPC_ERR_FAILED);
	}
	/*
	 * Get the actual status from the i2c bus
	 */
	rc = scsb_get_slot_state(hsp->hs_hpchandle, hsp->hs_slot_number,
								&rstate);
	if (rc != DDI_SUCCESS)
		return (HPC_ERR_FAILED);

	hsp->hs_slot_state = rstate;
	if (hsp->hs_slot_state == HPC_SLOT_EMPTY) {
#ifdef DEBUG
		cmn_err(CE_CONT,
			"?hsc_connect: slot %d is empty\n",
			hsp->hs_slot_number);
#endif
		return (HPC_ERR_FAILED);
	}

	if (hsp->hs_slot_state == HPC_SLOT_CONNECTED)
		return (HPC_SUCCESS);

	rc = HPC_SUCCESS;
	/*
	 * call scsb to connect the slot. This also makes sure board is healthy
	 */
	if (scsb_connect_slot(hsp->hs_hpchandle, hsp->hs_slot_number,
				hsp->hs_board_healthy) != DDI_SUCCESS) {
		DEBUG1("hsc_connect: slot %d connection failed",
				hsp->hs_slot_number);
		rc = HPC_ERR_FAILED;
	} else {
		if (hsp->hs_slot_state != HPC_SLOT_CONNECTED) {
			if (hsp->hs_board_healthy == B_FALSE) {
				cmn_err(CE_NOTE, "HEALTHY# not asserted on "
					" slot %d", hsp->hs_slot_number);
				return (HPC_ERR_FAILED);
			}
			hsc = hsp->hsc;
			hsc->hsp_last = hsp;
			if (scsb_reset_slot(hsp->hs_hpchandle,
				hsp->hs_slot_number, SCSB_UNRESET_SLOT) != 0) {

				return (HPC_ERR_FAILED);
			}
			/*
			 * Unresetting a board may have caused an interrupt
			 * burst in case of non friendly boards. So it is
			 * important to make sure that the ISR has not
			 * put this board back to disconnect state.
			 */
			delay(1);
			if (hsp->hs_flags & HSC_ENUM_FAILED) {
				hsp->hs_flags &= ~HSC_ENUM_FAILED;
				return (HPC_ERR_FAILED);
			}
			DEBUG1("hsc_connect: slot %d connected",
						hsp->hs_slot_number);
			rc = HPC_SUCCESS;
			hsp->hs_slot_state = HPC_SLOT_CONNECTED;
			(void) hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
				HPC_FAULT_LED, HPC_LED_OFF);
		}
	}

	/*
	 * PCI 2.2 specs recommend that the probe software wait
	 * for upto 2^25 PCI clock cycles after deassertion of
	 * PCI_RST# before the board is able to respond to config
	 * cycles. So, before we return, we wait for ~1 sec.
	 */
	delay(drv_usectohz(scsb_connect_delay * 1000));
	return (rc);
}


/* ARGSUSED */
static int
hsc_disconnect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags)
{
	hsc_slot_t		*hsp = (hsc_slot_t *)ops_arg;
	hsc_state_t		*hsc;
#ifdef	DEBUG
	static const char	func[] = "hsc_disconnect";
#endif

	DEBUG1("hsc_disconnect: slot %d", hsp->hs_slot_number);

	if (hsp->hs_board_configured) {
#ifdef	DEBUG
		cmn_err(CE_NOTE,
			"%s: cannot disconnect configured board in slot %d",
			func, hsp->hs_slot_number);
#endif
		return (HPC_ERR_FAILED);
	}

	if (hsp->hs_slot_state == HPC_SLOT_EMPTY) {
#ifdef	DEBUG
		cmn_err(CE_NOTE, "%s: slot %d is empty",
			func, hsp->hs_slot_number);
#endif
		return (HPC_SUCCESS);
	}

	if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) {
		/*
		 * if already disconnected, just return success
		 * Duplicate disconnect messages should not be failed!
		 */
		return (HPC_SUCCESS);
	}
	/* if SCB hotswapped, do not allow disconnect operations */
	if (hsp->hs_flags & HSC_SCB_HOTSWAPPED)
		return (HPC_ERR_FAILED);

	/* call scsb to disconnect the slot */
	if (scsb_disconnect_slot(hsp->hs_hpchandle, B_TRUE, hsp->hs_slot_number)
			!= DDI_SUCCESS)
		return (HPC_ERR_FAILED);
	hsc = hsp->hsc;
	if (hsc->hsp_last == hsp)
		hsc->hsp_last = NULL;

	return (HPC_SUCCESS);
}


/*
 * In the cPCI world, this operation is not applicable.
 * However, we use this function to enable full hotswap mode in debug mode.
 */
/* ARGSUSED */
static int
hsc_insert(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags)
{
	hsc_slot_t		*hsp = (hsc_slot_t *)ops_arg;

	if (scsb_hsc_enum_switch &&
			(scsb_enable_enum(hsp->hsc) == DDI_SUCCESS)) {
		return (HPC_SUCCESS);
	}
	return (HPC_ERR_NOTSUPPORTED);
}


/*
 * In the cPCI world, this operation is not applicable.
 * However, we use this function to disable full hotswap mode in debug mode.
 */
/* ARGSUSED */
static int
hsc_remove(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags)
{
	hsc_slot_t		*hsp = (hsc_slot_t *)ops_arg;

	if (scsb_hsc_enum_switch &&
			(scsb_disable_enum(hsp->hsc, SCSB_HSC_FORCE_REMOVE)
					== DDI_SUCCESS)) {
		hsp->hs_flags &= ~HSC_ENUM_FAILED;
		return (HPC_SUCCESS);
	}
	return (HPC_ERR_NOTSUPPORTED);
}

static void
hsc_led_op(hsc_slot_t *hsp, int cmd, hpc_led_t led, hpc_led_state_t led_state)
{
	hpc_led_info_t	ledinfo;

	ledinfo.led = led;
	ledinfo.state = led_state;
	(void) hsc_led_state(hsp, cmd, &ledinfo);
}

static int
hsc_led_state(hsc_slot_t *hsp, int cmd, hpc_led_info_t *hlip)
{
	hpc_led_state_t	*hlsp;
	scsb_uinfo_t	sunit;
	int		res;

	DEBUG3("hsc_led_state: slot %d, led %x, state %x",
		hsp->hs_slot_number, hlip->led, hlip->state);

	sunit.unit_type = SLOT;
	sunit.unit_number = hsp->hs_slot_number;
	/*
	 * We ignore operations on LEDs that we don't support
	 */
	switch (hlip->led) {
		case HPC_FAULT_LED:
			sunit.led_type = NOK;
			hlsp = &hsp->hs_fault_led_state;
			break;
		case HPC_ACTIVE_LED:
			sunit.led_type = OK;
			hlsp = &hsp->hs_active_led_state;
			break;
		default:
			return (HPC_ERR_NOTSUPPORTED);
	}

	switch (hlip->state) {
		case HPC_LED_BLINK:
			sunit.unit_state = BLINK;
			if (hlip->led != HPC_ACTIVE_LED)
				return (HPC_ERR_NOTSUPPORTED);
			break;
		case HPC_LED_ON:
			sunit.unit_state = ON;
			break;
		case HPC_LED_OFF:
			sunit.unit_state = OFF;
			break;
		default:
			break;
	}

	switch (cmd) {
	case HPC_CTRL_SET_LED_STATE:
		res = scsb_led_set(hsp->hs_hpchandle, &sunit, sunit.led_type);
		if (res != 0)
			return (HPC_ERR_FAILED);
		*hlsp = (hpc_led_state_t)sunit.unit_state;
		break;

	case HPC_CTRL_GET_LED_STATE:
		res = scsb_led_get(hsp->hs_hpchandle, &sunit, sunit.led_type);
		if (res)
			return (HPC_ERR_FAILED);
		/* hlip->state = sunit.unit_state; */
		break;

	default:
		return (HPC_ERR_INVALID);
	}

	return (HPC_SUCCESS);

}


static int
hsc_get_slot_state(hsc_slot_t *hsp, hpc_slot_state_t *hssp)
{
	int rstate = 0;
	int rc;
#ifdef	DEBUG
	int orstate;	/* original rstate */
#endif

	DEBUG1("hsc_get_slot_state: slot %d", hsp->hs_slot_number);
	rc = scsb_get_slot_state(hsp->hs_hpchandle, hsp->hs_slot_number,
								&rstate);
	if (rc != DDI_SUCCESS)
		return (HPC_ERR_FAILED);
#ifdef	DEBUG
	orstate = hsp->hs_slot_state;
#endif
	hsp->hs_slot_state = rstate;
	switch (hsp->hs_slot_state) {
	case HPC_SLOT_EMPTY:
		DEBUG0("empty");
		break;
	case HPC_SLOT_CONNECTED:
		DEBUG0("connected");
		break;
	case HPC_SLOT_DISCONNECTED:
		DEBUG0("disconnected");
		break;
	}

	*hssp = hsp->hs_slot_state;

	/* doing get-state above may have caused a freeze operation */
	if ((hsp->hs_flags & HSC_SCB_HOTSWAPPED) &&
			(rstate == HPC_SLOT_DISCONNECTED)) {
		/* freeze puts disconnected boards to connected state */
		*hssp = HPC_SLOT_CONNECTED;
#if 0
		/* in FHS, deassertion of reset may have configured the board */
		if (hsp->hs_board_configured == B_TRUE) {
			hsp->hs_slot_state = *hssp;
		}
#endif
	}
#ifdef	DEBUG
	/* a SCB hotswap may have forced a state change on the receptacle */
	if (orstate != *hssp) {
		cmn_err(CE_NOTE, "hsc_get_state: slot%d state change due"
			" to SCB hotswap!", hsp->hs_slot_number);
	}
#endif
	return (HPC_SUCCESS);
}


static int
hsc_set_config_state(hsc_slot_t *hsp, int cmd)
{
	hsc_state_t	*hsc = hsp->hsc;

	DEBUG1("hsc_set_config_state: slot %d", hsp->hs_slot_number);

	switch (cmd) {
	case HPC_CTRL_DEV_CONFIGURED:
		/*
		 * Closing of the Ejector switch in configured/busy state can
		 * cause duplicate CONFIGURED messages to come down.
		 * Make sure our LED states are fine.
		 */
		if (hsp->hs_board_configured == B_TRUE) {
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_ON);
			break;
		}
		hsp->hs_board_configured = B_TRUE;
		hsp->hs_board_configuring = B_FALSE;
		if ((hsc->state & HSC_ATTACHED) == HSC_ATTACHED &&
			hsp->hs_flags & HSC_ALARM_CARD_PRES)
			(void) scsb_hsc_ac_op(hsp->hs_hpchandle,
				hsp->hs_slot_number, SCSB_HSC_AC_CONFIGURED);
		/* LED must be OFF on the occupant. */
		(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_SLOT_BLUE_LED_OFF, 0);
		if (hsp->hs_flags & HSC_AUTOCFG)
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_ENABLE_ENUM, 0);
		else
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_DISABLE_ENUM, 0);
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_ON);
		if (hsc->hsp_last == hsp)
			hsc->hsp_last = NULL;
		break;
	case HPC_CTRL_DEV_UNCONFIGURED:
		hsp->hs_board_configured = B_FALSE;
		hsp->hs_board_unconfiguring = B_FALSE;
		hsp->hs_flags &= ~HSC_SLOT_BAD_STATE;
		if (hsp->hs_flags & HSC_ALARM_CARD_PRES)
			(void) scsb_hsc_ac_op(hsp->hs_hpchandle,
				hsp->hs_slot_number, SCSB_HSC_AC_UNCONFIGURED);
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
							HPC_LED_BLINK);
		if (((hsc->state & HSC_ENUM_ENABLED) &&
			scsb_hsc_fhs_slot_reset) ||
		(((hsc->state & HSC_ENUM_ENABLED) != HSC_ENUM_ENABLED) &&
				scsb_hsc_bhs_slot_reset) ||
				((hsp->hs_flags & HSC_AUTOCFG) !=
					HSC_AUTOCFG)) {
			if (scsb_reset_slot(hsp->hs_hpchandle,
				hsp->hs_slot_number, SCSB_RESET_SLOT) == 0) {

				hsp->hs_slot_state = HPC_SLOT_DISCONNECTED;
				hsp->hs_board_healthy = B_FALSE;
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
					HPC_FAULT_LED, HPC_LED_ON);
			}
		}
		break;
	case HPC_CTRL_DEV_CONFIG_FAILURE:
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
							HPC_LED_BLINK);
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
				HPC_FAULT_LED, HPC_LED_ON);
		break;
	case HPC_CTRL_DEV_UNCONFIG_FAILURE:
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
							HPC_LED_ON);
		break;
	case HPC_CTRL_DEV_CONFIG_START:
	case HPC_CTRL_DEV_UNCONFIG_START:
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
					HPC_LED_OFF);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
					HPC_LED_BLINK);
		break;
	default:
		return (HPC_ERR_INVALID);
	}

	if (cmd != HPC_CTRL_DEV_CONFIG_START &&
		cmd != HPC_CTRL_DEV_UNCONFIG_START &&
		hsc->regDone == B_FALSE &&
			scsb_hsc_numReg < hsc->n_registered_occupants) {
		scsb_hsc_numReg++;

		/*
		 * If the callback is invoked for all registered slots,
		 * enable ENUM.
		 */
		if (((hsc->state & HSC_ATTACHED) == HSC_ATTACHED) &&
			(scsb_hsc_numReg == hsc->n_registered_occupants)) {
			hsc->regDone = B_TRUE;
			if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL) {
#ifdef DEBUG
				cmn_err(CE_CONT, "%s%d: Enabling full hotswap"
					":%d non-empty slots\n",
					ddi_driver_name(hsc->dip),
					ddi_get_instance(hsc->dip),
					hsc->n_registered_occupants);
#endif
				if (scsb_enable_enum(hsc) != DDI_SUCCESS) {
					cmn_err(CE_WARN, "%s#%d: Cannot enable "
						"Full Hotswap",
						ddi_driver_name(hsc->dip),
						ddi_get_instance(hsc->dip));

					return (HPC_ERR_FAILED);
				}
			}
		}
	}

	return (HPC_SUCCESS);
}


/*ARGSUSED*/
static int
hsc_get_board_type(hsc_slot_t *hsp, hpc_board_type_t *hbtp)
{
	*hbtp = hsp->hs_board_type;
	return (HPC_SUCCESS);
}


/* ARGSUSED */
static int
hsc_autoconfig(hsc_slot_t *hsp, int cmd)
{
	int res = HPC_SUCCESS, enum_disable = B_TRUE, i;
	char slotautocfg_prop[18];
	hsc_state_t *hsc;

	DEBUG1("hsc_autoconfig: slot %d", hsp->hs_slot_number);
	(void) sprintf(slotautocfg_prop, "slot%d-autoconfig",
	    hsp->hs_slot_number);

	if (cmd == HPC_CTRL_ENABLE_AUTOCFG) {
		hsp->hs_flags |= HSC_AUTOCFG;
		(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip,
				slotautocfg_prop, "enabled");
		if ((res = scsb_enable_enum(hsp->hsc)) == DDI_SUCCESS) {
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_ENABLE_ENUM, 0);
		}
	} else {
		(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip,
		    slotautocfg_prop, "disabled");
		hsp->hs_flags &= ~HSC_AUTOCFG;
		hsc = hsp->hsc;
		if (hsc->state & HSC_ATTACHED) {
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
						HPC_EVENT_DISABLE_ENUM, 0);
			for (i = 0; i < hsc->slot_table_size; i++) {
				hsc_slot_t	*thsp;
				int slotnum;

				slotnum = hsc->slot_table_prop[i].pslotnum;
				thsp = hsc_find_slot(slotnum);
				if (thsp == NULL) {
					cmn_err(CE_WARN, "%s#%d: hsc_autocfg:"
						"No Slot Info for slot %d",
						ddi_driver_name(hsc->dip),
						ddi_get_instance(hsc->dip),
						slotnum);
					continue;
				}
				if (thsp->hs_flags & HSC_AUTOCFG) {
					enum_disable = B_FALSE;
					break;
				}
			}
			if (enum_disable == B_TRUE)
				(void) scsb_disable_enum(hsc,
				    SCSB_HSC_FORCE_REMOVE);
		}
	}
	return (res);
}


/*
 * This function is invoked to enable/disable a slot
 */
/* ARGSUSED */
#ifndef	lint
static int
hsc_slot_enable(hsc_slot_t *hsp, boolean_t enabled)
{
	scsb_uinfo_t	sunit;
	int		res;

	DEBUG1("hsc_slot_enable: slot %d", hsp->hs_slot_number);

	sunit.unit_type = SLOT;
	sunit.unit_number = hsp->hs_slot_number;
	if (enabled)
		sunit.unit_state = ON;
	else
		sunit.unit_state = OFF;

	res = scsb_reset_unit(hsp->hs_hpchandle, &sunit);
	if (res == 0)
		return (HPC_SUCCESS);
	else if (res == EINVAL)
		return (HPC_ERR_INVALID);
	else
		return (HPC_ERR_FAILED);
}
#endif


/*ARGSUSED*/
static int
hsc_control(caddr_t ops_arg, hpc_slot_t slot_hdl, int request, caddr_t arg)
{
	hsc_slot_t *hsp = (hsc_slot_t *)ops_arg;
	int rc = HPC_SUCCESS;

	DEBUG2("hsc_control: slot %d, op=%x\n", hsp->hs_slot_number, request);

	switch (request) {
	case HPC_CTRL_GET_LED_STATE:
		return (hsc_led_state(hsp,
			HPC_CTRL_GET_LED_STATE, (hpc_led_info_t *)arg));

	case HPC_CTRL_SET_LED_STATE:
		return (hsc_led_state(hsp,
			HPC_CTRL_SET_LED_STATE, (hpc_led_info_t *)arg));

	case HPC_CTRL_GET_SLOT_STATE:
		return (hsc_get_slot_state(hsp, (hpc_slot_state_t *)arg));

	case HPC_CTRL_DEV_CONFIGURED:
		return (hsc_set_config_state(hsp, HPC_CTRL_DEV_CONFIGURED));

	case HPC_CTRL_DEV_UNCONFIGURED:
		return (hsc_set_config_state(hsp, HPC_CTRL_DEV_UNCONFIGURED));

	case HPC_CTRL_DEV_CONFIG_FAILURE:
		return (hsc_set_config_state(hsp, HPC_CTRL_DEV_CONFIG_FAILURE));

	case HPC_CTRL_DEV_UNCONFIG_FAILURE:
		return (hsc_set_config_state(hsp,
				HPC_CTRL_DEV_UNCONFIG_FAILURE));

	case HPC_CTRL_DEV_CONFIG_START:
	case HPC_CTRL_DEV_UNCONFIG_START:
		return (hsc_set_config_state(hsp, request));

	case HPC_CTRL_GET_BOARD_TYPE:
		return (hsc_get_board_type(hsp, (hpc_board_type_t *)arg));

	case HPC_CTRL_DISABLE_AUTOCFG:
		return (hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG));

	case HPC_CTRL_ENABLE_AUTOCFG:
		return (hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG));

	case HPC_CTRL_DISABLE_SLOT:
		/*
		 * No hardware support for disabling the slot.
		 * Just imitate a disable_autoconfig operation for now
		 */
		if (hsp->hs_board_configured == B_TRUE)
			return (HPC_ERR_FAILED);
		if (scsb_hsc_disable_slot(hsp) != DDI_SUCCESS)
			rc = HPC_ERR_FAILED;
		return (rc);

	case HPC_CTRL_ENABLE_SLOT:
		if (scsb_hsc_enable_slot(hsp) != DDI_SUCCESS)
			rc = HPC_ERR_FAILED;
		return (rc);

	case HPC_CTRL_ENABLE_ENUM:
		return (scsb_enable_enum(hsp->hsc));

	case HPC_CTRL_DISABLE_ENUM:
		return (scsb_disable_enum(hsp->hsc, 0));

	default:
		return (HPC_ERR_INVALID);
	}
}

static int
scsb_hsc_disable_slot(hsc_slot_t *hsp)
{
	int rc;
	char slot_disable_prop[18];

	DEBUG1("hsc_disable_slot: slot %d", hsp->hs_slot_number);
	(void) sprintf(slot_disable_prop, "slot%d-status", hsp->hs_slot_number);

	rc = scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number,
					SCSB_RESET_SLOT);
	if (rc == DDI_SUCCESS) {
		(void) hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG);
		hsp->hs_flags &= ~HSC_SLOT_ENABLED;
		(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip,
		    slot_disable_prop, "disabled");
	} else
		rc = DDI_FAILURE;
	return (rc);
}

static int
scsb_hsc_enable_slot(hsc_slot_t *hsp)
{
	int rc;
	char slot_disable_prop[18];

	DEBUG1("hsc_disable_slot: slot %d", hsp->hs_slot_number);
	(void) sprintf(slot_disable_prop, "slot%d-status", hsp->hs_slot_number);

	rc = scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number,
					SCSB_UNRESET_SLOT);
	if (rc == DDI_SUCCESS) {
		(void) hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG);
		hsp->hs_flags |= HSC_SLOT_ENABLED;
		(void) ddi_prop_remove(DDI_DEV_T_NONE, hsp->hsc->dip,
		    slot_disable_prop);
	} else
		rc = HPC_ERR_FAILED;
	return (rc);
}

#define	NEW(type)	(type *) kmem_zalloc(sizeof (type), KM_SLEEP)

static hsc_slot_t *
hsc_alloc_slot(
		uint16_t	device_number,
		int		slot_number,
		boolean_t	board_in_slot)
{
	hpc_slot_info_t	*hsip;
	hsc_slot_t	*hsp = NEW(hsc_slot_t);

	DEBUG2("hsc_alloc_slot: slot %d %s", slot_number,
		board_in_slot ? "occupied" : "empty");

	if (hsp == NULL) {
		cmn_err(CE_NOTE,
			"hsc_alloc_slot: allocation failed for slot %d",
			slot_number);
		return (NULL);
	}

	hsip = &hsp->hs_info;

	hsip->version			= HPC_SLOT_INFO_VERSION;
	hsip->slot_type			= HPC_SLOT_TYPE_CPCI;
	hsip->pci_dev_num		= device_number;
	hsip->pci_slot_capabilities	= 0;
	hsip->slot_flags		= HPC_SLOT_CREATE_DEVLINK;
	/*
	 * Note: the name *must* be 'pci' so that the correct cfgadm plug-in
	 *	 library is selected
	 */
	(void) sprintf(hsip->pci_slot_name, "cpci_slot%d", slot_number);

	/*
	 * We assume that the following LED settings reflect
	 * the hardware state.
	 * After we register the slot, we will be invoked by the nexus
	 * if the slot is occupied, and we will turn on the LED then.
	 */
	hsp->hs_active_led_state	= HPC_LED_OFF;
	hsp->hs_fault_led_state		= HPC_LED_OFF;

	hsp->hs_board_configured	= B_FALSE;
	hsp->hs_board_healthy		= B_FALSE;
	hsp->hs_board_type		= HPC_BOARD_UNKNOWN;

	hsp->hs_flags			= HSC_ENABLED | HSC_SLOT_ENABLED;
	hsp->hs_slot_number		= slot_number;

	/*
	 * we should just set this to connected,
	 * as MC slots are always connected.
	 */
	if (board_in_slot)
		hsp->hs_slot_state = HPC_SLOT_CONNECTED;
	else
		hsp->hs_slot_state = HPC_SLOT_EMPTY;

	return (hsp);
}


static void
hsc_free_slot(hsc_slot_t *hsp)
{
	DEBUG0("hsc_free_slot");

	kmem_free(hsp, sizeof (*hsp));
}


/*
 * This function is invoked to register a slot
 */
static int
hsc_slot_register(
	hsc_state_t	*hsc,
	char		*bus_path,	/* PCI nexus pathname */
	uint16_t	device_number,	/* PCI device number */
	uint_t		slot_number,	/* physical slot number */
	boolean_t	board_in_slot)	/* receptacle status */
{
	int		rc = HPC_SUCCESS;
	hsc_slot_t	*hsp;

	DEBUG2("hsc_slot_register: slot number %d, device number %d",
		slot_number, device_number);

	hsp = hsc_alloc_slot(device_number, slot_number,
			board_in_slot);

	if (hsp == NULL) {
#ifdef	DEBUG
		cmn_err(CE_NOTE, "hsc_slot_register: hsc_alloc_slot failed");
#endif
		return (HPC_ERR_FAILED);
	}

	hsp->hs_hpchandle = hsc->scsb_handle; /* handle for call backs */
	hsp->hsc = hsc;

	rc = scsb_hsc_init_slot_state(hsc, hsp);
	if (rc != DDI_SUCCESS)
		return (HPC_ERR_FAILED);

	/* slot autoconfiguration by default. */
	if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL)
		(void) hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG);
	else
		(void) hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG);

	/*
	 * Append to our list
	 */
	mutex_enter(&hsc_mutex);
	hsp->hs_next = hsc_slot_list;
	hsc_slot_list = hsp;
	mutex_exit(&hsc_mutex);

	rc = hpc_slot_register(hsc->dip,
			bus_path,
			&hsp->hs_info,
			&hsp->hs_slot_handle,	/* return value */
			hsc_slotops,
			(caddr_t)hsp,
			0);

	if (rc != HPC_SUCCESS) {
		cmn_err(CE_WARN, "%s#%d: failed to register slot %s:%d",
			ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip),
			bus_path, device_number);
		hsc_free_slot(hsp);
		return (rc);
	}

	DEBUG0("hsc_slot_register: hpc_slot_register successful");

	return (rc);
}


static int
hsc_slot_unregister(int slot_number)
{
	hsc_slot_t	*hsp, *prev;

	DEBUG1("hsc_slot_unregister: slot number %d", slot_number);

	mutex_enter(&hsc_mutex);
	hsp = prev = NULL;
	for (hsp = hsc_slot_list; hsp != NULL; hsp = hsp->hs_next) {
		if (hsp->hs_slot_number == slot_number) {
			if (prev == NULL) /* first entry */
				hsc_slot_list = hsc_slot_list->hs_next;
			else
				prev->hs_next = hsp->hs_next;
			hsp->hs_next = NULL;
			break;
		}
		prev = hsp;
	}
	mutex_exit(&hsc_mutex);

	if (hsp != NULL) {
		(void) hpc_slot_unregister(&hsp->hs_slot_handle);
		if ((hsp->hsc->state & HSC_ATTACHED) != HSC_ATTACHED &&
				hsp->hs_slot_state != HPC_SLOT_EMPTY) {
			hsp->hsc->n_registered_occupants--;
		}
		hsc_free_slot(hsp);
		return (0);
	}
	return (1);
}

static int
scsb_hsc_init_slot_state(hsc_state_t *hsc, hsc_slot_t *hsp)
{
	int rc, rstate;
	int slot_number = hsp->hs_slot_number;
	scsb_state_t	*scsb = (scsb_state_t *)hsc->scsb_handle;

	rc = scsb_get_slot_state(hsc->scsb_handle, slot_number, &rstate);
	if (rc != DDI_SUCCESS)
		return (DDI_FAILURE);

	/*
	 * Set the healthy status for this slot
	 */
	hsp->hs_board_healthy = scsb_read_slot_health(scsb, slot_number);
	hsp->hs_slot_state = rstate;
	switch (rstate) {
		case HPC_SLOT_EMPTY:
			/*
			 * this will clear any state differences between
			 * SCB Freeze operations.
			 */
			hsp->hs_slot_state = HPC_SLOT_EMPTY;
			/* slot empty. */
			(void) scsb_reset_slot(hsc->scsb_handle, slot_number,
			    SCSB_RESET_SLOT);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
			    HPC_LED_OFF);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
			    HPC_LED_OFF);
			break;
		case HPC_SLOT_DISCONNECTED:
			/*
			 * this will clear any state differences between
			 * SCB Freeze operations.
			 */
			hsp->hs_slot_state = HPC_SLOT_DISCONNECTED;
			/* check recovery from SCB freeze */
			if (hsp->hs_board_configured != B_TRUE) {
				/*
				 * Force a disconnect just in case there are
				 * differences between healthy and reset states.
				 */
				(void) scsb_reset_slot(hsc->scsb_handle,
				    slot_number, SCSB_RESET_SLOT);
				/*
				 * Slot in reset. OBP has not probed this
				 * device. Hence it is ok to remove this board.
				 */
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
						HPC_ACTIVE_LED, HPC_LED_BLINK);
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
						HPC_FAULT_LED, HPC_LED_ON);
				break;
			}
			/*FALLTHROUGH*/
		case HPC_SLOT_CONNECTED:
			/*
			 * this will clear any state differences between
			 * SCB Freeze operations.
			 */
			hsp->hs_slot_state = HPC_SLOT_CONNECTED;
			/*
			 * OBP should have probed this device, unless
			 * it was plugged in during the boot operation
			 * before the driver was loaded. In any case,
			 * no assumption is made and hence we take
			 * the conservative approach by keeping fault
			 * led off so board removal is not allowed.
			 */
			if (hsp->hs_board_configured == B_TRUE)
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
					HPC_ACTIVE_LED, HPC_LED_ON);
			else
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
					HPC_ACTIVE_LED, HPC_LED_BLINK);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
							HPC_LED_OFF);
			/*
			 * Netra ct alarm card hotswap support
			 */
			if (slot_number == scsb->ac_slotnum &&
				scsb->scsb_hsc_state & SCSB_ALARM_CARD_PRES) {
				hsp->hs_flags |= HSC_ALARM_CARD_PRES;
				DEBUG0("Xscsb_hsc_init_slot_state: "
						"set HSC_ALARM_CARD_PRES");
			}
			break;
		default:
			break;
	}
	return (rc);
}

static hsc_slot_t *
hsc_get_slot_info(hsc_state_t *hsc, int pci_devno)
{
	int i;

	for (i = 0; i < hsc->slot_table_size; i++) {

		if (hsc->slot_table_prop[i].pci_devno == pci_devno)
			return ((hsc_slot_t *)hsc_find_slot(
				hsc->slot_table_prop[i].pslotnum));
	}
	return (NULL);
}

static hsc_slot_t *
hsc_find_slot(int slot_number)
{
	hsc_slot_t	*hsp;

	mutex_enter(&hsc_mutex);
	for (hsp = hsc_slot_list; hsp != NULL; hsp = hsp->hs_next) {
		if (hsp->hs_slot_number == slot_number)
			break;
	}
	mutex_exit(&hsc_mutex);
	return (hsp);
}


/*
 * This function is invoked by the SCSB when an interrupt
 * happens to indicate that a board has been inserted-in/removed-from
 * the specified slot.
 */
int
hsc_slot_occupancy(int slot_number, boolean_t occupied, int flags, int healthy)
{
	static const char	func[]	= "hsc_slot_occupancy";
	hsc_slot_t		*hsp;
	int			rc = DDI_SUCCESS;

	DEBUG4("hsc_slot_occupancy: slot %d %s, ac=%d, healthy=%d",
			slot_number, occupied ? "occupied" : "not occupied",
			(flags == ALARM_CARD_ON_SLOT) ? 1:0, healthy);

	hsp = hsc_find_slot(slot_number);

	if (hsp == NULL) {
		cmn_err(CE_NOTE,
			"%s: cannot map slot number %d to a hsc_slot_t",
			func, slot_number);
		return (DDI_FAILURE);
	}

	hsp->hs_board_healthy = healthy;
	if (occupied) {
		/*
		 * A board was just inserted. We are disconnected at this point.
		 */
		if (hsp->hs_slot_state == HPC_SLOT_EMPTY)
			hsp->hs_board_type = HPC_BOARD_CPCI_HS;
		hsp->hs_slot_state = HPC_SLOT_DISCONNECTED;
		if (flags == ALARM_CARD_ON_SLOT) {
			hsp->hs_flags |= HSC_ALARM_CARD_PRES;
			DEBUG0("Xhsc_slot_occupancy: set HSC_ALARM_CARD_PRES");
		}
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
						HPC_LED_ON);
		/*
		 * if previous occupant stayed configured, do not allow another
		 * occupant to be connected.
		 * So as soon as the board is plugged in, we turn both LEDs On.
		 * This behaviour is an indication that the slot state
		 * is not clean.
		 */
		if (hsp->hs_flags & HSC_SLOT_BAD_STATE) {
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_ON);
			return (DDI_SUCCESS);
		}

		/* Do not allow connect if slot is disabled */
		if ((hsp->hs_flags & HSC_SLOT_ENABLED) != HSC_SLOT_ENABLED)
			return (DDI_SUCCESS);
		/* if no healthy, we stay disconnected. */
		if (healthy == B_FALSE) {
			return (DDI_SUCCESS);
		}
		rc = hsc_slot_autoconnect(hsp);
		hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_BLINK);
	} else {
		/*
		 * A board was just removed
		 */
		hsp->hs_slot_state = HPC_SLOT_EMPTY;
		hsp->hs_board_type = HPC_BOARD_UNKNOWN;
		hsp->hs_flags &= ~HSC_ENUM_FAILED;
		if (hsp->hs_flags & HSC_ALARM_CARD_PRES) {
			hsp->hs_flags &= ~HSC_ALARM_CARD_PRES;
			DEBUG0("Xhsc_slot_occupancy:clear HSC_ALARM_CARD_PRES");
		}
		if (hsp->hs_board_configured == B_TRUE) {
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_SLOT_NOT_HEALTHY, 0);
			cmn_err(CE_WARN, "%s#%d: ALERT! Surprise Removal "
				" on Slot %d, Occupant Online!!",
					ddi_driver_name(hsp->hsc->dip),
					ddi_get_instance(hsp->hsc->dip),
					slot_number);
			cmn_err(CE_WARN, "%s#%d: ALERT! System now in "
				" Inconsistent State! Slot disabled. Halt!",
					ddi_driver_name(hsp->hsc->dip),
					ddi_get_instance(hsp->hsc->dip));
			/* Slot in reset and disabled */
			(void) scsb_hsc_disable_slot(hsp);
			hsp->hs_flags |= HSC_SLOT_BAD_STATE;
			/* the following works for P1.0 only. */
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
						HPC_LED_ON);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_ON);
		} else {
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
						HPC_LED_OFF);
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED,
								HPC_LED_OFF);
		}
	}
	return (rc);
}


/*
 * This function is invoked by the SCSB when the health status of
 * a board changes.
 */
/*ARGSUSED*/
int
scsb_hsc_board_healthy(int slot_number, boolean_t healthy)
{
	hsc_slot_t		*hsp;
	hsc_state_t		*hsc;

	DEBUG2("hsc_board_healthy: slot %d = %d\n", slot_number, healthy);

	hsp = hsc_find_slot(slot_number);
	if (hsp == NULL) {
		cmn_err(CE_NOTE, "hsc_board_healthy: No Slot Info.");
		return (DDI_FAILURE);
	}

	hsc = hsp->hsc;
	if (hsp->hs_slot_state == HPC_SLOT_EMPTY) {
#ifdef	DEBUG
		cmn_err(CE_NOTE, "%s#%d: Healthy# %s on "
			"empty slot %d", ddi_driver_name(hsc->dip),
			ddi_get_instance(hsc->dip),
			healthy == B_TRUE ? "On" : "Off", slot_number);
#endif
		return (DDI_FAILURE);
	}
	if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) {
		DEBUG2("healthy %s on disconnected slot %d\n",
			healthy == B_TRUE ? "On":"Off", slot_number);
		/*
		 * Connect the slot if board healthy and in autoconfig mode.
		 */
		hsp->hs_board_healthy = healthy;
		if (healthy == B_TRUE)
			return (hsc_slot_autoconnect(hsp));
	}

	/*
	 * the board is connected. The result could be seviour depending
	 * on the occupant state.
	 */
	if (healthy == B_TRUE) {
		if (hsp->hs_board_healthy != B_TRUE) {
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED,
					HPC_LED_OFF);
			/* Regained HEALTHY# at Run Time...!!! */
			cmn_err(CE_NOTE, "%s#%d: slot %d Occupant "
				"%s, Regained HEALTHY#!",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip), slot_number,
				hsp->hs_board_configured == B_TRUE ?
						"configured" : "Unconfigured");
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
				HPC_EVENT_SLOT_HEALTHY_OK, 0);
		}
	} else {
		if (hsp->hs_board_configured == B_TRUE) {
			/* Lost HEALTHY# at Run Time...Serious Condition. */
			cmn_err(CE_WARN, "%s#%d: ALERT! Lost HEALTHY#"
				" on Slot %d, Occupant %s",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip), slot_number,
					hsp->hs_board_configured == B_TRUE ?
						"Online!!!" : "Offline");
			(void) hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_SLOT_NOT_HEALTHY, 0);
		}
		if ((hsp->hs_board_configured != B_TRUE) ||
						scsb_hsc_healthy_reset) {
			if (scsb_reset_slot(hsp->hs_hpchandle,
					slot_number, SCSB_RESET_SLOT) == 0) {
				/* signal Ok to remove board. */
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
					HPC_FAULT_LED, HPC_LED_ON);
				cmn_err(CE_WARN, "%s#%d: Slot %d "
					"successfully taken offline",
					ddi_driver_name(hsc->dip),
					ddi_get_instance(hsc->dip),
					slot_number);
			}
		}
	}
	hsp->hs_board_healthy = healthy;
	return (DDI_SUCCESS);
}

static int
hsc_slot_autoconnect(hsc_slot_t *hsp)
{
	hsc_state_t *hsc = hsp->hsc;
	int rc = DDI_SUCCESS;
	/*
	 * Keep slot in reset unless autoconfiguration is enabled
	 * Ie. for Basic Hotswap mode, we stay disconnected at
	 * insertion. For full hotswap mode, we automatically
	 * go into connected state at insertion, so that occupant
	 * autoconfiguration is possible.
	 */
	if (((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) &&
			(hsp->hs_flags & HSC_AUTOCFG)) {
		/* this statement must be here before unreset. */
		hsc->hsp_last = hsp;
		if ((rc = scsb_reset_slot(hsp->hs_hpchandle,
			hsp->hs_slot_number, SCSB_UNRESET_SLOT)) == 0) {

			hsp->hs_slot_state = HPC_SLOT_CONNECTED;
			hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
					HPC_FAULT_LED, HPC_LED_OFF);
		} else {
			hsc->hsp_last = NULL;
			rc = DDI_FAILURE;
		}
	}
	return (rc);
}

/*
 * The SCSB code should invoke this function from its _init() function.
 */
int
hsc_init()
{
	int rc;

	rc = ddi_soft_state_init(&hsc_state, sizeof (hsc_state_t), 1);
	if (rc != 0)
		return (rc);

	hsc_slotops = hpc_alloc_slot_ops(KM_SLEEP);

	hsc_slotops->hpc_version	= HPC_SLOT_OPS_VERSION;
	hsc_slotops->hpc_op_connect	= hsc_connect;
	hsc_slotops->hpc_op_disconnect	= hsc_disconnect;
	hsc_slotops->hpc_op_insert	= hsc_insert;
	hsc_slotops->hpc_op_remove	= hsc_remove;
	hsc_slotops->hpc_op_control	= hsc_control;

	return (DDI_SUCCESS);
}


/*
 * The SCSB code should invoke this function from its _fini() function.
 */
int
hsc_fini()
{
	if (hsc_slotops != NULL) {
		hpc_free_slot_ops(hsc_slotops);
		hsc_slotops = NULL;
	}
	ddi_soft_state_fini(&hsc_state);
	return (DDI_SUCCESS);
}

static int
scsb_enable_enum(hsc_state_t *hsc)
{
	DEBUG0("hsc: Enable ENUM#\n");

	if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED)
		return (DDI_SUCCESS);
	if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED)
		return (DDI_FAILURE);

	if (ddi_add_intr(hsc->dip, 1, NULL, NULL,
			hsc_enum_intr, (caddr_t)hsc) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s#%d: failed ENUM# interrupt registration",
			ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip));
		return (DDI_FAILURE);
	}
	cmn_err(CE_CONT, "?%s%d: Successfully Upgraded to "
			"Full Hotswap Mode\n", ddi_driver_name(hsc->dip),
			ddi_get_instance(hsc->dip));
	hsc->state |= HSC_ENUM_ENABLED;
	(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip,
	    HOTSWAP_MODE_PROP, "full");
	return (DDI_SUCCESS);

}

/*ARGSUSED*/
static int
scsb_disable_enum(hsc_state_t *hsc, int op)
{

	DEBUG0("hsc: Disable ENUM#\n");
	if (op == SCSB_HSC_FORCE_REMOVE) {
		/*
		 * Clear all pending interrupts before unregistering
		 * the interrupt. Otherwise the system will hang.
		 *
		 * Due to the hang problem, we'll not turn off or disable
		 * interrupts because if there's a non-friendly full hotswap
		 * device out there, the ENUM# will be kept asserted and
		 * hence hsc_clear_all_enum() can never deassert ENUM#.
		 * So the system will hang.
		 */
		if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) {
			/* hsc_clear_all_enum(hsc); */
			ddi_remove_intr(hsc->dip, 1, NULL);
			hsc->state &= ~HSC_ENUM_ENABLED;
			cmn_err(CE_CONT, "?%s%d: Successfully Downgraded to "
				"Basic Hotswap Mode\n",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip));
		}
		(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip,
		    HOTSWAP_MODE_PROP, "basic");
		return (DDI_SUCCESS);
	} else
		/* No programming interface for disabling ENUM# on MC/Tonga */
		return (HPC_ERR_NOTSUPPORTED);
}

#ifndef	lint
static int
hsc_clear_all_enum(hsc_state_t *hsc)
{
	int i, rc;
	hsc_slot_t *hsp;

	for (i = 0; i < hsc->slot_table_size; i++) {

		hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum);
		if (hsp == NULL)
			continue;
		rc = hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_CLEAR_ENUM,
						HPC_EVENT_SYNCHRONOUS);
		if (rc == HPC_EVENT_UNCLAIMED)
			break;	/* no pending interrupts across the bus */
		DEBUG1("Pending Intr on slot %d\n",
			hsc->slot_table_prop[i].pslotnum);
	}
	return (0);
}
#endif

int
scsb_hsc_attach(dev_info_t *dip, void *scsb_handle, int instance)
{
	int i, n, prop_len;
	int prom_prop = 0;	/* default: OS property gives slot-table */
	int rc;
	char *hotswap_model;
	hsc_state_t	*hsc;
	scsb_state_t	*scsb = (scsb_state_t *)scsb_handle;
	caddr_t hpc_slot_table_data, s;
	int hpc_slot_table_size;
	hsc_prom_slot_table_t	*hpstp;
	int rstate;

	DEBUG0("hsc_attach: enter\n");
	/*
	 * To get the slot information,
	 * The OBP defines the 'slot-table' property. But the OS
	 * can override it with 'hsc-slot-map' property
	 * through the .conf file.
	 * Since the formats are different, 2 different property names
	 * are chosen.
	 * The OBP property format is
	 * <phandle>,<pci-devno>,<phys-slotno>,<ga-bits>
	 * The OS property format is (ga-bits is not used however)
	 * <busnexus-path>,<pci-devno>,<phys-slotno>,<ga-bits>
	 */
	rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		"hsc-slot-map", (caddr_t)&hpc_slot_table_data,
		&hpc_slot_table_size);
	if (rc != DDI_PROP_SUCCESS)  {
		prom_prop = 1;
		rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
			"slot-table", (caddr_t)&hpc_slot_table_data,
			&hpc_slot_table_size);
		if (rc != DDI_PROP_SUCCESS) {
			cmn_err(CE_WARN, "%s#%d: 'slot-table' property "
				"missing!", ddi_driver_name(dip),
						ddi_get_instance(dip));
			return (DDI_FAILURE);
		}
	}
	rc = ddi_soft_state_zalloc(hsc_state, instance);
	if (rc != DDI_SUCCESS)
		return (DDI_FAILURE);

	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	hsc->scsb_handle = scsb_handle;
	hsc->dip = dip;
	hsc->instance = instance;
	hsc->n_registered_occupants = 0;
	hsc->regDone = B_FALSE;
	/* hsc->slot_info = hsc_slot_list; */

	/*
	 * Check whether the system should be in basic or full
	 * hotswap mode. The PROM property always says full, so
	 * look at the .conf file property whether this is "full"
	 */
	if (scsb_hsc_enable_fhs) {
		hsc->hotswap_mode = HSC_HOTSWAP_MODE_FULL;
	} else {
		hsc->hotswap_mode = HSC_HOTSWAP_MODE_BASIC;
	}

	rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		"default-hotswap-mode", (caddr_t)&hotswap_model, &prop_len);

	if (rc == DDI_PROP_SUCCESS) {
		if (strcmp(hotswap_model, "full") == 0) {
			hsc->hotswap_mode = HSC_HOTSWAP_MODE_FULL;
		} else if (strcmp(hotswap_model, "basic") == 0) {
			hsc->hotswap_mode = HSC_HOTSWAP_MODE_BASIC;
		}

		kmem_free(hotswap_model, prop_len);
	}

	/*
	 * Determine the size of the slot table from the property and
	 * allocate the slot table arrary..Decoding is different for
	 * OS and PROM property.
	 */
	if (!prom_prop) {	/* OS .conf property */
		for (i = 0, n = 0; i < hpc_slot_table_size; i++) {
			if (hpc_slot_table_data[i] == 0) {
				n++;
			}
		}

		/* There should be four elements per entry */
		if (n % 4) {
			cmn_err(CE_WARN, "%s#%d: bad format for "
				"slot-table(%d)", ddi_driver_name(dip),
						ddi_get_instance(dip), n);
			kmem_free(hpc_slot_table_data, hpc_slot_table_size);
			ddi_soft_state_free(hsc_state, instance);
			return (DDI_FAILURE);
		}

		hsc->slot_table_size = n / 4;
	} else {
		hsc->slot_table_size = hpc_slot_table_size /
						sizeof (hsc_prom_slot_table_t);
		n = hpc_slot_table_size % sizeof (hsc_prom_slot_table_t);
		if (n) {
			cmn_err(CE_WARN, "%s#%d: bad format for "
				"slot-table(%d)", ddi_driver_name(dip),
				ddi_get_instance(dip), hpc_slot_table_size);
			kmem_free(hpc_slot_table_data, hpc_slot_table_size);
			ddi_soft_state_free(hsc_state, instance);
			return (DDI_FAILURE);
		}
	}

	/*
	 * Netract800 FTC (formerly known as CFTM) workaround.
	 * Leave Slot 2 out of the HS table if FTC is present in Slot 2
	 */
	if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES) {
		hsc->slot_table_size -= 1;
	}
	DEBUG1("hsc_attach: %d hotplug slots on bus\n", hsc->slot_table_size);
	/*
	 * Create enough space for each slot table entry
	 * based on how many entries in the property
	 */
	hsc->slot_table_prop = (hsc_slot_table_t *)
		kmem_zalloc(hsc->slot_table_size *
			sizeof (hsc_slot_table_t), KM_SLEEP);

	if (!prom_prop) {
		s = hpc_slot_table_data;
		for (i = 0; i < hsc->slot_table_size; i++) {

			char *nexus, *pcidev, *phys_slotname, *ga;

			/* Pick off pointer to nexus path or PROM handle */
			nexus = s;
			while (*s != NULL)
				s++;
			s++;

			/* Pick off pointer to the pci device number */
			pcidev = s;
			while (*s != NULL)
				s++;
			s++;

			/* Pick off physical slot no */
			phys_slotname = s;
			while (*s != NULL)
				s++;
			s++;

			/* Pick off GA bits which we dont use for now. */
			ga = s;
			while (*s != NULL)
				s++;
			s++;

			if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES &&
					atoi(phys_slotname) == SC_MC_CTC_SLOT) {
				--i;
				continue;
			}
			hsc->slot_table_prop[i].pslotnum = atoi(phys_slotname);
			hsc->slot_table_prop[i].ga = atoi(ga);
			hsc->slot_table_prop[i].pci_devno = atoi(pcidev);
			(void) strcpy(hsc->slot_table_prop[i].nexus, nexus);
		}
	} else {
		hpstp = (hsc_prom_slot_table_t *)hpc_slot_table_data;
		for (i = 0; i < hsc->slot_table_size; i++, hpstp++) {
			if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES &&
					hpstp->pslotnum == SC_MC_CTC_SLOT) {
				--i;
				continue;
			}
			hsc->slot_table_prop[i].pslotnum = hpstp->pslotnum;
			hsc->slot_table_prop[i].ga = hpstp->ga;
			hsc->slot_table_prop[i].pci_devno = hpstp->pci_devno;

			if (prom_phandle_to_path((uint_t)hpstp->phandle,
				hsc->slot_table_prop[i].nexus,
				sizeof (hsc->slot_table_prop[i].nexus))
						== -1) {
				cmn_err(CE_WARN, "%s#%d: Cannot get phandle "
					"to nexus path", ddi_driver_name(dip),
					ddi_get_instance(dip));
				kmem_free(hsc->slot_table_prop,
					(hsc->slot_table_size *
						sizeof (hsc_slot_table_t)));
				kmem_free(hpc_slot_table_data,
						hpc_slot_table_size);
				ddi_soft_state_free(hsc_state, instance);
				return (DDI_FAILURE);
			}
		}
	}

	/* keep healthy register cache uptodate before reading slot state */
	if (scsb_read_bhealthy(scsb_handle) != 0) {
		cmn_err(CE_WARN, "%s#%d: hsc_attach: Cannot read "
			"Healthy Registers", ddi_driver_name(dip),
				ddi_get_instance(dip));
		kmem_free(hsc->slot_table_prop,
			(hsc->slot_table_size *
				sizeof (hsc_slot_table_t)));
		kmem_free(hpc_slot_table_data,
				hpc_slot_table_size);
		ddi_soft_state_free(hsc_state, instance);
		return (DDI_FAILURE);
	}

	/*
	 * Before we start registering the slots, calculate how many
	 * slots are occupied.
	 */

	for (i = 0; i < hsc->slot_table_size; i++) {
		if (scsb_get_slot_state(scsb_handle,
				hsc->slot_table_prop[i].pslotnum, &rstate) !=
				DDI_SUCCESS)
				return (rc);
		if (rstate != HPC_SLOT_EMPTY)
			hsc->n_registered_occupants++;
	}

	mutex_init(&hsc->hsc_mutex, NULL, MUTEX_DRIVER, NULL);
	for (i = 0; i < hsc->slot_table_size; i++) {

		DEBUG2("Registering on nexus [%s] cPCI device [%d]\n",
			hsc->slot_table_prop[i].nexus,
			hsc->slot_table_prop[i].pci_devno);

		if (hsc_slot_register(hsc, hsc->slot_table_prop[i].nexus,
			hsc->slot_table_prop[i].pci_devno,
			hsc->slot_table_prop[i].pslotnum, B_FALSE) !=
								HPC_SUCCESS) {

			cmn_err(CE_WARN, "%s#%d: Slot Registration Failure",
				ddi_driver_name(dip), ddi_get_instance(dip));
			while (i) {
				i--;
				n = hsc->slot_table_prop[i].pslotnum;
				if (hsc_slot_unregister(n) != 0) {
					cmn_err(CE_WARN,
						"%s#%d: failed to unregister"
						" slot %d",
						ddi_driver_name(dip),
						ddi_get_instance(dip), n);

				}
			}
			mutex_destroy(&hsc->hsc_mutex);
			kmem_free(hsc->slot_table_prop, (hsc->slot_table_size *
					sizeof (hsc_slot_table_t)));
			kmem_free(hpc_slot_table_data, hpc_slot_table_size);
			ddi_soft_state_free(hsc_state, instance);
			return (DDI_FAILURE);
		}
	}

	hsc->hsp_last = NULL;
	hsc->hsc_intr_counter = 0;
	kmem_free(hpc_slot_table_data, hpc_slot_table_size);
	(void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip,
	    HOTSWAP_MODE_PROP, "basic");
	hsc->state |= (HSC_ATTACHED|HSC_SCB_CONNECTED);

	/*
	 * We enable full hotswap right here if all the slots are empty.
	 */
	if ((hsc->regDone == B_FALSE && hsc->n_registered_occupants == 0) ||
			scsb_hsc_numReg == hsc->n_registered_occupants) {
		hsc->regDone = B_TRUE;
		if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL) {
			if (scsb_enable_enum(hsc) != DDI_SUCCESS) {
				cmn_err(CE_WARN, "%s#%d: Cannot enable "
					"Full Hotswap", ddi_driver_name(dip),
					ddi_get_instance(dip));
			}
		}
	}
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
int
scsb_hsc_detach(dev_info_t *dip, void *scsb_handle, int instance)
{
	int i = 0;
	hsc_state_t	*hsc;
	char slotautocfg_prop[18];

	DEBUG0("hsc_detach: enter\n");
	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	if (hsc == NULL) {
		DEBUG2("%s#%d: hsc_detach: Soft state NULL",
				ddi_driver_name(dip), ddi_get_instance(dip));
		return (DDI_FAILURE);
	}

	if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED)
		return (DDI_FAILURE);
	/*
	 * let's unregister the hotpluggable slots with hotplug service.
	 */
	for (i = 0; i < hsc->slot_table_size; i++) {

		hsc_slot_t	*hsp;

		hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum);
		if (hsp == NULL) {
			cmn_err(CE_WARN, "%s#%d: hsc_detach: No Slot Info",
				ddi_driver_name(dip), ddi_get_instance(dip));
		} else {
			hpc_led_info_t	aledinfo;	/* active led info. */
			hpc_led_info_t	fledinfo;	/* fault led info. */

			aledinfo.led = HPC_ACTIVE_LED;
			aledinfo.state = HPC_LED_BLINK;
			fledinfo.led = HPC_FAULT_LED;
			fledinfo.state = HPC_LED_OFF;
			(void) hsc_led_state(hsp, HPC_CTRL_SET_LED_STATE,
							&aledinfo);
			(void) hsc_led_state(hsp, HPC_CTRL_SET_LED_STATE,
							&fledinfo);
		}
		(void) sprintf(slotautocfg_prop, "slot%d-autoconfig",
		    hsp->hs_slot_number);
		(void) ddi_prop_remove(DDI_DEV_T_NONE, hsc->dip,
		    slotautocfg_prop);
		if (hsc_slot_unregister(hsc->slot_table_prop[i].pslotnum)
						!= 0) {
			cmn_err(CE_NOTE, "%s#%d: failed to unregister"
				" slot %d\n", ddi_driver_name(dip),
				ddi_get_instance(dip),
				hsc->slot_table_prop[i].pslotnum);
			return (DDI_FAILURE);
		}
	}
	kmem_free(hsc->slot_table_prop, (hsc->slot_table_size *
					sizeof (hsc_slot_table_t)));
	if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) {
		ddi_remove_intr(hsc->dip, 1, hsc->enum_iblock);
		hsc->state &= ~HSC_ENUM_ENABLED;
	}
	mutex_destroy(&hsc->hsc_mutex);
	(void) ddi_prop_remove(DDI_DEV_T_NONE, hsc->dip, HOTSWAP_MODE_PROP);
	hsc->state &= ~(HSC_ATTACHED|HSC_SCB_CONNECTED);
	ddi_soft_state_free(hsc_state, instance);
	return (DDI_SUCCESS);
}

/*
 * The following function is called when the SCSB is hot extracted from
 * the system.
 */
int
scsb_hsc_freeze(dev_info_t *dip)
{
	hsc_state_t	*hsc;
	int instance = ddi_get_instance(dip);
	int i;
	hsc_slot_t	*hsp;

	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	if (hsc == NULL) {
		DEBUG2("%s#%d: Soft state NULL",
				ddi_driver_name(dip), ddi_get_instance(dip));
		return (DDI_SUCCESS);
	}
	if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED)
		return (DDI_SUCCESS);
	hsc->state &= ~HSC_SCB_CONNECTED;

	for (i = 0; i < hsc->slot_table_size; i++) {
		hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum);

		if (hsp == NULL) {
			cmn_err(CE_NOTE, "hsc_freeze: "
				" Cannot map slot number %d to a hsc_slot_t",
					hsc->slot_table_prop[i].pslotnum);
			continue;
		}
		/*
		 * Since reset lines are pulled low, lets mark these
		 * slots and not allow a connect operation.
		 * Note that we still keep the slot as slot disconnected,
		 * although it is connected from the hardware standpoint.
		 * As soon as the SCB is plugged back in, we check these
		 * states and put the hardware state back to its original
		 * state.
		 */
		if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) {
			cmn_err(CE_WARN, "%s#%d: Slot %d Now out of Reset!",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip),
				hsp->hs_slot_number);
		}
		hsp->hs_flags |= HSC_SCB_HOTSWAPPED;
	}

	return (DDI_SUCCESS);
}

/*
 * The following function is called when the SCSB is hot inserted from
 * the system. We must update the LED status and set the RST# registers
 * again.
 */
int
scsb_hsc_restore(dev_info_t *dip)
{
	int i;
	hsc_state_t	*hsc;
	hsc_slot_t	*hsp;
	int instance = ddi_get_instance(dip);

	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	if (hsc == NULL) {
		DEBUG2("%s#%d: Soft state NULL",
				ddi_driver_name(dip), ddi_get_instance(dip));
		return (DDI_SUCCESS);
	}

	if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED)
		return (DDI_SUCCESS);
	hsc->state |= HSC_SCB_CONNECTED;
	for (i = 0; i < hsc->slot_table_size; i++) {
		hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum);

		if (hsp == NULL) {
			cmn_err(CE_NOTE, "%s#%d: hsc_restore: "
				" Cannot map slot number %d to a hsc_slot_t",
					ddi_driver_name(hsc->dip),
					ddi_get_instance(hsc->dip),
					hsc->slot_table_prop[i].pslotnum);
			continue;
		}
		if ((hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) &&
				(hsp->hs_board_configured == B_FALSE)) {
			if (scsb_reset_slot(hsp->hs_hpchandle,
					hsp->hs_slot_number,
					SCSB_RESET_SLOT) != 0) {
				cmn_err(CE_WARN, "%s#%d: hsc_restore: "
					" Cannot reset disconnected slot %d",
						ddi_driver_name(hsc->dip),
						ddi_get_instance(hsc->dip),
						hsp->hs_slot_number);
			}
		}

		if (scsb_hsc_init_slot_state(hsc, hsp) != DDI_SUCCESS) {

			cmn_err(CE_WARN, "%s#%d: hsc_freeze: Cannot init"
				" slot%d state",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip),
				hsp->hs_slot_number);
		}
		hsp->hs_flags &= ~HSC_SCB_HOTSWAPPED;
	}
	return (DDI_SUCCESS);
}

#ifndef	lint
int
scsb_hsc_freeze_check(dev_info_t *dip)
{
	hsc_state_t	*hsc;
	int instance = ddi_get_instance(dip);

	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	if (hsc == NULL) {
		DEBUG2("%s#%d: Soft state NULL",
				ddi_driver_name(dip), ddi_get_instance(dip));
		return (DDI_SUCCESS);
	}
	if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED)
		return (DDI_SUCCESS);
	return (DDI_SUCCESS);
}
#endif

/*
 * update info about Alarm Card insert/remove mechanism.
 */
void
hsc_ac_op(int instance, int pslotnum, int op, void *arg)
{
	hsc_slot_t *hsp;
	hsc_state_t	*hsc;

	hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance);
	if (hsc == NULL) {
		cmn_err(CE_WARN, "%s#%d: hsc_ac_op: No Soft State Info",
			ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip));
		return;
	}

	hsp = hsc_find_slot(pslotnum);
	if (hsp == NULL) {
		cmn_err(CE_WARN, "%s#%d: hsc_ac_op: No Slot Info",
			ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip));
		return;
	}

	switch (op) {
		case SCSB_HSC_AC_UNCONFIGURE :
			/*
			 * If ENUM# is enabled, then action is pending on
			 * this slot, just send a event.
			 */
			if (hsc->state & HSC_ENUM_ENABLED)
				(void) hpc_slot_event_notify(
				    hsp->hs_slot_handle,
				    HPC_EVENT_PROCESS_ENUM, 0);
			break;
		case SCSB_HSC_AC_GET_SLOT_INFO :
			*(hsc_slot_t **)arg = hsp;
			break;
		default :
			break;
	}
}

static uint_t
hsc_enum_intr(caddr_t iarg)
{
	int rc;
	hsc_state_t *hsc = (hsc_state_t *)iarg;
	hsc_slot_t *hsp;

	DEBUG0("!E!");
	if ((hsc->state & HSC_ATTACHED) == 0)
		return (DDI_INTR_UNCLAIMED);

	hsp = hsc_find_slot(hsc->slot_table_prop[0].pslotnum);
	if (hsp == NULL)	/* No slots registered */
		return (DDI_INTR_UNCLAIMED);

	/*
	 * The following must be done to clear interrupt (synchronous event).
	 * To process the interrupt, we send an asynchronous event.
	 */
	rc = hpc_slot_event_notify(hsp->hs_slot_handle,
					HPC_EVENT_CLEAR_ENUM,
						HPC_EVENT_SYNCHRONOUS);
	if (rc == HPC_EVENT_UNCLAIMED) {
		/*
		 * possible support for handling insertion of non friendly
		 * full hotswap boards, otherwise the system hangs due
		 * to uncleared interrupt bursts.
		 */
		DEBUG2("!E>counter %d, last op@slot %lx\n",
				hsc->hsc_intr_counter, hsc->hsp_last);
		hsc->hsc_intr_counter ++;
		if (hsc->hsc_intr_counter == scsb_hsc_max_intr_count) {
			if (!hsc->hsp_last) {
				cmn_err(CE_WARN, "%s#%d: hsc_enum_intr: "
					" No Last Board Insertion Info.",
					ddi_driver_name(hsc->dip),
					ddi_get_instance(hsc->dip));
				hsc->hsc_intr_counter = 0;
				return (DDI_INTR_UNCLAIMED);
			}
			hsp = hsc->hsp_last;
			cmn_err(CE_WARN, "%s#%d: Bad (non friendly ?) Board "
				"in Slot %d ? Taking it Offline.",
				ddi_driver_name(hsc->dip),
				ddi_get_instance(hsc->dip),
				hsp->hs_slot_number);
			/*
			 * this should put just inserted board back in
			 * reset, thus deasserting the ENUM# and the
			 * system hang.
			 */
			if (scsb_reset_slot(hsp->hs_hpchandle,
					hsp->hs_slot_number,
					SCSB_RESET_SLOT) == 0) {
				/* Enumeration failed on this board */
				hsp->hs_flags |= HSC_ENUM_FAILED;
				if (hsp->hs_board_configured == B_TRUE)
					cmn_err(CE_WARN, "%s#%d: ALERT! System"
						" now in Inconsistent State."
						" Halt!",
					    ddi_driver_name(hsc->dip),
					    ddi_get_instance(hsc->dip));
				hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE,
						HPC_FAULT_LED, HPC_LED_ON);
			}
			hsc->hsc_intr_counter = 0;
		}
		return (DDI_INTR_UNCLAIMED);
	}
	hsc->hsc_intr_counter = 0;
	/*
	 * if interrupt success, rc denotes the PCI device number which
	 * generated the ENUM# interrupt.
	 */
	hsp = hsc_get_slot_info(hsc, rc);
	if (hsp == NULL) {
		cmn_err(CE_WARN, "%s#%d: hsc_enum_intr: no slot info for "
			"dev %x", ddi_driver_name(hsc->dip),
			ddi_get_instance(hsc->dip), rc);
		return (DDI_INTR_CLAIMED);	/* interrupt already cleared */
	}
	/* if this is Alarm Card and if it is busy, dont process event */
	if (hsp->hs_flags & HSC_ALARM_CARD_PRES) {
		if (scsb_hsc_ac_op(hsp->hs_hpchandle, hsp->hs_slot_number,
						SCSB_HSC_AC_BUSY) == B_TRUE) {
			/*
			 * Busy means we need to inform (envmond)alarmcard.so
			 * that it should save the AC configuration, stop the
			 * heartbeat, and shutdown the RSC link.
			 */
			(void) scsb_hsc_ac_op(hsp->hs_hpchandle,
					hsp->hs_slot_number,
					SCSB_HSC_AC_REMOVAL_ALERT);
			return (DDI_INTR_CLAIMED);
		}
	}
	/*
	 * If SCB was swapped out, dont process ENUM#. We put this slot
	 * back in reset after SCB is inserted.
	 */
	if ((hsp->hs_flags & HSC_SCB_HOTSWAPPED) &&
			(hsp->hs_slot_state == HPC_SLOT_DISCONNECTED))
		return (DDI_INTR_CLAIMED);

	(void) hpc_slot_event_notify(hsp->hs_slot_handle,
	    HPC_EVENT_PROCESS_ENUM, 0);
	return (DDI_INTR_CLAIMED);
}
/*
 * A routine to convert a number (represented as a string) to
 * the integer value it represents.
 */

static int
isdigit(int ch)
{
	return (ch >= '0' && ch <= '9');
}

#define	isspace(c)	((c) == ' ' || (c) == '\t' || (c) == '\n')
#define	bad(val)	(val == NULL || !isdigit(*val))

static int
atoi(const char *p)
{
	int n;
	int c, neg = 0;

	if (!isdigit(c = *p)) {
		while (isspace(c))
			c = *++p;
		switch (c) {
			case '-':
				neg++;
				/* FALLTHROUGH */
			case '+':
			c = *++p;
		}
		if (!isdigit(c))
			return (0);
	}
	for (n = '0' - c; isdigit(c = *++p); ) {
		n *= 10; /* two steps to avoid unnecessary overflow */
		n += '0' - c; /* accum neg to avoid surprises at MAX */
	}
	return (neg ? n : -n);
}