root/usr/src/uts/sun4u/sunfire/io/sysctrl_dr.c
/*
 * 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 2005 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/sunndi.h>
#include <sys/ddi_impldefs.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/machsystm.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/cpr.h>
#include <sys/cpuvar.h>
#include <sys/machcpuvar.h>
#include <sys/x_call.h>

#ifdef DEBUG
struct  regs_data {
        caddr_t msg;
        u_longlong_t physaddr;
        uint_t pre_dsct;
        uint_t post_dsct;
        uint_t eflag;
        uint_t oflag;
};

static  struct regs_data reg_tmpl[] = {
        "AC  Control and Status reg = 0x", AC_BCSR(0), 0, 0, 0, 0,
        "FHC Control and Status reg = 0x", FHC_CTRL(0), 0, 0, 0, 0,
        "JTAG Control reg = 0x", FHC_JTAG_CTRL(0), 0, 0, 0, 0,
        "Interrupt Group Number reg = 0x", FHC_IGN(0), 0, 0, 0, 0,
        "System Interrupt Mapping reg = 0x", FHC_SIM(0), 0, 0, 0, 0,
        "System Interrupt State reg = 0x", FHC_SSM(0), 0, 0, 0, 0,
        "UART Interrupt Mapping reg = 0x", FHC_UIM(0), 0, 0, 0, 0,
        "UART Interrupt State reg = 0x", FHC_USM(0), 0, 0, 0, 0
};

#define NUM_REG  (sizeof (reg_tmpl)/sizeof (reg_tmpl[0]))
static  struct regs_data reg_dt[MAX_BOARDS][NUM_REG];

int sysctrl_enable_regdump = 0;

static void precache_regdump(int board);
static void display_regdump(void);
static void boardstat_regdump(void);

#endif /* DEBUG */

extern void bd_remove_poll(struct sysctrl_soft_state *);
extern int sysctrl_getsystem_freq(void);
extern enum power_state compute_power_state(struct sysctrl_soft_state *, int);
extern enum temp_state fhc_env_temp_state(int);
extern int sysctrl_hotplug_disabled;
/* Let user disable Sunfire Dynamic Reconfiguration */
int enable_dynamic_reconfiguration = 1;

int enable_redist = 1;

static void sysc_dr_err_decode(sysc_dr_handle_t *, dev_info_t *, int);
static uint_t
sysc_policy_enough_cooling(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t ps_mutex_is_held);
static uint_t
sysc_policy_enough_precharge(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat);
static uint_t
sysc_policy_enough_power(struct sysctrl_soft_state *softsp,
        int plus_load, uint_t ps_mutex_is_held);
static uint_t
sysc_policy_hardware_compatible(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, sysc_cfga_pkt_t *pkt);
static void sysc_policy_empty_condition(
        struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t failure,
        uint_t ps_mutex_is_held);
static void sysc_policy_disconnected_condition(
        struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t failure,
        uint_t ps_mutex_is_held);
static void sysc_policy_connected_condition(struct sysctrl_soft_state *softsp,
                sysc_cfga_stat_t *sysc_stat,
                uint_t ps_mutex_is_held);
static void sysc_policy_set_condition(void *sp, sysc_cfga_stat_t *sysc_stat,
                                uint_t ps_mutex_is_held);
static void sysc_policy_audit_messages(sysc_audit_evt_t event,
                sysc_cfga_stat_t *sysc_stat);

static void sysctrl_post_config_change(struct sysctrl_soft_state *softsp);
static int sysc_bd_connect(int, sysc_cfga_pkt_t *);
static int sysc_bd_disconnect(int, sysc_cfga_pkt_t *);
static int sysc_bd_configure(int, sysc_cfga_pkt_t *);
static int sysc_bd_unconfigure(int, sysc_cfga_pkt_t *);

static void sysc_dr_init(sysc_dr_handle_t *handle);
static void sysc_dr_uninit(sysc_dr_handle_t *handle);
static int sysc_dr_attach(sysc_dr_handle_t *handle, int board);
static int sysc_dr_detach(sysc_dr_handle_t *handle, int board);

static int sysc_prom_select(pnode_t pnode, void *arg, uint_t flag);
static void sysc_branch_callback(dev_info_t *rdip, void *arg, uint_t flags);

static int find_and_setup_cpu(int);

static int sysc_board_connect_supported(enum board_type);

static int find_and_setup_cpu_start(void *cpuid_arg, int has_changed);
/*
 * This function will basically do a prediction on the power state
 * based on adding one additional load to the equation implemented
 * by the function compute_power_state.
 */
/*ARGSUSED*/
static uint_t
sysc_policy_enough_power(struct sysctrl_soft_state *softsp,
        int plus_load, uint_t ps_mutex_is_held)
{
        int retval = 0;

        ASSERT(softsp);

        if (!ps_mutex_is_held) {
                mutex_enter(&softsp->ps_fail_lock);
        }

        /*
         * note that we add one more load
         * to the equation in compute_power_state
         * and the answer better be REDUNDANT or
         * MINIMUM before proceeding.
         */
        switch (compute_power_state(softsp, plus_load)) {
                case REDUNDANT:
                case MINIMUM:
                        retval = 1;
                        break;
                case BELOW_MINIMUM:
                default:
                        break;
        }

        if (!ps_mutex_is_held) {
                mutex_exit(&softsp->ps_fail_lock);
        }
        return (retval);
}

/*
 * This function gropes through the shadow registers in the sysctrl soft_state
 * for the core power supply status, since fan status for them are ORed into
 * the same status bit, and all other remaining fans.
 */
static uint_t
sysc_policy_enough_cooling(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t ps_mutex_is_held)
{
        int     retval = 0;

        if (!ps_mutex_is_held) {
                mutex_enter(&softsp->ps_fail_lock);
        }

        /*
         * check the power supply in the slot in question
         * for fans then check all the common fans.
         */
        retval = ((softsp->ps_stats[FHC_BOARD2PS(sysc_stat->board)].pshadow ==
                        PRES_IN) &&
                (softsp->ps_stats[FHC_BOARD2PS(sysc_stat->board)].dcshadow ==
                        PS_OK));
        if (!ps_mutex_is_held) {
                mutex_exit(&softsp->ps_fail_lock);
        }
        return (retval);
}

/*
 * This function will check all precharge voltage status.
 */
/*ARGSUSED*/
static uint_t
sysc_policy_enough_precharge(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat)
{
        int     retval = 0;
        int     ppsval = 0;

        mutex_enter(&softsp->ps_fail_lock);

                /*
                 *      note that we always have to explicitly check
                 *      the peripheral power supply for precharge since it
                 *      supplies all of the precharge voltages.
                 */
        ppsval = (softsp->ps_stats[SYS_PPS0_INDEX].pshadow == PRES_IN) &&
                (softsp->ps_stats[SYS_PPS0_INDEX].dcshadow == PS_OK);

                /*
                 *      check all the precharge status
                 */
        retval = ((softsp->ps_stats[SYS_V3_PCH_INDEX].pshadow == PRES_IN) &&
                (softsp->ps_stats[SYS_V3_PCH_INDEX].dcshadow == PS_OK) &&
                (softsp->ps_stats[SYS_V5_PCH_INDEX].pshadow == PRES_IN) &&
                (softsp->ps_stats[SYS_V5_PCH_INDEX].dcshadow == PS_OK));

        mutex_exit(&softsp->ps_fail_lock);
        return (retval&&ppsval);
}

static int Fsys;

/*
 * This function should only be called once as we may
 * zero the clock board registers to indicate a configuration change.
 * The code to calculate the bus frequency has been removed and we
 * read the eeprom property instead. Another static Fmod (module
 * frequency may be needed later but so far it is commented out.
 */
void
set_clockbrd_info(void)
{
        uint_t clock_freq = 0;

        pnode_t root = prom_nextnode((pnode_t)0);
        (void) prom_getprop(root, "clock-frequency", (caddr_t)&clock_freq);
        Fsys = clock_freq / 1000000;
}

#define abs(x)  ((x) < 0 ? -(x) : (x))

