/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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"

/*
 * CPU support routines for DR
 */

#include <sys/debug.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/dditypes.h>
#include <sys/devops.h>
#include <sys/modctl.h>
#include <sys/poll.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/stat.h>
#include <sys/kmem.h>
#include <sys/processor.h>
#include <sys/cpuvar.h>
#include <sys/mem_config.h>
#include <sys/promif.h>
#include <sys/x_call.h>
#include <sys/cpu_sgnblk_defs.h>
#include <sys/membar.h>
#include <sys/stack.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/spitregs.h>

#include <sys/archsystm.h>
#include <vm/hat_sfmmu.h>
#include <sys/pte.h>
#include <sys/mmu.h>
#include <sys/x_call.h>
#include <sys/cpu_module.h>
#include <sys/cheetahregs.h>

#include <sys/autoconf.h>
#include <sys/cmn_err.h>

#include <sys/sbdpriv.h>

void
sbd_cpu_set_prop(sbd_cpu_unit_t *cp, dev_info_t *dip)
{
	uint32_t	clock_freq;
	int		ecache_size = 0;
	char		*cache_str = NULL;

	/* read in the CPU speed */
	clock_freq = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "clock-frequency", 0);

	ASSERT(clock_freq != 0);

	/*
	 * The ecache property string is not the same
	 * for all CPU implementations.
	 */
	switch (cp->sbc_cpu_impl) {
	case CHEETAH_IMPL:
	case CHEETAH_PLUS_IMPL:
		cache_str = "ecache-size";
		break;
	case JAGUAR_IMPL:
		cache_str = "l2-cache-size";
		break;
	case PANTHER_IMPL:
		cache_str = "l3-cache-size";
		break;
	default:
		cmn_err(CE_WARN, "cpu implementation type "
		    "is an unknown %d value", cp->sbc_cpu_impl);
		ASSERT(0);
		break;
	}

	if (cache_str != NULL) {
		/* read in the ecache size */
		ecache_size = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
		    DDI_PROP_DONTPASS, cache_str, 0);
	}

	/*
	 * In the case the size is still 0,
	 * a zero value will be displayed running non-debug.
	 */
	ASSERT(ecache_size != 0);

	/* convert to the proper units */
	cp->sbc_speed = (clock_freq + 500000) / 1000000;
	cp->sbc_ecache = ecache_size / (1024 * 1024);
}

static void
sbd_fill_cpu_stat(sbd_cpu_unit_t *cp, dev_info_t *dip, sbd_cpu_stat_t *csp)
{
	int		namelen;

	bzero((caddr_t)csp, sizeof (*csp));
	csp->cs_type = cp->sbc_cm.sbdev_type;
	csp->cs_unit = cp->sbc_cm.sbdev_unum;
	namelen = sizeof (csp->cs_name);
	(void) ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
	    OBP_DEVICETYPE, (caddr_t)csp->cs_name, &namelen);
	csp->cs_busy = cp->sbc_cm.sbdev_busy;
	csp->cs_time = cp->sbc_cm.sbdev_time;
	csp->cs_ostate = cp->sbc_cm.sbdev_ostate;
	csp->cs_cpuid = cp->sbc_cpu_id;
	csp->cs_suspend = 0;

	/*
	 * If we have marked the cpu's condition previously
	 * then don't rewrite it
	 */
	if (csp->cs_cond != SBD_COND_UNUSABLE)
		csp->cs_cond = sbd_get_comp_cond(dip);

	/*
	 * If the speed and ecache properties have not been
	 * cached yet, read them in from the device tree.
	 */
	if ((cp->sbc_speed == 0) || (cp->sbc_ecache == 0))
		sbd_cpu_set_prop(cp, dip);

	/* use the cached speed and ecache values */
	csp->cs_speed = cp->sbc_speed;
	csp->cs_ecache = cp->sbc_ecache;
}