/*ARGSUSED*/
static uint_t
sysc_policy_hardware_compatible(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, sysc_cfga_pkt_t *pkt)
{
        int status;

        ASSERT(Fsys > 0);

        /* Only allow DR operations on supported hardware */
        switch (sysc_stat->type) {
        case CPU_BOARD: {
#ifdef RFE_4174486
                int i;
                int cpu_freq;
                int sram_mode;

                ASSERT(Fmod > 0);

                cpu_freq = CPU->cpu_type_info.pi_clock;

                if (abs(cpu_freq - Fmod) < 8)
                        sram_mode = 1;
                else
                        sram_mode = 2;

                status = TRUE;
                for (i = 0; i < 2; i++) {
                        /*
                         * XXX: Add jtag code which rescans disabled boards.
                         * For the time being disabled boards are not
                         * checked for compatibility when cpu_speed is 0.
                         */
                        if (sysc_stat->bd.cpu[i].cpu_speed == 0)
                                continue;

                        if (sysc_stat->bd.cpu[i].cpu_speed < cpu_freq) {
                                cmn_err(CE_WARN, "board %d, cpu module %c "
                                        "rated at %d Mhz, system freq %d Mhz",
                                        sysc_stat->board, (i == 0) ? 'A' : 'B',
                                        sysc_stat->bd.cpu[i].cpu_speed,
                                        cpu_freq);
                                status = FALSE;
                        }

                        if (sram_mode != sysc_stat->bd.cpu[i].cpu_sram_mode) {
                                cmn_err(CE_WARN, "board %d, cpu module %c "
                                        "incompatible sram mode of %dx, "
                                        "system is %dx", sysc_stat->board,
                                        (i == 0) ? 'A' : 'B',
                                        sysc_stat->bd.cpu[i].cpu_sram_mode,
                                        sram_mode);
                                status = FALSE;
                        }
                }
                break;
#endif /* RFE_4174486 */
        }

        case MEM_BOARD:
        case IO_2SBUS_BOARD:
        case IO_SBUS_FFB_BOARD:
        case IO_PCI_BOARD:
        case IO_2SBUS_SOCPLUS_BOARD:
        case IO_SBUS_FFB_SOCPLUS_BOARD:
                status = TRUE;
                break;

        case CLOCK_BOARD:
        case DISK_BOARD:
        default:
                status = FALSE;         /* default is not supported */
                break;
        }

        if (status == FALSE)
                return (status);

        /* Check for Sunfire boards in a Sunfire+ system */
        if (status == TRUE && Fsys > 84 && !fhc_bd_is_plus(sysc_stat->board)) {
                (void) snprintf(pkt->errbuf, SYSC_OUTPUT_LEN,
                    "not 100 MHz capable   ");
                cmn_err(CE_WARN, "board %d, is not capable of running at "
                    "current system clock (%dMhz)", sysc_stat->board, Fsys);

                status = FALSE;
        }

        return (status);
}

/*
 * This function is called to check the policy for a request to transition
 * to the connected state from the disconnected state. The generic policy
 * is to do sanity checks again before going live.
 */
/*ARGSUSED*/
int
sysc_policy_connect(struct sysctrl_soft_state *softsp,
                sysc_cfga_pkt_t *pkt, sysc_cfga_stat_t *sysc_stat)
{
        int retval;

        ASSERT(fhc_bdlist_locked());

        DPRINTF(SYSC_DEBUG, ("Previous RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Previous OState: %d\n", sysc_stat->ostate));

        switch (sysc_stat->rstate) {
        case SYSC_CFGA_RSTATE_DISCONNECTED:
                /*
                 * Safety policy: only allow connect if board is UNKNOWN cond.
                 * cold start board will be demoted to UNKNOWN cond when
                 * disconnected
                 */
                if (sysc_stat->condition != SYSC_CFGA_COND_UNKNOWN) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_COND);
                        return (EINVAL);
                }

                if (!enable_dynamic_reconfiguration) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_NON_DR_PROM);
                        return (ENOTSUP);
                }

                if (sysctrl_hotplug_disabled) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_HOTPLUG);
                        return (ENOTSUP);
                }

                /* Check PROM support. */
                if (!sysc_board_connect_supported(sysc_stat->type)) {
                        cmn_err(CE_WARN, "%s board %d connect"
                            " is not supported by firmware.",
                            fhc_bd_typestr(sysc_stat->type), sysc_stat->board);
                        SYSC_ERR_SET(pkt, SYSC_ERR_HW_COMPAT);
                        return (ENOTSUP);
                }

                if (!sysc_policy_enough_power(softsp, TRUE, FALSE)) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_POWER);
                        return (EAGAIN);
                }

                if (!sysc_policy_enough_precharge(softsp, sysc_stat)) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_PRECHARGE);
                        return (EAGAIN);
                }

                if (!sysc_policy_enough_cooling(softsp, sysc_stat, FALSE)) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_COOLING);
                        return (EAGAIN);
                }

                if (!sysc_policy_hardware_compatible(softsp, sysc_stat, pkt)) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_HW_COMPAT);
                        return (ENOTSUP);
                }
                sysc_policy_audit_messages(SYSC_AUDIT_RSTATE_CONNECT,
                        sysc_stat);

                retval = sysc_bd_connect(sysc_stat->board, pkt);
                if (!retval) {
                        sysc_stat->rstate = SYSC_CFGA_RSTATE_CONNECTED;
                        sysc_policy_connected_condition(softsp,
                                sysc_stat, FALSE);
                        sysc_policy_audit_messages(SYSC_AUDIT_RSTATE_SUCCEEDED,
                                sysc_stat);
                } else {
                        uint_t prom_failure;

                        prom_failure = (retval == EIO &&
                            pkt->cmd_cfga.errtype == SYSC_ERR_PROM) ?
                            TRUE : FALSE;
                        sysc_policy_disconnected_condition(softsp,
                                sysc_stat, prom_failure, FALSE);
                        sysc_policy_audit_messages(
                                SYSC_AUDIT_RSTATE_CONNECT_FAILED,
                                sysc_stat);
                }
                break;
        case SYSC_CFGA_RSTATE_EMPTY:
        case SYSC_CFGA_RSTATE_CONNECTED:
        default:
                SYSC_ERR_SET(pkt, SYSC_ERR_RSTATE);
                retval = EINVAL;
                break;
        }

        DPRINTF(SYSC_DEBUG, ("Current RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Current OState: %d\n", sysc_stat->ostate));
        DPRINTF(SYSC_DEBUG, ("Current Condition: %d\n", sysc_stat->condition));

        return (retval);
}

/*
 * This function is called to check the policy for a request to transition
 * to the disconnected state from the connected/unconfigured state only.
 * All other requests are invalid.
 */
/*ARGSUSED*/
int
sysc_policy_disconnect(struct sysctrl_soft_state *softsp,
                        sysc_cfga_pkt_t *pkt, sysc_cfga_stat_t *sysc_stat)
{
        int retval;

        ASSERT(fhc_bdlist_locked());

        DPRINTF(SYSC_DEBUG, ("Previous RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Previous OState: %d\n", sysc_stat->ostate));

        switch (sysc_stat->rstate) {
        case SYSC_CFGA_RSTATE_CONNECTED:
                switch (sysc_stat->ostate) {
                case SYSC_CFGA_OSTATE_UNCONFIGURED:
                        if (!enable_dynamic_reconfiguration) {
                                SYSC_ERR_SET(pkt, SYSC_ERR_NON_DR_PROM);
                                return (ENOTSUP);
                        }

                        /* Check PROM support. */
                        if (!sysc_board_connect_supported(sysc_stat->type)) {
                                cmn_err(CE_WARN, "%s board %d disconnect"
                                    " is not supported by firmware.",
                                    fhc_bd_typestr(sysc_stat->type),
                                    sysc_stat->board);
                                SYSC_ERR_SET(pkt, SYSC_ERR_HW_COMPAT);
                                return (ENOTSUP);
                        }

                        if (!sysc_policy_hardware_compatible(softsp,
                                sysc_stat, pkt)) {
                                cmn_err(CE_WARN, "%s board %d disconnect"
                                " is not yet supported.",
                                fhc_bd_typestr(sysc_stat->type),
                                        sysc_stat->board);
                                SYSC_ERR_SET(pkt, SYSC_ERR_HW_COMPAT);
                                return (ENOTSUP);
                        }

                        if (fhc_bd_is_jtag_master(sysc_stat->board)) {
                                sysc_policy_update(softsp, sysc_stat,
                                        SYSC_EVT_BD_CORE_RESOURCE_DISCONNECT);
                                SYSC_ERR_SET(pkt, SYSC_ERR_CORE_RESOURCE);
                                return (EINVAL);
                        }

                        sysc_policy_audit_messages(SYSC_AUDIT_RSTATE_DISCONNECT,
                                sysc_stat);

                        retval = sysc_bd_disconnect(sysc_stat->board, pkt);
                        if (!retval) {
                                sysc_stat->rstate =
                                        SYSC_CFGA_RSTATE_DISCONNECTED;
                                DPRINTF(SYSCTRL_ATTACH_DEBUG,
                                    ("disconnect starting bd_remove_poll()"));
                                bd_remove_poll(softsp);
                                sysc_policy_disconnected_condition(
                                        softsp,
                                        sysc_stat, FALSE, FALSE);
                                sysc_policy_audit_messages(
                                        SYSC_AUDIT_RSTATE_SUCCEEDED,
                                        sysc_stat);
                                cmn_err(CE_NOTE,
                                        "board %d is ready to remove",
                                        sysc_stat->board);
                        } else {
                                sysc_policy_connected_condition(
                                        softsp, sysc_stat, FALSE);
                                sysc_policy_audit_messages(
                                        SYSC_AUDIT_RSTATE_DISCONNECT_FAILED,
                                        sysc_stat);
                        }
                        break;
                case SYSC_CFGA_OSTATE_CONFIGURED:
                default:
                        SYSC_ERR_SET(pkt, SYSC_ERR_OSTATE);
                        retval = EINVAL;
                        break;
                }
                break;
        case SYSC_CFGA_RSTATE_EMPTY:
        case SYSC_CFGA_RSTATE_DISCONNECTED:
        default:
                SYSC_ERR_SET(pkt, SYSC_ERR_RSTATE);
                retval = EINVAL;
                break;
        }

        DPRINTF(SYSC_DEBUG, ("Current RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Current OState: %d\n", sysc_stat->ostate));
        DPRINTF(SYSC_DEBUG, ("Current Condition: %d\n", sysc_stat->condition));

        return (retval);
}

/*
 * This function is called to check the policy for a request to transition
 * from the connected/configured state to the connected/unconfigured state only.
 * All other requests are invalid.
 */
/*ARGSUSED*/
int
sysc_policy_unconfigure(struct sysctrl_soft_state *softsp,
                        sysc_cfga_pkt_t *pkt, sysc_cfga_stat_t *sysc_stat)
{
        int retval;

        ASSERT(fhc_bdlist_locked());

        DPRINTF(SYSC_DEBUG, ("Previous RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Previous OState: %d\n", sysc_stat->ostate));

        switch (sysc_stat->ostate) {
        case SYSC_CFGA_OSTATE_CONFIGURED:
                if (!enable_dynamic_reconfiguration) {
                        SYSC_ERR_SET(pkt, SYSC_ERR_NON_DR_PROM);
                        return (ENOTSUP);
                }

                if (!sysc_policy_hardware_compatible(softsp, sysc_stat, pkt)) {
                        cmn_err(CE_WARN, "%s board %d unconfigure"
                        " is not yet supported.",
                        fhc_bd_typestr(sysc_stat->type), sysc_stat->board);
                        SYSC_ERR_SET(pkt, SYSC_ERR_HW_COMPAT);
                        return (ENOTSUP);
                }

                sysc_policy_audit_messages(SYSC_AUDIT_OSTATE_UNCONFIGURE,
                        sysc_stat);

                retval = sysc_bd_unconfigure(sysc_stat->board, pkt);
                if (!retval) {
                    sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                    sysc_policy_audit_messages(
                        SYSC_AUDIT_OSTATE_SUCCEEDED,
                        sysc_stat);
                } else {
                    sysc_policy_audit_messages(
                        SYSC_AUDIT_OSTATE_UNCONFIGURE_FAILED,
                        sysc_stat);
                }
                sysc_policy_connected_condition(softsp, sysc_stat, FALSE);
                break;
        case SYSC_CFGA_OSTATE_UNCONFIGURED:
        default:
                SYSC_ERR_SET(pkt, SYSC_ERR_OSTATE);
                retval = EINVAL;
                break;
        }

        DPRINTF(SYSC_DEBUG, ("Current RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Current OState: %d\n", sysc_stat->ostate));
        DPRINTF(SYSC_DEBUG, ("Current Condition: %d\n", sysc_stat->condition));

        return (retval);
}

/*
 * This function is called to check the policy for a requested transition
 * from either the connected/unconfigured state or the connected/configured
 * state to the connected/configured state.  The redundant state transition
 * is permitted for partially configured set of devices.  Basically, we
 * retry the configure.
 */
/*ARGSUSED*/
int
sysc_policy_configure(struct sysctrl_soft_state *softsp,
                        sysc_cfga_pkt_t *pkt, sysc_cfga_stat_t *sysc_stat)
{
        int retval;

        ASSERT(fhc_bdlist_locked());

        DPRINTF(SYSC_DEBUG, ("Previous RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Previous OState: %d\n", sysc_stat->ostate));

        switch (sysc_stat->rstate) {
        case SYSC_CFGA_RSTATE_CONNECTED:
                switch (sysc_stat->ostate) {
                case SYSC_CFGA_OSTATE_UNCONFIGURED:
                        if (sysc_stat->condition != SYSC_CFGA_COND_OK) {
                                SYSC_ERR_SET(pkt, SYSC_ERR_COND);
                                return (EINVAL);
                        }

                        sysc_policy_audit_messages(SYSC_AUDIT_OSTATE_CONFIGURE,
                                sysc_stat);
                        retval = sysc_bd_configure(sysc_stat->board, pkt);
                        sysc_stat->ostate = SYSC_CFGA_OSTATE_CONFIGURED;
                        sysc_policy_connected_condition(softsp,
                                sysc_stat, FALSE);
                        if (!retval) {
                                sysc_policy_audit_messages(
                                        SYSC_AUDIT_OSTATE_SUCCEEDED,
                                        sysc_stat);
                        } else {
                                sysc_policy_audit_messages(
                                        SYSC_AUDIT_OSTATE_CONFIGURE_FAILED,
                                        sysc_stat);
                        }
                        break;
                case SYSC_CFGA_OSTATE_CONFIGURED:
                        SYSC_ERR_SET(pkt, SYSC_ERR_OSTATE);
                        retval = ENOTSUP;
                        break;
                default:
                        SYSC_ERR_SET(pkt, SYSC_ERR_OSTATE);
                        retval = EINVAL;
                        break;
                }
                break;
        case SYSC_CFGA_RSTATE_EMPTY:
        case SYSC_CFGA_RSTATE_DISCONNECTED:
        default:
                SYSC_ERR_SET(pkt, SYSC_ERR_RSTATE);
                retval = EINVAL;
                break;
        }


        DPRINTF(SYSC_DEBUG, ("Current RState: %d\n", sysc_stat->rstate));
        DPRINTF(SYSC_DEBUG, ("Current OState: %d\n", sysc_stat->ostate));
        DPRINTF(SYSC_DEBUG, ("Current Condition: %d\n", sysc_stat->condition));

        return (retval);
}

/*ARGSUSED*/
static void
sysc_policy_empty_condition(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t failure,
        uint_t ps_mutex_is_held)
{
        ASSERT(fhc_bdlist_locked());

        switch (sysc_stat->condition) {
        case SYSC_CFGA_COND_UNKNOWN:
        case SYSC_CFGA_COND_OK:
        case SYSC_CFGA_COND_FAILING:
        case SYSC_CFGA_COND_FAILED:
        /* nothing in the slot so just check power supplies */
        case SYSC_CFGA_COND_UNUSABLE:
            if (sysc_policy_enough_cooling(softsp, sysc_stat,
                ps_mutex_is_held) &&
                sysc_policy_enough_power(softsp, FALSE,
                ps_mutex_is_held)) {
                sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;
            } else {
                sysc_stat->condition = SYSC_CFGA_COND_UNUSABLE;
            }
            sysc_stat->last_change = gethrestime_sec();
            break;
        default:
            ASSERT(FALSE);
            break;
        }
}
/*ARGSUSED*/
static void
sysc_policy_disconnected_condition(struct sysctrl_soft_state *softsp,
        sysc_cfga_stat_t *sysc_stat, uint_t failure,
        uint_t ps_mutex_is_held)
{
        ASSERT(fhc_bdlist_locked());

        if (failure) {
                sysc_stat->condition = SYSC_CFGA_COND_FAILED;
                sysc_stat->last_change = gethrestime_sec();
                return;
        }
        switch (sysc_stat->condition) {
        /*
         * if unknown, we have come from hotplug case so do a quick
         * reevaluation.
         */
        case SYSC_CFGA_COND_UNKNOWN:
        /*
         * if ok, we have come from connected to disconnected and we stay
         * ok until removed or reevaluate when reconnect.  We might have
         * experienced a ps fail so reevaluate the condition.
         */
        case SYSC_CFGA_COND_OK:
        /*
         * if unsuable, either power supply was missing or
         * hardware was not compatible.  Check to see if
         * this is still true.
         */
        case SYSC_CFGA_COND_UNUSABLE:
        /*
         * failing must transition in the disconnected state
         * to either unusable or unknown.  We may have come here
         * from cfgadm -f -c disconnect after a power supply failure
         * in an attempt to protect the board.
         */
        case SYSC_CFGA_COND_FAILING:
            if (sysc_policy_enough_cooling(softsp, sysc_stat,
                ps_mutex_is_held) &&
                sysc_policy_enough_power(softsp, FALSE,
                ps_mutex_is_held)) {
                sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;
            } else {
                sysc_stat->condition = SYSC_CFGA_COND_UNUSABLE;
            }
            sysc_stat->last_change = gethrestime_sec();
            break;
        /*
         * if failed, we have failed POST and must stay in this
         * condition until the board has been removed
         * before ever coming back into another condition
         */
        case SYSC_CFGA_COND_FAILED:
                break;
        default:
                ASSERT(FALSE);
                break;
        }
}