static void
sbd_fill_cmp_stat(sbd_cpu_stat_t *csp, int ncores, int impl,
    sbd_cmp_stat_t *psp)
{
	int	core;

	ASSERT(csp && psp && (ncores >= 1));

	bzero((caddr_t)psp, sizeof (*psp));

	/*
	 * Fill in the common status information based
	 * on the data for the first core.
	 */
	psp->ps_type = SBD_COMP_CMP;
	psp->ps_unit = SBD_CMP_NUM(csp->cs_unit);
	(void) strncpy(psp->ps_name, csp->cs_name, sizeof (psp->ps_name));
	psp->ps_cond = csp->cs_cond;
	psp->ps_busy = csp->cs_busy;
	psp->ps_time = csp->cs_time;
	psp->ps_ostate = csp->cs_ostate;
	psp->ps_suspend = csp->cs_suspend;

	/* CMP specific status data */
	*psp->ps_cpuid = csp->cs_cpuid;
	psp->ps_ncores = 1;
	psp->ps_speed = csp->cs_speed;
	psp->ps_ecache = csp->cs_ecache;

	/*
	 * Walk through the data for the remaining cores.
	 * Make any adjustments to the common status data,
	 * or the shared CMP specific data if necessary.
	 */
	for (core = 1; core < ncores; core++) {

		/*
		 * The following properties should be the same
		 * for all the cores of the CMP.
		 */
		ASSERT(psp->ps_unit == SBD_CMP_NUM(csp[core].cs_unit));
		ASSERT(psp->ps_speed == csp[core].cs_speed);

		psp->ps_cpuid[core] = csp[core].cs_cpuid;
		psp->ps_ncores++;

		/*
		 * Jaguar has a split ecache, so the ecache
		 * for each core must be added together to
		 * get the total ecache for the whole chip.
		 */
		if (IS_JAGUAR(impl)) {
			psp->ps_ecache += csp[core].cs_ecache;
		}

		/* adjust time if necessary */
		if (csp[core].cs_time > psp->ps_time) {
			psp->ps_time = csp[core].cs_time;
		}

		psp->ps_busy |= csp[core].cs_busy;

		/*
		 * If any of the cores are configured, the
		 * entire CMP is marked as configured.
		 */
		if (csp[core].cs_ostate == SBD_STAT_CONFIGURED) {
			psp->ps_ostate = csp[core].cs_ostate;
		}
	}
}

int
sbd_cpu_flags(sbd_handle_t *hp, sbd_devset_t devset, sbd_dev_stat_t *dsp)
{
	int		cmp;
	int		ncpu;
	sbd_board_t	*sbp;
	sbdp_handle_t	*hdp;
	sbd_cpu_stat_t	cstat[MAX_CORES_PER_CMP];

	sbp = SBDH2BD(hp->h_sbd);
	hdp = sbd_get_sbdp_handle(sbp, hp);

	/*
	 * Grab the status lock before accessing the dip as we allow
	 * concurrent status and branch unconfigure and disconnect.
	 *
	 * The disconnect thread clears the present devset first
	 * and then destroys dips. It is possible that the status
	 * thread checks the present devset before they are cleared
	 * but accesses the dip after they are destroyed causing a
	 * panic. To prevent this, the status thread should check
	 * the present devset and access dips with status lock held.
	 * Similarly disconnect thread should clear the present devset
	 * and destroy dips with status lock held.
	 */
	mutex_enter(&sbp->sb_slock);

	/*
	 * Only look for requested devices that are actually present.
	 */
	devset &= SBD_DEVS_PRESENT(sbp);

	/*
	 * Treat every CPU as a CMP.  In the case where the
	 * device is not a CMP, treat it as a CMP with only
	 * one core.
	 */
	for (cmp = ncpu = 0; cmp < MAX_CMP_UNITS_PER_BOARD; cmp++) {

		int		ncores;
		int		core;
		dev_info_t	*dip;
		sbd_cpu_unit_t	*cp;
		sbd_cmp_stat_t	*psp;

		if (DEVSET_IN_SET(devset, SBD_COMP_CMP, cmp) == 0)
			continue;

		ncores = 0;

		for (core = 0; core < MAX_CORES_PER_CMP; core++) {
			int	unit;

			unit = sbdp_portid_to_cpu_unit(cmp, core);

			/*
			 * Check to make sure the cpu is in a state
			 * where its fully initialized.
			 */
			if (SBD_DEVICE_STATE(sbp, SBD_COMP_CPU, unit) ==
			    SBD_STATE_EMPTY)
				continue;

			dip = sbp->sb_devlist[NIX(SBD_COMP_CMP)][unit];
			if (dip == NULL)
				continue;

			cp = SBD_GET_BOARD_CPUUNIT(sbp, unit);

			sbd_fill_cpu_stat(cp, dip, &cstat[ncores++]);
		}

		if (ncores == 0)
			continue;

		/*
		 * Store the data to the outgoing array. If the
		 * device is a CMP, combine all the data for the
		 * cores into a single stat structure.
		 *
		 * The check for a CMP device uses the last core
		 * found, assuming that all cores will have the
		 * same implementation.
		 */
		if (CPU_IMPL_IS_CMP(cp->sbc_cpu_impl)) {
			psp = (sbd_cmp_stat_t *)dsp;
			sbd_fill_cmp_stat(cstat, ncores, cp->sbc_cpu_impl, psp);
		} else {
			ASSERT(ncores == 1);
			bcopy(cstat, dsp, sizeof (sbd_cpu_stat_t));
		}

		dsp++;
		ncpu++;
	}

	mutex_exit(&sbp->sb_slock);

	sbd_release_sbdp_handle(hdp);

	return (ncpu);
}

int
sbd_pre_release_cpu(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
{
	int		i, rv = 0, unit;
	dev_info_t	*dip;
	processorid_t	cpuid;
	struct cpu	*cpup;
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	sbderror_t	*ep = SBD_HD2ERR(hp);
	sbd_cpu_unit_t	*cp;
	static fn_t	f = "sbd_pre_release_cpu";
	sbdp_handle_t	*hdp;

	hdp = sbd_get_sbdp_handle(sbp, hp);
	/*
	 * May have to juggle bootproc in release_component
	 */
	mutex_enter(&cpu_lock);

	for (i = 0; i < devnum; i++, devlist++) {
		dip = devlist->dv_dip;

		cpuid = sbdp_get_cpuid(hdp, dip);
		if (cpuid < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
					"sbd:%s: failed to get cpuid for "
					"dip (0x%p)", f, (void *)dip);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}


		unit = sbdp_get_unit_num(hdp, dip);
		if (unit < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
			cmn_err(CE_WARN,
				"sbd:%s: failed to get unit (cpu %d)",
				f, cpuid);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}

		cp = SBD_GET_BOARD_CPUUNIT(sbp, unit);
		cp->sbc_cpu_flags = cpu[cpuid]->cpu_flags;

		if (cpu_flagged_active(cp->sbc_cpu_flags)) {
			int cpu_offline_flags = 0;

			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE)
				cpu_offline_flags = CPU_FORCED;
			PR_CPU("%s: offlining cpuid %d unit %d", f,
				cpuid, unit);
			if (cpu_offline(cpu[cpuid], cpu_offline_flags)) {
				cmn_err(CE_WARN,
					"%s: failed to offline cpu %d",
					f, cpuid);
				rv = -1;
				SBD_SET_ERR(ep, ESBD_OFFLINE);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
				cpup = cpu_get(cpuid);
				if (cpup && disp_bound_threads(cpup, 0)) {
					cmn_err(CE_WARN, "sbd:%s: thread(s) "
						"bound to cpu %d",
						f, cpup->cpu_id);
				}
				break;
			}
		}

		if (rv == 0) {
			if (sbdp_release_component(hdp, dip)) {
				SBD_GET_PERR(hdp->h_err, ep);
				break;
			}
		}

		if (rv)
			break;
	}

	mutex_exit(&cpu_lock);

	if (rv) {
		/*
		 * Need to unwind others since at this level (pre-release)
		 * the device state has not yet transitioned and failures
		 * will prevent us from reaching the "post" release
		 * function where states are normally transitioned.
		 */
		for (; i >= 0; i--, devlist--) {
			dip = devlist->dv_dip;
			unit = sbdp_get_unit_num(hdp, dip);
			if (unit < 0) {
				cmn_err(CE_WARN,
					"sbd:%s: failed to get unit for "
					"dip (0x%p)", f, (void *)dip);
				break;
			}
			(void) sbd_cancel_cpu(hp, unit);
		}
	}

	SBD_INJECT_ERR(SBD_OFFLINE_CPU_PSEUDO_ERR,
		hp->h_err, EIO,
		ESBD_OFFLINE,
		sbp->sb_cpupath[devnum - 1]);

	sbd_release_sbdp_handle(hdp);

	return (rv);
}