/*ARGSUSED*/
static void
sysc_policy_connected_condition(struct sysctrl_soft_state *softsp,
                sysc_cfga_stat_t *sysc_stat,
                uint_t ps_mutex_is_held)
{
        ASSERT(fhc_bdlist_locked());

        switch (sysc_stat->condition) {
        case SYSC_CFGA_COND_UNKNOWN:
        case SYSC_CFGA_COND_OK:
        case SYSC_CFGA_COND_FAILING:
        case SYSC_CFGA_COND_UNUSABLE:
            if (sysc_policy_enough_cooling(softsp, sysc_stat,
                ps_mutex_is_held) &&
                sysc_policy_enough_power(softsp, FALSE,
                ps_mutex_is_held) &&
                (fhc_env_temp_state(sysc_stat->board) == TEMP_OK)) {
                        sysc_stat->condition = SYSC_CFGA_COND_OK;
            } else {
                        sysc_stat->condition = SYSC_CFGA_COND_FAILING;
            }
            sysc_stat->last_change = gethrestime_sec();
            break;
        case SYSC_CFGA_COND_FAILED:
            break;
        default:
            ASSERT(FALSE);
            break;
        }
}

static void
sysc_policy_set_condition(void *sp, sysc_cfga_stat_t *sysc_stat,
                uint_t ps_mutex_is_held)
{
        struct sysctrl_soft_state *softsp = (struct sysctrl_soft_state *)sp;

        ASSERT(fhc_bdlist_locked());

        switch (sysc_stat->rstate) {
        case SYSC_CFGA_RSTATE_EMPTY:
                sysc_policy_empty_condition(softsp, sysc_stat,
                        FALSE, ps_mutex_is_held);
                break;
        case SYSC_CFGA_RSTATE_DISCONNECTED:
                sysc_policy_disconnected_condition(softsp, sysc_stat,
                        FALSE, ps_mutex_is_held);
                break;
        case SYSC_CFGA_RSTATE_CONNECTED:
                sysc_policy_connected_condition(softsp, sysc_stat,
                        ps_mutex_is_held);
                break;
        default:
                ASSERT(FALSE);
                break;
        }
}

void
sysc_policy_update(void *softsp, sysc_cfga_stat_t *sysc_stat,
        sysc_evt_t event)
{
        fhc_bd_t *list;

        ASSERT(event == SYSC_EVT_BD_HP_DISABLED || fhc_bdlist_locked());

        switch (event) {
        case SYSC_EVT_BD_EMPTY:
                sysc_stat->rstate = SYSC_CFGA_RSTATE_EMPTY;
                sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;
                sysc_policy_empty_condition(softsp, sysc_stat, FALSE, FALSE);
                break;
        case SYSC_EVT_BD_PRESENT:
                if (sysc_stat->type == DISK_BOARD) {
                        sysc_stat->rstate = SYSC_CFGA_RSTATE_DISCONNECTED;
                        sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                        sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;
                } else {
                        sysc_stat->rstate = SYSC_CFGA_RSTATE_CONNECTED;
                        sysc_stat->ostate = SYSC_CFGA_OSTATE_CONFIGURED;
                        sysc_stat->condition = SYSC_CFGA_COND_OK;
                }
                sysc_stat->last_change = gethrestime_sec();
                break;
        case SYSC_EVT_BD_DISABLED:
                sysc_stat->rstate = SYSC_CFGA_RSTATE_DISCONNECTED;
                sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;
                sysc_policy_disconnected_condition(softsp,
                        sysc_stat, FALSE, FALSE);
                cmn_err(CE_NOTE,
                        "disabled %s board in slot %d",
                        fhc_bd_typestr(sysc_stat->type),
                        sysc_stat->board);
                break;
        case SYSC_EVT_BD_FAILED:
                sysc_stat->rstate = SYSC_CFGA_RSTATE_DISCONNECTED;
                sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                sysc_stat->condition = SYSC_CFGA_COND_UNUSABLE;
                sysc_policy_disconnected_condition(softsp, sysc_stat,
                        TRUE, FALSE);
                cmn_err(CE_WARN,
                        "failed %s board in slot %d",
                        fhc_bd_typestr(sysc_stat->type),
                        sysc_stat->board);
                break;
        case SYSC_EVT_BD_OVERTEMP:
        case SYSC_EVT_BD_TEMP_OK:
                sysc_policy_set_condition((void *)softsp, sysc_stat, FALSE);
                break;
        case SYSC_EVT_BD_PS_CHANGE:
                for (list = fhc_bd_first(); list; list = fhc_bd_next(list)) {
                        sysc_stat = &(list->sc);
                        sysc_policy_set_condition((void *)softsp,
                                sysc_stat, TRUE);
                }
                break;
        case SYSC_EVT_BD_INS_FAILED:
                cmn_err(CE_WARN, "powerdown of board %d failed",
                        sysc_stat->board);
                break;
        case SYSC_EVT_BD_INSERTED:
                sysc_stat->rstate = SYSC_CFGA_RSTATE_DISCONNECTED;
                sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                sysctrl_post_config_change(softsp);
                sysc_policy_disconnected_condition(softsp,
                        sysc_stat, FALSE, FALSE);
                cmn_err(CE_NOTE, "%s board has been inserted into slot %d",
                        fhc_bd_typestr(sysc_stat->type), sysc_stat->board);
                cmn_err(CE_NOTE,
                        "board %d can be removed", sysc_stat->board);
                break;
        case SYSC_EVT_BD_REMOVED:
                sysc_stat->rstate = SYSC_CFGA_RSTATE_EMPTY;
                sysc_stat->ostate = SYSC_CFGA_OSTATE_UNCONFIGURED;
                sysc_stat->condition = SYSC_CFGA_COND_UNKNOWN;

                /* now it is ok to free the ac pa memory database */
                fhc_del_memloc(sysc_stat->board);

                /* reinitialize sysc_cfga_stat structure */
                sysc_stat->type = UNKNOWN_BOARD;
                sysc_stat->fhc_compid = 0;
                sysc_stat->ac_compid = 0;
                (void) bzero(&(sysc_stat->prom_rev),
                        sizeof (sysc_stat->prom_rev));
                (void) bzero(&(sysc_stat->bd),
                        sizeof (union bd_un));
                sysc_stat->no_detach = sysc_stat->plus_board = 0;
                sysc_policy_disconnected_condition(softsp,
                        sysc_stat, FALSE, FALSE);
                cmn_err(CE_NOTE, "board %d has been removed",
                        sysc_stat->board);
                break;
        case SYSC_EVT_BD_HP_DISABLED:
                cmn_err(CE_NOTE, "Hot Plug not supported in this system");
                break;
        case SYSC_EVT_BD_CORE_RESOURCE_DISCONNECT:
                cmn_err(CE_WARN, "board %d cannot be disconnected because it"
                        " is a core system resource", sysc_stat->board);
                break;
        default:
                ASSERT(FALSE);
                break;
        }

}

/*
 * signal to POST that the system has been reconfigured and that
 * the system configuration status information should be invalidated
 * the next time through POST
 */