int
sbd_pre_attach_cpu(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
{
	int		i;
	int		unit;
	processorid_t	cpuid;
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	sbd_istate_t	dstate;
	dev_info_t	*dip;
	static fn_t	f = "sbd_pre_attach_cpu";
	sbdp_handle_t	*hdp;

	PR_CPU("%s...\n", f);

	hdp = sbd_get_sbdp_handle(sbp, hp);

	for (i = 0; i < devnum; i++, devlist++) {
		dip = devlist->dv_dip;

		ASSERT(sbd_is_cmp_child(dip) || e_ddi_branch_held(dip));

		cpuid = sbdp_get_cpuid(hdp, dip);
		if (cpuid < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
					"sbd:%s: failed to get cpuid for "
					"dip (0x%p)", f, (void *)dip);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}

		unit = sbdp_get_unit_num(hdp, dip);
		if (unit < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
			cmn_err(CE_WARN,
				"sbd:%s: failed to get unit (cpu %d)",
				f, cpuid);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}

		PR_CPU("%s: attach cpu-unit (%d.%d)\n",
			f, sbp->sb_num, unit);

		dstate = SBD_DEVICE_STATE(sbp, SBD_COMP_CPU, unit);

		if (dstate == SBD_STATE_UNCONFIGURED) {
			/*
			 * If we're coming from the UNCONFIGURED
			 * state then the cpu's sigblock will
			 * still be mapped in.  Need to unmap it
			 * before continuing with attachment.
			 */
			PR_CPU("%s: unmapping sigblk for cpu %d\n",
				f, cpuid);

			/* platform specific release of sigblk */
			CPU_SGN_MAPOUT(cpuid);
		}

	}

	mutex_enter(&cpu_lock);

	sbd_release_sbdp_handle(hdp);

	return (0);
}