static void
sysctrl_post_config_change(struct sysctrl_soft_state *softsp)
{
        /*
         * We are heading into a configuration change!
         * Tell post to invalidate its notion of the system configuration.
         * This is done by clearing the clock registers...
         */
        *softsp->clk_freq1 = 0;
        *softsp->clk_freq2 &=
                ~(CLOCK_FREQ_8 | CLOCK_DIV_1 | CLOCK_RANGE | CLOCK_DIV_0);
}

static int
sysc_attach_board(void *arg)
{
        int board = *(int *)arg;

        return (prom_sunfire_attach_board((uint_t)board));
}

static int
sysc_bd_connect(int board, sysc_cfga_pkt_t *pkt)
{
        int error = 0;
        fhc_bd_t *bdp;
        sysc_dr_handle_t *sh;
        uint64_t mempa;
        int del_kstat = 0;

        ASSERT(fhc_bd_busy(board));

        bdp = fhc_bd(board);

        /* find gap for largest supported simm in advance */
#define MAX_BANK_SIZE_MB        (2 * 1024)
#define BANKS_PER_BOARD         2
        mempa = fhc_find_memloc_gap(BANKS_PER_BOARD * MAX_BANK_SIZE_MB);

        fhc_bdlist_unlock();

        /* TODO: Is mempa vulnerable to re-use here? */

        sysctrl_suspend_prepare();

        if ((error = sysctrl_suspend(pkt)) == DDI_SUCCESS) {
                /* ASSERT(jtag not held) */
                error = prom_tree_update(sysc_attach_board, &board);
                if (error) {
                        error = EIO;
                        SYSC_ERR_SET(pkt, SYSC_ERR_PROM);
                } else {
                        /* attempt to program the memory while frozen */
                        fhc_program_memory(board, mempa);
                }
                sysctrl_resume(pkt);
        }

        if (error) {
                goto done;
        }

        /*
         * Must not delete kstat used by prtdiag until the PROM
         * has successfully connected to board.
         */
        del_kstat = 1;

        sh = &bdp->sh[SYSC_DR_HANDLE_FHC];
        sh->flags |= SYSC_DR_FHC;
        sh->errstr = pkt->errbuf;

        sysc_dr_init(sh);

        error = sysc_dr_attach(sh, board);
        if (error)
                SYSC_ERR_SET(pkt, SYSC_ERR_NDI_ATTACH);

        sysc_dr_uninit(sh);

        if (enable_redist) {
                mutex_enter(&cpu_lock);
                intr_redist_all_cpus();
                mutex_exit(&cpu_lock);
        }
done:
        if (del_kstat && bdp->ksp) {
                kstat_delete(bdp->ksp);
                bdp->ksp = NULL;
        }

        (void) fhc_bdlist_lock(-1);

        return (error);
}

static int
sysc_detach_board(void * arg)
{
        int rt;
        cpuset_t xcset;
        struct jt_mstr *jtm;
        int board = *(int *)arg;

        (void) fhc_bdlist_lock(-1);

#ifdef DEBUG
        /* it is important to have fhc_bdlist_lock() earlier */
        if (sysctrl_enable_regdump)
                precache_regdump(board);
#endif /* DEBUG */

        jtm = jtag_master_lock();
        CPUSET_ALL(xcset);
        promsafe_xc_attention(xcset);

#ifdef DEBUG
        if (sysctrl_enable_regdump)
                boardstat_regdump();
#endif /* DEBUG */

        rt =  prom_sunfire_detach_board((uint_t)board);

#ifdef DEBUG
        if (sysctrl_enable_regdump)
                display_regdump();
#endif /* DEBUG */

        xc_dismissed(xcset);
        jtag_master_unlock(jtm);
        fhc_bdlist_unlock();
        return (rt);
}

static int
sysc_bd_disconnect(int board, sysc_cfga_pkt_t *pkt)
{
        int error;
        fhc_bd_t *bdp;
        sysc_dr_handle_t *sh;
        void fhc_bd_ks_alloc(fhc_bd_t *);

        ASSERT(fhc_bd_busy(board));
        ASSERT(!fhc_bd_is_jtag_master(board));


        bdp = fhc_bd(board);

        bdp->flags |= BDF_DETACH;

        fhc_bdlist_unlock();

        sh = &bdp->sh[SYSC_DR_HANDLE_FHC];
        sh->errstr = pkt->errbuf;

        ASSERT(sh->dip_list == NULL);

        sh->flags |= SYSC_DR_FHC;
        sysc_dr_init(sh);

        error = sysc_dr_detach(sh, board);
        sh->flags &= ~SYSC_DR_REMOVE;

        sysc_dr_uninit(sh);
        if (error) {
                SYSC_ERR_SET(pkt, SYSC_ERR_NDI_DETACH);
                goto done;
        }
        error = prom_tree_update(sysc_detach_board, &board);

        if (error) {
                error = EIO;
                SYSC_ERR_SET(pkt, SYSC_ERR_PROM);
                goto done;
        }

        if (enable_redist) {
                mutex_enter(&cpu_lock);
                intr_redist_all_cpus();
                mutex_exit(&cpu_lock);
        }

        fhc_bd_ks_alloc(bdp);
done:
        (void) fhc_bdlist_lock(-1);

        return (error);
}

static int
sysc_bd_configure(int board, sysc_cfga_pkt_t *pkt)
{
        int error = 0;
        fhc_bd_t *bdp;
        sysc_dr_handle_t *sh;

        ASSERT(fhc_bd_busy(board));

        bdp = fhc_bd(board);

        fhc_bdlist_unlock();


        sh = &bdp->sh[SYSC_DR_HANDLE_DEVS];
        sh->errstr = pkt->errbuf;

        ASSERT(sh->dip_list == NULL);

        sysc_dr_init(sh);

        sh->flags |= SYSC_DR_DEVS;
        error = sysc_dr_attach(sh, board);
        if (error) {
                SYSC_ERR_SET(pkt, SYSC_ERR_NDI_ATTACH);
                sysc_dr_uninit(sh);
                goto done;
        }

        sysc_dr_uninit(sh);

        if (enable_redist) {
                mutex_enter(&cpu_lock);
                intr_redist_all_cpus();
                mutex_exit(&cpu_lock);
        }
done:
        if (bdp->sc.type == CPU_BOARD) {
                /*
                 * Value of error gets lost for CPU boards.
                 */
                mutex_enter(&cpu_lock);

                error = find_and_setup_cpu(FHC_BOARD2CPU_A(board));
                if ((error == 0) || (error == ENODEV)) {
                        int retval_b;

                        retval_b = find_and_setup_cpu(FHC_BOARD2CPU_B(board));
                        if (retval_b != ENODEV)
                                error = retval_b;
                }

                mutex_exit(&cpu_lock);
        }

        (void) fhc_bdlist_lock(-1);

        return (error);
}