int
sbd_post_attach_cpu(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
{
	int		i;
	sbderror_t	*ep = SBD_HD2ERR(hp);
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	processorid_t	cpuid;
	struct cpu	*cp;
	dev_info_t	*dip;
	int		err = ESBD_NOERROR;
	sbdp_handle_t	*hdp;
	static fn_t	f = "sbd_post_attach_cpu";
	sbd_cpu_unit_t	*cpup;
	int		unit;

	hdp = sbd_get_sbdp_handle(sbp, hp);

	/* Startup and online newly-attached CPUs */
	for (i = 0; i < devnum; i++, devlist++) {
		dip = devlist->dv_dip;
		cpuid = sbdp_get_cpuid(hdp, dip);
		if (cpuid < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to get cpuid for "
				    "dip (0x%p)", f, (void *)dip);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, ep);
				break;
			}
		}

		cp = cpu_get(cpuid);

		if (cp == NULL) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
				    "sbd:%s: cpu_get failed for cpu %d",
				    f, cpuid);
				continue;
			} else {
				SBD_SET_ERR(ep, ESBD_INTERNAL);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
				break;
			}
		}

		if (cpu_is_poweredoff(cp)) {
			if (cpu_poweron(cp) != 0) {
				SBD_SET_ERR(ep, ESBD_CPUSTART);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
				cmn_err(CE_WARN,
				    "%s: failed to power-on cpu %d",
				    f, cpuid);
				break;
			}
			SBD_INJECT_ERR(SBD_POWERON_CPU_PSEUDO_ERR,
			    ep, EIO,
			    ESBD_CPUSTOP,
			    sbp->sb_cpupath[i]);
			PR_CPU("%s: cpu %d powered ON\n", f, cpuid);
		}

		if (cpu_is_offline(cp)) {
			PR_CPU("%s: onlining cpu %d...\n", f, cpuid);

			if (cpu_online(cp) != 0) {
				SBD_SET_ERR(ep, ESBD_ONLINE);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
				cmn_err(CE_WARN,
				    "%s: failed to online cpu %d",
				    f, cp->cpu_id);
			}
			SBD_INJECT_ERR(SBD_ONLINE_CPU_PSEUDO_ERR,
			    ep, EIO,
			    ESBD_ONLINE,
			    sbp->sb_cpupath[i]);
		}

		/*
		 * if there is no error mark the cpu as OK to use
		 */
		if (SBD_GET_ERR(ep) == 0) {
			unit = sbdp_get_unit_num(hdp, dip);
			if (unit < 0) {
				if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
					cmn_err(CE_WARN,
					    "sbd:%s: failed to get unit "
					    "(cpu %d)", f, cpuid);
					continue;
				} else {
					SBD_GET_PERR(hdp->h_err,
					    SBD_HD2ERR(hp));
					break;
				}
			}
			cpup = SBD_GET_BOARD_CPUUNIT(sbp, unit);
			cpup->sbc_cm.sbdev_cond = SBD_COND_OK;
		}
	}

	mutex_exit(&cpu_lock);

	sbd_release_sbdp_handle(hdp);

	if (err != ESBD_NOERROR) {
		return (-1);
	} else {
		return (0);
	}
}

int
sbd_pre_detach_cpu(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
{
	int		i;
	int		unit;
	processorid_t	cpuid;
	dev_info_t	*dip;
	struct cpu	*cpu;
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	sbderror_t	*ep = SBD_HD2ERR(hp);
	static fn_t	f = "sbd_pre_detach_cpu";
	sbdp_handle_t	*hdp;
	int		rv = 0;

	PR_CPU("%s...\n", f);

	hdp = sbd_get_sbdp_handle(sbp, hp);

	mutex_enter(&cpu_lock);

	for (i = 0; i < devnum; i++, devlist++) {
		dip = devlist->dv_dip;
		cpuid = sbdp_get_cpuid(hdp, dip);
		if (cpuid < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to get cpuid for "
				    "dip (0x%p)", f, (void *)dip);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}

		cpu = cpu_get(cpuid);

		if (cpu == NULL) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to get cpu %d",
				    f, cpuid);
				continue;
			} else {
				SBD_SET_ERR(ep, ESBD_INTERNAL);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
				break;
			}
		}

		unit = sbdp_get_unit_num(hdp, dip);
		if (unit < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to get unit (cpu %d)",
				    f, cpuid);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
				break;
			}
		}

		PR_CPU("%s: OS detach cpu-unit (%d.%d)\n",
		    f, sbp->sb_num, unit);

		/*
		 * CPUs were offlined during Release.
		 */
		if (cpu_is_poweredoff(cpu)) {
			PR_CPU("%s: cpu %d already powered OFF\n", f, cpuid);
			continue;
		}

		if (cpu_is_offline(cpu)) {
			int	e;

			if (e = cpu_poweroff(cpu)) {
				cmn_err(CE_WARN,
				    "%s: failed to power-off cpu %d "
				    "(errno %d)",
				    f, cpu->cpu_id, e);
				SBD_SET_ERR(ep, ESBD_CPUSTOP);
				SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);

				rv = -1;
				break;
			} else {
				PR_CPU("%s: cpu %d powered OFF\n",
					f, cpuid);
			}
		} else {
			cmn_err(CE_WARN, "%s: cpu %d still active",
				f, cpu->cpu_id);
			SBD_SET_ERR(ep, ESBD_BUSY);
			SBD_SET_ERRSTR(ep, sbp->sb_cpupath[i]);
			rv = -1;
			break;
		}
	}

	sbd_release_sbdp_handle(hdp);

	return (rv);
}

int
sbd_post_detach_cpu(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
{
	static fn_t	f = "sbd_post_detach_cpu";
	int		i;
	sbderror_t	*ep = SBD_HD2ERR(hp);
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	processorid_t	cpuid;
	dev_info_t	*dip;
	sbdp_handle_t	*hdp;
	sbd_cpu_unit_t	*cpup;
	int		unit;

	PR_CPU("%s...\n", f);

	/*
	 * We should be holding the cpu_lock at this point,
	 * and should have blocked device tree changes.
	 */
	ASSERT(MUTEX_HELD(&cpu_lock));

	for (i = 0; i < devnum; i++, devlist++) {
		dip = devlist->dv_dip;
		hdp = sbd_get_sbdp_handle(sbp, hp);
		cpuid = sbdp_get_cpuid(hdp, dip);
		if (cpuid < 0) {
			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
				cmn_err(CE_WARN,
					"sbd:%s: failed to get cpuid for "
					"dip (0x%p)", f, (void *)dip);
				continue;
			} else {
				SBD_GET_PERR(hdp->h_err, ep);
				break;
			}
		}
		/*
		 * if there is no error mark the cpu as unusable
		 */
		if (SBD_GET_ERR(ep) == 0) {
			unit = sbdp_get_unit_num(hdp, dip);
			if (unit < 0) {
				if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) {
					cmn_err(CE_WARN,
					    "sbd:%s: failed to get unit "
					    "(cpu %d)", f, cpuid);
					continue;
				} else {
					SBD_GET_PERR(hdp->h_err,
					    SBD_HD2ERR(hp));
					break;
				}
			}
			cpup = SBD_GET_BOARD_CPUUNIT(sbp, unit);
			cpup->sbc_cm.sbdev_cond = SBD_COND_UNUSABLE;
		}
		sbd_release_sbdp_handle(hdp);
	}

	mutex_exit(&cpu_lock);


	return (0);
}

/*
 * Cancel previous release operation for cpu.  For cpus this means simply
 * bringing cpus that were offline back online.  Note that they had to have been
 * online at the time they were released.  If attempting to power on or online
 * a CPU fails, SBD_CPUERR_FATAL is returned to indicate that the CPU appears to
 * be unsalvageable.  If a CPU reaches an online or nointr state but can't be
 * taken to a "lesser" state, SBD_CPUERR_RECOVERABLE is returned to indicate
 * that it was not returned to its original state but appears to be functional.
 * Note that the latter case can occur due to unexpected but non-erroneous CPU
 * manipulation (e.g. by the "psradm" command) during the DR operation.
 */