static int
sysc_bd_unconfigure(int board, sysc_cfga_pkt_t *pkt)
{
        int error;
        fhc_bd_t *bdp;
        sysc_dr_handle_t *sh;

        ASSERT(fhc_bdlist_locked());
        ASSERT(fhc_bd_busy(board));

        bdp = fhc_bd(board);

        if (bdp->sc.type == CPU_BOARD || bdp->sc.type == MEM_BOARD) {
                struct ac_soft_state *acsp;

                /*
                 * Check that any memory on board is not in use.
                 * This must be done while the board list lock is held
                 * as memory state can change while fhc_bd_busy() is true
                 * even though a memory operation cannot be started
                 * if fhc_bd_busy() is true.
                 */
                if ((acsp = (struct ac_soft_state *)bdp->ac_softsp) != NULL) {
                        if (acsp->bank[Bank0].busy != 0 ||
                            acsp->bank[Bank0].ostate ==
                            SYSC_CFGA_OSTATE_CONFIGURED) {
                                cmn_err(CE_WARN, "memory bank %d in "
                                    "slot %d is in use.", Bank0, board);
                                (void) snprintf(pkt->errbuf,
                                    SYSC_OUTPUT_LEN,
                                    "memory bank %d in use",
                                    Bank0);
                                return (EBUSY);
                        }

                        if (acsp->bank[Bank1].busy != 0 ||
                            acsp->bank[Bank1].ostate ==
                            SYSC_CFGA_OSTATE_CONFIGURED) {
                                cmn_err(CE_WARN, "memory bank %d in "
                                    "slot %d is in use.", Bank1, board);
                                (void) snprintf(pkt->errbuf,
                                    SYSC_OUTPUT_LEN,
                                    "memory bank %d in use",
                                    Bank1);
                                return (EBUSY);
                        }
                        /*
                         * Nothing more to do here. The memory interface
                         * will not make any transitions while
                         * fhc_bd_busy() is true. Once the ostate
                         * becomes unconfigured, the memory becomes
                         * invisible.
                         */
                }
                error = 0;
                if (bdp->sc.type == CPU_BOARD) {
                        struct cpu *cpua, *cpub;
                        int cpu_flags = 0;

                        if (pkt->cmd_cfga.force)
                                cpu_flags = CPU_FORCED;

                        fhc_bdlist_unlock();

                        mutex_enter(&cpu_lock); /* protects CPU states */

                        error = fhc_board_poweroffcpus(board, pkt->errbuf,
                            cpu_flags);

                        cpua = cpu_get(FHC_BOARD2CPU_A(board));
                        cpub = cpu_get(FHC_BOARD2CPU_B(board));

                        if ((error == 0) && (cpua != NULL)) {
                                error = cpu_unconfigure(cpua->cpu_id);
                                if (error != 0) {
                                        (void) snprintf(pkt->errbuf,
                                            SYSC_OUTPUT_LEN,
                                            "processor %d unconfigure failed",
                                            cpua->cpu_id);
                                }
                        }
                        if ((error == 0) && (cpub != NULL)) {
                                error = cpu_unconfigure(cpub->cpu_id);
                                if (error != 0) {
                                        (void) snprintf(pkt->errbuf,
                                            SYSC_OUTPUT_LEN,
                                            "processor %d unconfigure failed",
                                            cpub->cpu_id);
                                }
                        }

                        mutex_exit(&cpu_lock);

                        (void) fhc_bdlist_lock(-1);
                }

                if (error != 0)
                        return (error);
        }

        fhc_bdlist_unlock();

        sh = &bdp->sh[SYSC_DR_HANDLE_DEVS];
        sh->errstr = pkt->errbuf;

        ASSERT(sh->dip_list == NULL);

        sysc_dr_init(sh);

        sh->flags |= SYSC_DR_DEVS;
        error = sysc_dr_detach(sh, board);
        sh->flags &= ~SYSC_DR_REMOVE;
        if (error) {
                SYSC_ERR_SET(pkt, SYSC_ERR_NDI_DETACH);
                sysc_dr_uninit(sh);
                goto done;
        }

        sysc_dr_uninit(sh);

        if (enable_redist) {
                mutex_enter(&cpu_lock);
                intr_redist_all_cpus();
                mutex_exit(&cpu_lock);
        }

done:
        (void) fhc_bdlist_lock(-1);

        return (error);
}


typedef struct sysc_prom {
        sysc_dr_handle_t *handle;       /* DR handle                    */
        int board;                      /* board id                     */
        dev_info_t **dipp;              /* next slot for storing dip    */
} sysc_prom_t;

/*
 * Attaching devices on a board.
 */
static int
sysc_dr_attach(sysc_dr_handle_t  *handle, int board)
{
        int                     i;
        int                     err;
        sysc_prom_t             arg;
        devi_branch_t           b = {0};

        arg.handle = handle;
        arg.board = board;
        arg.dipp = handle->dip_list;

        b.arg = &arg;
        b.type = DEVI_BRANCH_PROM;
        b.create.prom_branch_select = sysc_prom_select;
        b.devi_branch_callback = sysc_branch_callback;

        handle->error = e_ddi_branch_create(ddi_root_node(), &b,
            NULL, DEVI_BRANCH_CHILD);

        if (handle->error)
                return (handle->error);

        for (i = 0, arg.dipp = handle->dip_list;
            i < handle->dip_list_len; i++, arg.dipp++) {

                err = e_ddi_branch_configure(*arg.dipp, NULL, 0);
                /*
                 * Error only if we fail for fhc dips
                 */
                if (err && (handle->flags & SYSC_DR_FHC)) {
                        handle->error = err;
                        sysc_dr_err_decode(handle, *arg.dipp, TRUE);
                        return (handle->error);
                }
        }

        return (0);
}

/*ARGSUSED*/
static int
sysc_make_list(void *arg, int has_changed)
{
        dev_info_t *rdip;
        sysc_prom_t *wp = (sysc_prom_t *)arg;
        pnode_t nid = prom_childnode(prom_rootnode());

        if (wp == NULL)
                return (EINVAL);

        for (; nid != OBP_NONODE && nid != OBP_BADNODE;
            nid = prom_nextnode(nid)) {
                if (sysc_prom_select(nid, arg, 0) != DDI_SUCCESS)
                        continue;
                if (wp->handle->dip_list_len < SYSC_DR_MAX_NODE) {
                        rdip = wp->handle->dip_list[wp->handle->dip_list_len] =
                            e_ddi_nodeid_to_dip(nid);
                        if (rdip != NULL) {
                                wp->handle->dip_list_len++;
                                /*
                                 * Branch rooted at dip already held, so
                                 * release hold acquired in e_ddi_nodeid_to_dip
                                 */
                                ddi_release_devi(rdip);
                                ASSERT(e_ddi_branch_held(rdip));
#ifdef  DEBUG
                        } else {
                                DPRINTF(SYSC_DEBUG, ("sysc_make_list:"
                                    " e_ddi_nodeid_to_dip() failed for"
                                    " nodeid: %d\n", nid));
#endif
                        }
                } else {
#ifdef  DEBUG
                        cmn_err(CE_WARN, "sysc_make_list: list overflow\n");
#endif
                        return (EFAULT);
                }
        }

        return (0);
}

/*
 * Detaching devices on a board.
 */
static int
sysc_dr_detach(sysc_dr_handle_t *handle, int board)
{
        int             i;
        uint_t          flags;
        sysc_prom_t     arg;

        ASSERT(handle->dip_list);
        ASSERT(handle->dip_list_len == 0);
        ASSERT(*handle->dip_list == NULL);

        arg.handle = handle;
        arg.board = board;
        arg.dipp = NULL;

        handle->error = prom_tree_access(sysc_make_list, &arg, NULL);
        if (handle->error)
                return (handle->error);

        flags = DEVI_BRANCH_DESTROY | DEVI_BRANCH_EVENT;

        for (i = handle->dip_list_len; i > 0; i--) {
                ASSERT(e_ddi_branch_held(handle->dip_list[i - 1]));
                handle->error = e_ddi_branch_unconfigure(
                    handle->dip_list[i - 1], NULL, flags);
                if (handle->error)
                        return (handle->error);
        }

        return (0);
}

static void
sysc_dr_init(sysc_dr_handle_t *handle)
{
        handle->dip_list = kmem_zalloc(sizeof (dev_info_t *) * SYSC_DR_MAX_NODE,
            KM_SLEEP);
        handle->dip_list_len = 0;
}

/*ARGSUSED2*/
static int
sysc_prom_select(pnode_t pnode, void *arg, uint_t flag)
{
        int             bd_id;
        char            name[OBP_MAXDRVNAME];
        int             len;
        int             *regp;
        sysc_prom_t     *wp = (sysc_prom_t *)arg;

        bd_id = -1;
        len = prom_getproplen(pnode, OBP_REG);
        if (len > 0) {
                regp = kmem_alloc(len, KM_SLEEP);
                (void) prom_getprop(pnode, OBP_REG, (caddr_t)regp);
                /*
                 * Get board id for EXXXX platforms where
                 * 0x1c0 is EXXXX platform specific data to
                 * acquire board id.
                 */
                bd_id = (*regp - 0x1c0) >> 2;
                kmem_free(regp, len);
        }

        (void) prom_getprop(pnode, OBP_NAME, (caddr_t)name);
        if ((bd_id == wp->board) &&
            ((wp->handle->flags & SYSC_DR_FHC) ?
            (strcmp(name, "fhc") == 0):
            (strcmp(name, "fhc") != 0)) &&
            (strcmp(name, "central") != 0)) {
                return (DDI_SUCCESS);
        }

        return (DDI_FAILURE);
}

/*ARGSUSED*/
static void
sysc_branch_callback(dev_info_t *rdip, void *arg, uint_t flags)
{
        sysc_prom_t *wp = (sysc_prom_t *)arg;

        ASSERT(wp->dipp != NULL);
        ASSERT(*wp->dipp == NULL);
        ASSERT((wp->handle->flags & SYSC_DR_REMOVE) == 0);

        if (wp->handle->dip_list_len < SYSC_DR_MAX_NODE) {
                *wp->dipp = rdip;
                wp->dipp++;
                wp->handle->dip_list_len++;
        } else {
                cmn_err(CE_PANIC, "sysc_branch_callback: list overflow");
        }
}