int
sbd_cancel_cpu(sbd_handle_t *hp, int unit)
{
	int		rv = SBD_CPUERR_NONE;
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	sbderror_t	*ep = SBD_HD2ERR(hp);
	sbd_cpu_unit_t	*cp;
	static fn_t	f = "sbd_cancel_cpu";
	struct cpu	*cpup;
	int		cpu_offline_flags = 0;

	PR_ALL("%s...\n", f);

	cp = SBD_GET_BOARD_CPUUNIT(sbp, unit);

	/*
	 * If CPU should remain off, nothing needs to be done.
	 */
	if (cpu_flagged_poweredoff(cp->sbc_cpu_flags))
		return (rv);

	if (hp->h_flags & SBD_IOCTL_FLAG_FORCE)
		cpu_offline_flags = CPU_FORCED;

	/*
	 * CPU had been either offline, online, or set to no-intr.  We
	 * will return a component to its original state that it was
	 * prior to the failed DR operation.  There is a possible race
	 * condition between the calls to this function and re-obtaining
	 * the cpu_lock where a cpu state could change.  Because of this
	 * we can't externally document that we are trying to roll cpus
	 * back to their original state, but we believe a best effort
	 * should be made.
	 */

	mutex_enter(&cpu_lock);
	cpup = cpu[cp->sbc_cpu_id];

	/*
	 * The following will compare the cpu's current state with a
	 * snapshot of its state taken before the failed DR operation
	 * had started.
	 */
	/* POWEROFF */
	if (cpu_is_poweredoff(cpup)) {
		if (cpu_poweron(cpup)) {
			cmn_err(CE_WARN,
			    "sbd:%s: failed to power-on cpu %d",
			    f, cp->sbc_cpu_id);
			SBD_SET_ERR(ep, ESBD_CPUSTART);
			SBD_SET_ERRSTR(ep, sbp->sb_cpupath[unit]);
			rv = SBD_CPUERR_FATAL;
			goto out;
		}
		SBD_INJECT_ERR(SBD_POWERON_CPU_PSEUDO_ERR,
		    hp->h_err, EIO,
		    ESBD_CPUSTART,
		    sbp->sb_cpupath[unit]);
	}

	/* OFFLINE */
	if (cpu_is_offline(cpup)) {
		if (cpu_flagged_offline(cp->sbc_cpu_flags)) {
			PR_CPU("%s: leaving cpu %d OFFLINE\n",
			    f, cp->sbc_cpu_id);
		} else if (cpu_online(cpup)) {
			cmn_err(CE_WARN,
			    "sbd:%s: failed to online cpu %d",
			    f, cp->sbc_cpu_id);
			SBD_SET_ERR(ep, ESBD_ONLINE);
			SBD_SET_ERRSTR(ep, sbp->sb_cpupath[unit]);
			rv = SBD_CPUERR_FATAL;
			goto out;
		} else {
			SBD_INJECT_ERR(SBD_ONLINE_CPU_PSEUDO_ERR,
			    hp->h_err, EIO,
			    ESBD_ONLINE,
			    sbp->sb_cpupath[unit]);
		}
	}

	/* ONLINE */
	if (cpu_is_online(cpup)) {
		if (cpu_flagged_online(cp->sbc_cpu_flags)) {
			PR_CPU("%s: setting cpu %d ONLINE\n",
			    f, cp->sbc_cpu_id);
		} else if (cpu_flagged_offline(cp->sbc_cpu_flags)) {
			if (cpu_offline(cpup, cpu_offline_flags)) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to offline"
				    " cpu %d", f, cp->sbc_cpu_id);
				rv = SBD_CPUERR_RECOVERABLE;
				goto out;
			}
		} else if (cpu_flagged_nointr(cp->sbc_cpu_flags)) {
			if (cpu_intr_disable(cpup)) {
				cmn_err(CE_WARN, "%s: failed to "
				    "disable interrupts on cpu %d",
				    f, cp->sbc_cpu_id);
				rv = SBD_CPUERR_RECOVERABLE;
			} else {
				PR_CPU("%s: setting cpu %d to NOINTR"
				    " (was online)\n",
				    f, cp->sbc_cpu_id);
			}
			goto out;
		}
	}

	/* NOINTR */
	if (cpu_is_nointr(cpup)) {
		if (cpu_flagged_online(cp->sbc_cpu_flags)) {
			cpu_intr_enable(cpup);
			PR_CPU("%s: setting cpu %d ONLINE"
			    "(was nointr)\n",
			    f, cp->sbc_cpu_id);
		}
		if (cpu_flagged_offline(cp->sbc_cpu_flags)) {
			if (cpu_offline(cpup, cpu_offline_flags)) {
				cmn_err(CE_WARN,
				    "sbd:%s: failed to offline"
				    " cpu %d", f, cp->sbc_cpu_id);
				rv = SBD_CPUERR_RECOVERABLE;
			}
		}
	}