/*
 * Uninitialize devices for the state of a board.
 */
static void
sysc_dr_uninit(sysc_dr_handle_t *handle)
{
        kmem_free(handle->dip_list,
            sizeof (dev_info_t *) * SYSC_DR_MAX_NODE);
        handle->dip_list = NULL;
        handle->dip_list_len = 0;
}

static void
sysc_dr_err_decode(sysc_dr_handle_t *handle, dev_info_t *dip, int attach)
{
        char    *p;

        ASSERT(handle->error != 0);

        switch (handle->error) {
        case ENOMEM:
                break;
        case EBUSY:
                (void) ddi_pathname(dip, handle->errstr);
                break;
        default:
                handle->error = EFAULT;
                if (attach)
                        (void) ddi_pathname(ddi_get_parent(dip),
                            handle->errstr);
                else
                        (void) ddi_pathname(dip, handle->errstr);
                if (attach) {
                        p = "/";
                        (void) strcat(handle->errstr, p);
                        (void) strcat(handle->errstr, ddi_node_name(dip));
                }
                break;
        }
}

static char *
sysc_rstate_typestr(sysc_cfga_rstate_t rstate, sysc_audit_evt_t event)
{
        char *type_str;

        switch (rstate) {
        case SYSC_CFGA_RSTATE_EMPTY:
                switch (event) {
                case SYSC_AUDIT_RSTATE_EMPTY:
                        type_str = "emptying";
                        break;
                case SYSC_AUDIT_RSTATE_SUCCEEDED:
                        type_str = "emptied";
                        break;
                case SYSC_AUDIT_RSTATE_EMPTY_FAILED:
                        type_str = "empty";
                        break;
                default:
                        type_str = "empty?";
                        break;
                }
                break;
        case SYSC_CFGA_RSTATE_DISCONNECTED:
                switch (event) {
                case SYSC_AUDIT_RSTATE_DISCONNECT:
                        type_str = "disconnecting";
                        break;
                case SYSC_AUDIT_RSTATE_SUCCEEDED:
                        type_str = "disconnected";
                        break;
                case SYSC_AUDIT_RSTATE_DISCONNECT_FAILED:
                        type_str = "disconnect";
                        break;
                default:
                        type_str = "disconnect?";
                        break;
                }
                break;
        case SYSC_CFGA_RSTATE_CONNECTED:
                switch (event) {
                case SYSC_AUDIT_RSTATE_CONNECT:
                        type_str = "connecting";
                        break;
                case SYSC_AUDIT_RSTATE_SUCCEEDED:
                        type_str = "connected";
                        break;
                case SYSC_AUDIT_RSTATE_CONNECT_FAILED:
                        type_str = "connect";
                        break;
                default:
                        type_str = "connect?";
                        break;
                }
                break;
        default:
                type_str = "undefined receptacle state";
                break;
        }
        return (type_str);
}

static char *
sysc_ostate_typestr(sysc_cfga_ostate_t ostate, sysc_audit_evt_t event)
{
        char *type_str;

        switch (ostate) {
        case SYSC_CFGA_OSTATE_UNCONFIGURED:
                switch (event) {
                case SYSC_AUDIT_OSTATE_UNCONFIGURE:
                        type_str = "unconfiguring";
                        break;
                case SYSC_AUDIT_OSTATE_SUCCEEDED:
                case SYSC_AUDIT_OSTATE_UNCONFIGURE_FAILED:
                        type_str = "unconfigured";
                        break;
                default:
                        type_str = "unconfigure?";
                        break;
                }
                break;
        case SYSC_CFGA_OSTATE_CONFIGURED:
                switch (event) {
                case SYSC_AUDIT_OSTATE_CONFIGURE:
                        type_str = "configuring";
                        break;
                case SYSC_AUDIT_OSTATE_SUCCEEDED:
                case SYSC_AUDIT_OSTATE_CONFIGURE_FAILED:
                        type_str = "configured";
                        break;
                default:
                        type_str = "configure?";
                        break;
                }
                break;

        default:
                type_str = "undefined occupant state";
                break;
        }
        return (type_str);
}

static void
sysc_policy_audit_messages(sysc_audit_evt_t event, sysc_cfga_stat_t *sysc_stat)
{
        switch (event) {
                case SYSC_AUDIT_RSTATE_CONNECT:
                        cmn_err(CE_NOTE,
                                "%s %s board in slot %d",
                                sysc_rstate_typestr(SYSC_CFGA_RSTATE_CONNECTED,
                                event),
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board);
                        break;
                case SYSC_AUDIT_RSTATE_DISCONNECT:
                        cmn_err(CE_NOTE,
                                "%s %s board in slot %d",
                                sysc_rstate_typestr(
                                        SYSC_CFGA_RSTATE_DISCONNECTED,
                                        event),
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board);
                        break;
                case SYSC_AUDIT_RSTATE_SUCCEEDED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d is %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_rstate_typestr(sysc_stat->rstate,
                                        event));
                        break;
                case SYSC_AUDIT_RSTATE_CONNECT_FAILED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d failed to %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_rstate_typestr(SYSC_CFGA_RSTATE_CONNECTED,
                                        event));
                        break;
                case SYSC_AUDIT_RSTATE_DISCONNECT_FAILED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d failed to %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_rstate_typestr(
                                        SYSC_CFGA_RSTATE_DISCONNECTED,
                                        event));
                        break;
                case SYSC_AUDIT_OSTATE_CONFIGURE:
                        cmn_err(CE_NOTE,
                                "%s %s board in slot %d",
                                sysc_ostate_typestr(SYSC_CFGA_OSTATE_CONFIGURED,
                                event),
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board);
                        break;
                case SYSC_AUDIT_OSTATE_UNCONFIGURE:
                        cmn_err(CE_NOTE,
                                "%s %s board in slot %d",
                                sysc_ostate_typestr(
                                        SYSC_CFGA_OSTATE_UNCONFIGURED,
                                        event),
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board);
                        break;
                case SYSC_AUDIT_OSTATE_SUCCEEDED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d is %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_ostate_typestr(sysc_stat->ostate,
                                        event));
                        break;
                case SYSC_AUDIT_OSTATE_CONFIGURE_FAILED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d partially %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_ostate_typestr(
                                        SYSC_CFGA_OSTATE_CONFIGURED,
                                        event));
                        break;
                case SYSC_AUDIT_OSTATE_UNCONFIGURE_FAILED:
                        cmn_err(CE_NOTE,
                                "%s board in slot %d partially %s",
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board,
                                sysc_ostate_typestr(
                                        SYSC_CFGA_OSTATE_UNCONFIGURED,
                                        event));
                        break;
                default:
                        cmn_err(CE_NOTE,
                                "unknown audit of a %s %s board in"
                                " slot %d",
                                sysc_rstate_typestr(sysc_stat->rstate,
                                        event),
                                fhc_bd_typestr(sysc_stat->type),
                                sysc_stat->board);
                        break;
        }
}

#define MAX_PROP_LEN    33      /* must be > strlen("cpu") */

static int
find_and_setup_cpu(int cpuid)
{
        return (prom_tree_access(find_and_setup_cpu_start, &cpuid, NULL));
}

/* ARGSUSED */
static int
find_and_setup_cpu_start(void *cpuid_arg, int has_changed)
{
        pnode_t nodeid;
        int upaid;
        char type[MAX_PROP_LEN];
        int cpuid = *(int *)cpuid_arg;

        nodeid = prom_childnode(prom_rootnode());
        while (nodeid != OBP_NONODE) {
                if (prom_getproplen(nodeid, "device_type") < MAX_PROP_LEN)
                        (void) prom_getprop(nodeid, "device_type",
                            (caddr_t)type);
                else
                        type[0] = '\0';
                (void) prom_getprop(nodeid, "upa-portid", (caddr_t)&upaid);
                if ((strcmp(type, "cpu") == 0) && (upaid == cpuid)) {
                        return (cpu_configure(cpuid));
                }
                nodeid = prom_nextnode(nodeid);
        }
        return (ENODEV);
}

#define MAX_BOARD_TYPE  IO_SBUS_FFB_SOCPLUS_BOARD

static char sysc_supp_conn[MAX_BOARD_TYPE + 1];

static int
sysc_board_connect_supported(enum board_type type)
{
        if (type > MAX_BOARD_TYPE)
                return (0);
        return (sysc_supp_conn[type]);
}

void
sysc_board_connect_supported_init(void)
{
        pnode_t openprom_node;
        char sup_list[16];
        int proplen;
        int i;
        char tstr[3 * 5 + 1];

        /* Check the firmware for Dynamic Reconfiguration support */
        if (prom_test("SUNW,Ultra-Enterprise,rm-brd") != 0) {
                /* The message was printed in platmod:set_platform_defaults */
                enable_dynamic_reconfiguration = 0;
        }

        openprom_node = prom_finddevice("/openprom");
        if (openprom_node != OBP_BADNODE) {
                proplen = prom_bounded_getprop(openprom_node,
                    "add-brd-supported-types",
                    sup_list, sizeof (sup_list) - 1);
        } else {
                proplen = -1;
        }

        if (proplen < 0) {
                /*
                 * This is an old prom which may cause a fatal reset,
                 * so don't allow any DR operations.
                 * If enable_dynamic_reconfiguration is 0
                 * we have already printed a similar message.
                 */
                if (enable_dynamic_reconfiguration) {
                        cmn_err(CE_WARN, "Firmware does not support"
                            " Dynamic Reconfiguration");
                        enable_dynamic_reconfiguration = 0;
                }
                return;
        }
        for (i = 0; i < proplen; i++) {
                switch (sup_list[i]) {
                case '0':
                        sysc_supp_conn[CPU_BOARD] = 1;
                        sysc_supp_conn[MEM_BOARD] = 1;
                        break;
                case '1':
                        sysc_supp_conn[IO_2SBUS_BOARD] = 1;
                        break;
                case '2':
                        sysc_supp_conn[IO_SBUS_FFB_BOARD] = 1;
                        break;
                case '3':
                        sysc_supp_conn[IO_PCI_BOARD] = 1;
                        break;
                case '4':
                        sysc_supp_conn[IO_2SBUS_SOCPLUS_BOARD] = 1;
                        break;
                case '5':
                        sysc_supp_conn[IO_SBUS_FFB_SOCPLUS_BOARD] = 1;
                        break;
                default:
                        /* Ignore other characters. */
                        break;
                }
        }
        if (sysc_supp_conn[CPU_BOARD]) {
                cmn_err(CE_NOTE, "!Firmware supports Dynamic Reconfiguration"
                    " of CPU/Memory boards.");
        } else {
                cmn_err(CE_NOTE, "Firmware does not support Dynamic"
                    " Reconfiguration of CPU/Memory boards.");
        }

        tstr[0] = '\0';
        if (sysc_supp_conn[IO_2SBUS_BOARD])
                (void) strcat(tstr, ", 1");
        if (sysc_supp_conn[IO_SBUS_FFB_BOARD])
                (void) strcat(tstr, ", 2");
        if (sysc_supp_conn[IO_PCI_BOARD])
                (void) strcat(tstr, ", 3");
        if (sysc_supp_conn[IO_2SBUS_SOCPLUS_BOARD])
                (void) strcat(tstr, ", 4");
        if (sysc_supp_conn[IO_SBUS_FFB_SOCPLUS_BOARD])
                (void) strcat(tstr, ", 5");
        if (tstr[0] != '\0') {
                /* Skip leading ", " using &tstr[2]. */
                cmn_err(CE_NOTE, "!Firmware supports Dynamic Reconfiguration"
                    " of I/O board types %s.", &tstr[2]);
        } else {
                cmn_err(CE_NOTE, "Firmware does not support Dynamic"
                    " Reconfiguration of I/O boards.");
        }
}

#ifdef DEBUG

static void
precache_regdump(int board)
{
        fhc_bd_t *curr_bdp;
        int bd_idx;
        int reg_idx;

        for (bd_idx = 0; bd_idx < fhc_max_boards(); bd_idx++) {
                bcopy((void *) reg_tmpl, (void *) &reg_dt[bd_idx][0],
                    (sizeof (struct regs_data))*NUM_REG);
                curr_bdp = fhc_bd(bd_idx);
                if (curr_bdp->sc.rstate == SYSC_CFGA_RSTATE_CONNECTED) {
                        for (reg_idx = 0; reg_idx < NUM_REG; reg_idx++) {
                                reg_dt[bd_idx][reg_idx].eflag = TRUE;
                                if (bd_idx != board)
                                        reg_dt[bd_idx][reg_idx].oflag = TRUE;
                                reg_dt[bd_idx][reg_idx].physaddr +=
                                    (FHC_BOARD_SPAN*2*bd_idx);
                                reg_dt[bd_idx][reg_idx].pre_dsct =
                                    ldphysio(reg_dt[bd_idx][reg_idx].physaddr);
                        }
                }
        }


}

static void
boardstat_regdump(void)
{
        int bd_idx;

        prom_printf("\nBoard status before disconnect.\n");
        for (bd_idx = 0; bd_idx < fhc_max_boards(); bd_idx++) {
                if (reg_dt[bd_idx][0].eflag == 0) {
                        prom_printf("Board #%d is idle.\n", bd_idx);
                } else {
                        prom_printf("Board #%d is on.\n", bd_idx);
                }
        }

        for (bd_idx = 0; bd_idx < fhc_max_boards(); bd_idx++) {
                if (reg_dt[bd_idx][0].eflag) {
                        prom_printf("\nRegisters for Board #%d", bd_idx);
                        prom_printf(" (before disconnect).\n");
                        prom_printf("AC_BCSR   FHC_CTRL  JTAG      IGN   SIM"
                            "       SISM  UIM       USM\n");
                        prom_printf("%08x  %08x  %08x  %04x"
                            "  %08x  %04x  %08x  %04x\n",
                            reg_dt[bd_idx][0].pre_dsct,
                            reg_dt[bd_idx][1].pre_dsct,
                            reg_dt[bd_idx][2].pre_dsct,
                            reg_dt[bd_idx][3].pre_dsct,
                            reg_dt[bd_idx][4].pre_dsct,
                            reg_dt[bd_idx][5].pre_dsct,
                            reg_dt[bd_idx][6].pre_dsct,
                            reg_dt[bd_idx][7].pre_dsct);
                }
        }

}

static void
display_regdump(void)
{
        int bd_idx;
        int reg_idx;

        prom_printf("Board status after disconnect.\n");
        for (bd_idx = 0; bd_idx < fhc_max_boards(); bd_idx++) {
                if (reg_dt[bd_idx][0].oflag == 0) {
                        prom_printf("Board #%d is idle.\n", bd_idx);
                } else {
                        prom_printf("Board #%d is on.\n", bd_idx);
                        for (reg_idx = 0; reg_idx < NUM_REG; reg_idx++)
                                reg_dt[bd_idx][reg_idx].post_dsct =
                                    ldphysio(reg_dt[bd_idx][reg_idx].physaddr);
                }
        }

        for (bd_idx = 0; bd_idx < fhc_max_boards(); bd_idx++) {
                if (reg_dt[bd_idx][0].eflag) {
                        prom_printf("\nRegisters for Board #%d", bd_idx);
                        prom_printf(" (before and after disconnect).\n");
                        prom_printf("AC_BCSR   FHC_CTRL  JTAG      IGN   SIM"
                            "       SISM  UIM       USM\n");
                        prom_printf("%08x  %08x  %08x  %04x"
                            "  %08x  %04x  %08x  %04x\n",
                            reg_dt[bd_idx][0].pre_dsct,
                            reg_dt[bd_idx][1].pre_dsct,
                            reg_dt[bd_idx][2].pre_dsct,
                            reg_dt[bd_idx][3].pre_dsct,
                            reg_dt[bd_idx][4].pre_dsct,
                            reg_dt[bd_idx][5].pre_dsct,
                            reg_dt[bd_idx][6].pre_dsct,
                            reg_dt[bd_idx][7].pre_dsct);
                        if (reg_dt[bd_idx][0].oflag) {
                                prom_printf("%08x  %08x  %08x  %04x"
                                    "  %08x  %04x  %08x  %04x\n",
                                    reg_dt[bd_idx][0].post_dsct,
                                    reg_dt[bd_idx][1].post_dsct,
                                    reg_dt[bd_idx][2].post_dsct,
                                    reg_dt[bd_idx][3].post_dsct,
                                    reg_dt[bd_idx][4].post_dsct,
                                    reg_dt[bd_idx][5].post_dsct,
                                    reg_dt[bd_idx][6].post_dsct,
                                    reg_dt[bd_idx][7].post_dsct);
                        } else {
                                prom_printf("no data (board got"
                                    " disconnected)-------------------"
                                    "---------------\n");
                        }
                }

        }

}

#endif /* DEBUG */