out:
	mutex_exit(&cpu_lock);

	return (rv);
}

int
sbd_connect_cpu(sbd_board_t *sbp, int unit)
{
	int		rv;
	processorid_t	cpuid;
	struct cpu	*cpu;
	dev_info_t	*dip;
	sbdp_handle_t	*hdp;
	extern kmutex_t	cpu_lock;
	static fn_t	f = "sbd_connect_cpu";
	sbd_handle_t	*hp = MACHBD2HD(sbp);

	/*
	 * get dip for cpu just located in tree walk
	 */
	if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_CPU, unit)) {
		dip = sbp->sb_devlist[NIX(SBD_COMP_CPU)][unit];
		if (dip == NULL) {
			cmn_err(CE_WARN,
			"sbd:%s: bad dip for cpu unit %d board %d",
			f, unit, sbp->sb_num);
			return (-1);
		}
		PR_CPU("%s...\n", f);
	} else {
		return (0);
	}

	/*
	 * if sbd has attached this cpu, no need to bring
	 * it out of reset
	 */
	if (SBD_DEV_IS_ATTACHED(sbp, SBD_COMP_CPU, unit)) {
		return (0);
	}

	hdp = sbd_get_sbdp_handle(sbp, hp);

	cpuid = sbdp_get_cpuid(hdp, dip);
	if (cpuid == -1) {
		sbd_release_sbdp_handle(hdp);
		return (-1);
	}

	/*
	 * if the cpu is already under Solaris control,
	 * do not wake it up
	 */
	mutex_enter(&cpu_lock);
	cpu = cpu_get(cpuid);
	mutex_exit(&cpu_lock);
	if (cpu != NULL) {
		sbd_release_sbdp_handle(hdp);
		return (0);
	}

	rv = sbdp_connect_cpu(hdp, dip, cpuid);

	if (rv != 0) {
		sbp->sb_memaccess_ok = 0;
		cmn_err(CE_WARN,
			"sbd:%s: failed to wake up cpu unit %d board %d",
			f, unit, sbp->sb_num);
		sbd_release_sbdp_handle(hdp);
		return (rv);
	}
	sbd_release_sbdp_handle(hdp);

	return (rv);
}

int
sbd_disconnect_cpu(sbd_handle_t *hp, int unit)
{
	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
	int		rv;
	dev_info_t	*dip;
	sbdp_handle_t	*hdp;
	sbd_cpu_unit_t *cp;
	processorid_t   cpuid;
	static fn_t	f = "sbd_disconnect_cpu";

	PR_CPU("%s...\n", f);

	ASSERT((SBD_DEVICE_STATE(sbp, SBD_COMP_CPU, unit) ==
						SBD_STATE_CONNECTED) ||
		(SBD_DEVICE_STATE(sbp, SBD_COMP_CPU, unit) ==
						SBD_STATE_UNCONFIGURED));

	cp = SBD_GET_BOARD_CPUUNIT(sbp, unit);

	cpuid = cp->sbc_cpu_id;

	dip = sbp->sb_devlist[NIX(SBD_COMP_CPU)][unit];

	hdp = sbd_get_sbdp_handle(sbp, hp);

	rv = sbdp_disconnect_cpu(hdp, dip, cpuid);

	if (rv != 0) {
		SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
	}
	sbd_release_sbdp_handle(hdp);

	return (rv);
}