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

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */

/*
 * CPU management for serengeti DR
 *
 * There are three states a CPU can be in:
 *
 *	disconnected:		In reset
 *	connect,unconfigured:	Idling in OBP's idle loop
 *	configured:		Running Solaris
 *
 * State transitions:
 *
 *                connect              configure
 *              ------------>         ------------>
 * disconnected              connected             configured
 *                          unconfigured
 *              <-----------         <-------------
 *                disconnect           unconfigure
 *
 * Firmware involvements
 *
 *              start_cpu(SC)
 *      prom_serengeti_wakeupcpu(OBP)
 *              ------------>         ------------------------->
 * disconnected              connected                         configured
 *                          unconfigured
 *              <-----------          <-------------------------
 *      prom_serengeti_cpu_off(OBP)  prom_serengeti_cpu_off(OBP)
 *               stop_cpu(SC)        prom_serengeti_wakeupcpu(OBP)
 *
 * SIR (Software Initiated Reset) is used to unconfigure a CPU.
 * After the CPU has completed flushing the caches, it issues an
 * sir instruction to put itself through POST.  POST detects that
 * it is an SIR, and re-enters OBP as a slave.  When the operation
 * completes successfully, the CPU will be idling in OBP.
 */

#include <sys/obpdefs.h>
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/cpuvar.h>
#include <sys/membar.h>
#include <sys/x_call.h>
#include <sys/machsystm.h>
#include <sys/cpu_sgnblk_defs.h>
#include <sys/pte.h>
#include <vm/hat_sfmmu.h>
#include <sys/promif.h>
#include <sys/note.h>
#include <sys/vmsystm.h>
#include <vm/seg_kmem.h>

#include <sys/sbd_ioctl.h>
#include <sys/sbd.h>
#include <sys/sbdp_priv.h>
#include <sys/sbdp_mem.h>
#include <sys/sbdp_error.h>
#include <sys/sgsbbc_iosram.h>
#include <sys/prom_plat.h>
#include <sys/cheetahregs.h>

uint64_t	*sbdp_valp;
extern uint64_t	va_to_pa(void *);
static int	sbdp_cpu_ntries = 50000;
static int	sbdp_cpu_delay = 100;
void		sbdp_get_cpu_sram_addr(uint64_t, uint64_t);
static int	cpusram_map(caddr_t *, pgcnt_t *);
static void	cpusram_unmap(caddr_t *, pgcnt_t);
extern int	prom_serengeti_wakeupcpu(pnode_t);
extern int	prom_serengeti_cpu_off(pnode_t);
extern sbdp_wnode_t *sbdp_get_wnodep(int);
extern caddr_t	sbdp_shutdown_va;
static int	sbdp_prom_get_cpu(void *arg, int changed);
static void	sbdp_cpu_shutdown_self(void);

int
sbdp_disconnect_cpu(sbdp_handle_t *hp, dev_info_t *dip, processorid_t cpuid)
{
	pnode_t		nodeid;
	int		bd, wnode;
	sbdp_wnode_t	*wnodep;
	sbdp_bd_t	*bdp = NULL;
	int		rv = 0;
	processorid_t	cpu = cpuid;
	processorid_t	portid;
	static fn_t	f = "sbdp_disconnect_cpu";

	SBDP_DBG_FUNC("%s\n", f);

	nodeid = ddi_get_nodeid(dip);

	/*
	 * Get board number and node number
	 * The check for determining if nodeid is valid is done inside
	 * sbdp_get_bd_and_wnode_num.
	 */
	if (SBDP_INJECT_ERROR(f, 0) ||
	    sbdp_get_bd_and_wnode_num(nodeid, &bd, &wnode) != 0) {

		rv = -1;
		goto out;
	}

	/*
	 * Grab the lock to prevent status threads from accessing
	 * registers on the CPU when it is being put into reset.
	 */
	wnodep = sbdp_get_wnodep(wnode);
	bdp = &wnodep->bds[bd];
	ASSERT(bdp);
	mutex_enter(&bdp->bd_mutex);

	/*
	 * Mark the CPU in reset.  This should be done before calling
	 * the SC because we won't know at which stage it failed if
	 * the SC call returns failure.
	 */
	sbdp_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid), 1);

	/*
	 * Ask OBP to mark the CPU as in POST
	 */
	if (SBDP_INJECT_ERROR(f, 1) || prom_serengeti_cpu_off(nodeid) != 0) {

		rv = -1;
		goto out;
	}

	/*
	 * Ask the SC to put the CPU into reset. If the first
	 * core is not present, the stop CPU interface needs
	 * to be called with the portid rather than the cpuid.
	 */
	portid = SG_CPUID_TO_PORTID(cpuid);
	if (!SBDP_IS_CPU_PRESENT(bdp, SG_CPUID_TO_CPU_UNIT(portid))) {
		cpu = portid;
	}

	if (SBDP_INJECT_ERROR(f, 2) || sbdp_stop_cpu(cpu) != 0) {

		rv = -1;
		goto out;
	}

out:
	if (bdp != NULL) {
		mutex_exit(&bdp->bd_mutex);
	}

	if (rv != 0) {
		sbdp_set_err(hp->h_err, ESGT_STOPCPU, NULL);
	}

	return (rv);
}

int
sbdp_connect_cpu(sbdp_handle_t *hp, dev_info_t *dip, processorid_t cpuid)
{
	pnode_t		nodeid;
	sbd_error_t	*sep;
	int		i;
	int		bd, wnode;
	int		rv = 0;
	static fn_t	f = "sbdp_connect_cpu";

	SBDP_DBG_FUNC("%s\n", f);

	sep = hp->h_err;

	nodeid = ddi_get_nodeid(dip);

	/*
	 * The check for determining if nodeid is valid is done inside
	 * sbdp_get_bd_and_wnode_num.
	 */
	if (SBDP_INJECT_ERROR(f, 0) ||
	    sbdp_get_bd_and_wnode_num(nodeid, &bd, &wnode) != 0) {

		rv = -1;
		goto out;
	}

	/*
	 * Ask the SC to bring the CPU out of reset.
	 * At this point, the sb_dev_present bit is not set for the CPU.
	 * From sbd point of view the CPU is not present yet.  No
	 * status threads will try to read registers off the CPU.
	 * Since we are already holding sb_mutex, it is not necessary
	 * to grab the board mutex when checking and setting the
	 * cpus_in_reset bit.
	 */
	if (sbdp_is_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid))) {

		sbdp_wnode_t	*wnodep;
		sbdp_bd_t	*bdp = NULL;
		processorid_t	cpu = cpuid;
		processorid_t	portid;

		wnodep = sbdp_get_wnodep(wnode);
		bdp = &wnodep->bds[bd];
		ASSERT(bdp);

		/*
		 * If the first core is not present, the start CPU
		 * interface needs to be called with the portid rather
		 * than the cpuid.
		 */
		portid = SG_CPUID_TO_PORTID(cpuid);
		if (!SBDP_IS_CPU_PRESENT(bdp, SG_CPUID_TO_CPU_UNIT(portid))) {
			cpu = portid;
		}

		if (SBDP_INJECT_ERROR(f, 1) || sbdp_start_cpu(cpu) != 0) {

			rv = -1;
			goto out;
		}

		if (SBDP_INJECT_ERROR(f, 2) ||
		    prom_serengeti_wakeupcpu(nodeid) != 0) {

			rv = -1;
			goto out;
		}
	}

	/*
	 * Mark the CPU out of reset.
	 */
	sbdp_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid), 0);

	/*
	 * Refresh the bd info
	 * we need to wait until all cpus are out of reset
	 */
	for (i = 0; i < SG_MAX_CPUS_PER_BD; i++)
		if (sbdp_is_cpu_present(wnode, bd, i) &&
		    sbdp_is_cpu_in_reset(wnode, bd, i) == 1) {
			break;
		}

	if (i == SG_MAX_CPUS_PER_BD) {
		/*
		 * All cpus are out of reset so it is safe to
		 * update the bd info
		 */
		sbdp_add_new_bd_info(wnode, bd);
	}

out:
	if (rv != 0)
		sbdp_set_err(sep, ESGT_WAKEUPCPU, NULL);

	return (rv);
}

int
sbdp_cpu_poweron(struct cpu *cp)
{
	int		cpuid;
	int		ntries;
	pnode_t		nodeid;
	extern void	restart_other_cpu(int);
	static fn_t	f = "sbdp_cpu_poweron";

	SBDP_DBG_FUNC("%s\n", f);

	ASSERT(MUTEX_HELD(&cpu_lock));

	ntries = sbdp_cpu_ntries;
	cpuid = cp->cpu_id;

	nodeid = cpunodes[cpuid].nodeid;
	ASSERT(nodeid != (pnode_t)0);

	/*
	 * This is a safe guard in case the CPU has taken a trap
	 * and idling in POST.
	 */
	if (SBDP_INJECT_ERROR(f, 0) ||
	    prom_serengeti_wakeupcpu(nodeid) != 0) {

		return (EBUSY);
	}

	cp->cpu_flags &= ~CPU_POWEROFF;

	/*
	 * NOTE: restart_other_cpu pauses cpus during the
	 *	slave cpu start.  This helps to quiesce the
	 *	bus traffic a bit which makes the tick sync
	 *	routine in the prom more robust.
	 */
	SBDP_DBG_CPU("%s: COLD START for cpu (%d)\n", f, cpuid);

	restart_other_cpu(cpuid);

	SBDP_DBG_CPU("after restarting other cpus\n");

	/*
	 * Wait for the cpu to reach its idle thread before
	 * we zap it with a request to blow away the mappings
	 * it (might) have for the sbdp_shutdown_asm code
	 * it may have executed on unconfigure.
	 */
	while ((cp->cpu_thread != cp->cpu_idle_thread) && (ntries > 0)) {
		DELAY(sbdp_cpu_delay);
		ntries--;
	}

	SBDP_DBG_CPU("%s: waited %d out of %d loops for cpu %d\n",
	    f, sbdp_cpu_ntries - ntries, sbdp_cpu_ntries, cpuid);

	return (0);
}


#define	SBDP_CPU_SRAM_ADDR	0x7fff0900000ull
#define	SBDP_CPU_SRAM_SIZE	0x20000ull

static const char cpyren_key[] = "COPYREN";

static uint64_t bbsram_pa;
static uint_t bbsram_size;

typedef struct {
	caddr_t		vaddr;
	pgcnt_t		npages;
	uint64_t	*pa;
	uint_t		*size;
} sbdp_cpu_sram_map_t;

int
sbdp_cpu_poweroff(struct cpu *cp)
{
	processorid_t	cpuid;
	pnode_t		nodeid;
	sbdp_cpu_sram_map_t	map;
	static fn_t	f = "sbdp_cpu_poweroff";

	SBDP_DBG_FUNC("%s\n", f);

	ASSERT(MUTEX_HELD(&cpu_lock));

	/*
	 * Capture all CPUs (except for detaching proc) to prevent
	 * crosscalls to the detaching proc until it has cleared its
	 * bit in cpu_ready_set.
	 */
	cpuid = cp->cpu_id;

	nodeid = cpunodes[cpuid].nodeid;
	ASSERT(nodeid != (pnode_t)0);

	*sbdp_valp = 0ull;
	/*
	 * Do the cpu sram mapping now.  This avoids problems with
	 * mutexes and high PILS
	 */
	if (SBDP_INJECT_ERROR(f, 0) ||
	    cpusram_map(&map.vaddr, &map.npages) != DDI_SUCCESS) {
		return (EBUSY);
	}

	map.pa = &bbsram_pa;
	map.size = &bbsram_size;

	/*
	 * Do a cross call to the cpu so it obtains the base address
	 */
	xc_one(cpuid, sbdp_get_cpu_sram_addr, (uint64_t)&map,
	    (uint64_t)NULL);

	cpusram_unmap(&map.vaddr, map.npages);

	if (SBDP_INJECT_ERROR(f, 1) || bbsram_size == 0) {
		cmn_err(CE_WARN, "cpu%d: Key \"%s\" missing from CPU SRAM TOC",
		    cpuid, cpyren_key);
		return (EBUSY);
	}

	if ((bbsram_pa & MMU_PAGEOFFSET) != 0) {
		cmn_err(CE_WARN, "cpu%d: CPU SRAM key \"%s\" not page aligned, "
		    "offset = 0x%lx", cpuid, cpyren_key,
		    (bbsram_pa - (uint64_t)SBDP_CPU_SRAM_ADDR));
		return (EBUSY);
	}

	if (bbsram_size < MMU_PAGESIZE) {
		cmn_err(CE_WARN, "cpu%d: CPU SRAM key \"%s\" too small, "
		    "size = 0x%x", cpuid, cpyren_key, bbsram_size);
		return (EBUSY);
	}

	/*
	 * Capture all CPUs (except for detaching proc) to prevent
	 * crosscalls to the detaching proc until it has cleared its
	 * bit in cpu_ready_set.
	 *
	 * The CPU's remain paused and the prom_mutex is known to be free.
	 * This prevents the x-trap victim from blocking when doing prom
	 * IEEE-1275 calls at a high PIL level.
	 */

	promsafe_pause_cpus();

	/*
	 * Quiesce interrupts on the target CPU. We do this by setting
	 * the CPU 'not ready'- (i.e. removing the CPU from cpu_ready_set) to
	 * prevent it from receiving cross calls and cross traps.
	 * This prevents the processor from receiving any new soft interrupts.
	 */

	mp_cpu_quiesce(cp);

	/* tell the prom the cpu is going away */
	if (SBDP_INJECT_ERROR(f, 2) || prom_serengeti_cpu_off(nodeid) != 0)
		return (EBUSY);

	/*
	 * An sir instruction is issued at the end of the shutdown
	 * routine to make the CPU go through POST and re-enter OBP.
	 */
	xt_one_unchecked(cp->cpu_id, (xcfunc_t *)idle_stop_xcall,
	    (uint64_t)sbdp_cpu_shutdown_self, 0);

	*sbdp_valp = 3ull;

	start_cpus();

	/*
	 * Wait until we reach the OBP idle loop or time out.
	 * prom_serengeti_wakeupcpu waits for up to 60 seconds for the
	 * CPU to reach OBP idle loop.
	 */
	if (SBDP_INJECT_ERROR(f, 3) ||
	    prom_serengeti_wakeupcpu(nodeid) != 0) {

		/*
		 * If it fails here, we still consider the unconfigure
		 * operation as successful.
		 */
		cmn_err(CE_WARN, "cpu%d: CPU failed to enter OBP idle loop.\n",
		    cpuid);
	}

	ASSERT(!(CPU_IN_SET(cpu_ready_set, cpuid)));

	bbsram_pa = 0;
	bbsram_size = 0;

	return (0);
}

processorid_t
sbdp_get_cpuid(sbdp_handle_t *hp, dev_info_t *dip)
{
	int		cpuid;
	char		type[OBP_MAXPROPNAME];
	pnode_t		nodeid;
	sbd_error_t	*sep;
	static fn_t	f = "sbdp_get_cpuid";

	SBDP_DBG_FUNC("%s\n", f);

	nodeid = ddi_get_nodeid(dip);
	if (sbdp_is_node_bad(nodeid))
		return (-1);

	sep = hp->h_err;

	if (prom_getproplen(nodeid, "device_type") < OBP_MAXPROPNAME)
		(void) prom_getprop(nodeid, "device_type", (caddr_t)type);
	else {
		sbdp_set_err(sep, ESGT_NO_DEV_TYPE, NULL);
		return (-1);
	}

	if (strcmp(type, "cpu") != 0) {
		sbdp_set_err(sep, ESGT_NOT_CPUTYPE, NULL);
		return (-1);
	}

	/*
	 * Check to see if property "cpuid" exists first.
	 * If not, check for "portid".
	 */
	if (prom_getprop(nodeid, "cpuid", (caddr_t)&cpuid) == -1)
		if (prom_getprop(nodeid, "portid", (caddr_t)&cpuid) == -1) {

			return (-1);
	}

	return ((processorid_t)cpuid & SG_CPU_ID_MASK);
}

int
sbdp_cpu_get_impl(sbdp_handle_t *hp, dev_info_t *dip)
{
	int		impl;
	char		type[OBP_MAXPROPNAME];
	pnode_t		nodeid;
	sbd_error_t	*sep;
	static fn_t	f = "sbdp_cpu_get_impl";

	SBDP_DBG_FUNC("%s\n", f);

	nodeid = ddi_get_nodeid(dip);
	if (sbdp_is_node_bad(nodeid))
		return (-1);

	sep = hp->h_err;

	if (prom_getproplen(nodeid, "device_type") < OBP_MAXPROPNAME)
		(void) prom_getprop(nodeid, "device_type", (caddr_t)type);
	else {
		sbdp_set_err(sep, ESGT_NO_DEV_TYPE, NULL);
		return (-1);
	}

	if (strcmp(type, "cpu") != 0) {
		sbdp_set_err(sep, ESGT_NOT_CPUTYPE, NULL);
		return (-1);
	}

	/*
	 * Get the implementation# property.
	 */
	if (prom_getprop(nodeid, "implementation#", (caddr_t)&impl) == -1)
		return (-1);

	return (impl);
}

struct sbdp_prom_get_node_args {
	pnode_t node;		/* current node */
	processorid_t portid;	/* portid we are looking for */
	pnode_t result_node;	/* node found with the above portid */
};

pnode_t
sbdp_find_nearby_cpu_by_portid(pnode_t nodeid, processorid_t portid)
{
	struct sbdp_prom_get_node_args arg;
	static fn_t	f = "sbdp_find_nearby_cpu_by_portid";

	SBDP_DBG_FUNC("%s\n", f);

	arg.node = nodeid;
	arg.portid = portid;
	(void) prom_tree_access(sbdp_prom_get_cpu, &arg, NULL);

	return (arg.result_node);
}

/*ARGSUSED*/
static int
sbdp_prom_get_cpu(void *arg, int changed)
{
	int	portid;
	pnode_t	parent, cur_node;
	struct sbdp_prom_get_node_args *argp = arg;
	static fn_t	f = "sbdp_prom_get_cpu";

	SBDP_DBG_FUNC("%s\n", f);

	parent = prom_parentnode(argp->node);

	for (cur_node = prom_childnode(parent); cur_node != OBP_NONODE;
	    cur_node = prom_nextnode(cur_node)) {

		if (prom_getprop(cur_node, OBP_PORTID, (caddr_t)&portid) < 0)
			continue;

		if ((portid == argp->portid) && (cur_node != argp->node))
			break;
	}

	argp->result_node = cur_node;

	return (0);
}


/*
 * A detaching CPU is xcalled with an xtrap to sbdp_cpu_stop_self() after
 * it has been offlined. The function of this routine is to get the cpu
 * spinning in a safe place. The requirement is that the system will not
 * reference anything on the detaching board (memory and i/o is detached
 * elsewhere) and that the CPU not reference anything on any other board
 * in the system.  This isolation is required during and after the writes
 * to the domain masks to remove the board from the domain.
 *
 * To accomplish this isolation the following is done:
 *	0) Map the CPUSRAM to obtain the correct address in SRAM
 *      1) Create a locked mapping to a location in CPU SRAM where
 *      the cpu will execute.
 *      2) Copy the target function (sbdp_shutdown_asm) in which
 *      the cpu will execute into CPU SRAM.
 *      3) Jump into function with CPU SRAM.
 *      Function will:
 *      3.1) Flush its Ecache (displacement).
 *      3.2) Flush its Dcache with HW mechanism.
 *      3.3) Flush its Icache with HW mechanism.
 *      3.4) Flush all valid and _unlocked_ D-TLB entries.
 *      3.5) Flush all valid and _unlocked_ I-TLB entries.
 *      4) Jump into a tight loop.
 */

static void
sbdp_cpu_stop_self(uint64_t pa)
{
	cpu_t		*cp = CPU;
	int		cpuid = cp->cpu_id;
	tte_t		tte;
	volatile uint_t	*src, *dst;
	size_t		funclen;
	sbdp_shutdown_t	sht;
	uint_t		bbsram_pfn;
	uint64_t	bbsram_addr;
	void		(*bbsram_func)(sbdp_shutdown_t *);
	extern void	sbdp_shutdown_asm(sbdp_shutdown_t *);
	extern void	sbdp_shutdown_asm_end(void);

	funclen = (uintptr_t)sbdp_shutdown_asm_end -
	    (uintptr_t)sbdp_shutdown_asm;
	ASSERT(funclen <= MMU_PAGESIZE);
	ASSERT(bbsram_pa != 0);
	ASSERT((bbsram_pa & MMU_PAGEOFFSET) == 0);
	ASSERT(bbsram_size >= MMU_PAGESIZE);

	stdphys(pa, 3);
	bbsram_pfn = (uint_t)(bbsram_pa >> MMU_PAGESHIFT);

	bbsram_addr = (uint64_t)sbdp_shutdown_va;
	sht.estack = bbsram_addr + MMU_PAGESIZE;
	sht.flushaddr = ecache_flushaddr;

	tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(TTE8K) |
	    TTE_PFN_INTHI(bbsram_pfn);
	tte.tte_intlo = TTE_PFN_INTLO(bbsram_pfn) |
	    TTE_HWWR_INT | TTE_PRIV_INT | TTE_LCK_INT;
	sfmmu_dtlb_ld_kva(sbdp_shutdown_va, &tte); /* load dtlb */
	sfmmu_itlb_ld_kva(sbdp_shutdown_va, &tte); /* load itlb */

	for (src = (uint_t *)sbdp_shutdown_asm, dst = (uint_t *)bbsram_addr;
	    src < (uint_t *)sbdp_shutdown_asm_end; src++, dst++)
	*dst = *src;

	bbsram_func = (void (*)())bbsram_addr;
	sht.size = (uint32_t)cpunodes[cpuid].ecache_size << 1;
	sht.linesize = (uint32_t)cpunodes[cpuid].ecache_linesize;
	sht.physaddr = pa;

	/*
	 * Signal to sbdp_cpu_poweroff() that we're just
	 * about done.
	 */
	cp->cpu_m.in_prom = 1;

	stdphys(pa, 4);
	(*bbsram_func)(&sht);
}

/* ARGSUSED */
void
sbdp_get_cpu_sram_addr(uint64_t arg1, uint64_t arg2)
{
	uint64_t	*pap;
	uint_t		*sizep;
	struct iosram_toc *tocp;
	uint_t		offset;
	uint_t		size;
	sbdp_cpu_sram_map_t *map;
	int		i;
	fn_t		f = "sbdp_get_cpu_sram_addr";

	SBDP_DBG_FUNC("%s\n", f);

	map = (sbdp_cpu_sram_map_t *)arg1;
	tocp = (struct iosram_toc *)map->vaddr;
	pap = map->pa;
	sizep = map->size;

	for (i = 0; i < tocp->iosram_tagno; i++) {
		if (strcmp(tocp->iosram_keys[i].key, cpyren_key) == 0)
			break;
	}
	if (i == tocp->iosram_tagno) {
		*pap = 0;
		*sizep = 0;
		return;
	}
	offset = tocp->iosram_keys[i].offset;
	size = tocp->iosram_keys[i].size;

	/*
	 * The address we want is the begining of cpusram + offset
	 */
	*pap = SBDP_CPU_SRAM_ADDR + offset;

	*sizep = size;
}

static int
cpusram_map(caddr_t *vaddrp, pgcnt_t *npp)
{
	uint_t		pgoffset;
	pgcnt_t		npages;
	pfn_t		pfn;
	uint64_t	base;
	caddr_t		kaddr;
	uint_t		mapping_attr;

	base = (uint64_t)SBDP_CPU_SRAM_ADDR & (~MMU_PAGEOFFSET);
	pfn = mmu_btop(base);

	/*
	 * Do a quick sanity check to make sure we are in I/O space.
	 */
	if (pf_is_memory(pfn))
		return (DDI_FAILURE);

	pgoffset = (ulong_t)SBDP_CPU_SRAM_ADDR & MMU_PAGEOFFSET;
	npages = mmu_btopr(SBDP_CPU_SRAM_SIZE + pgoffset);

	kaddr = vmem_alloc(heap_arena, ptob(npages), VM_NOSLEEP);
	if (kaddr == NULL)
		return (DDI_ME_NORESOURCES);

	mapping_attr = PROT_READ;
	/*
	 * Now map in the pages we've allocated...
	 */
	hat_devload(kas.a_hat, kaddr, ptob(npages), pfn, mapping_attr,
	    HAT_LOAD_LOCK);

	*vaddrp = kaddr + pgoffset;
	*npp = npages;

	return (DDI_SUCCESS);
}

static void
cpusram_unmap(caddr_t *vaddrp, pgcnt_t npages)
{
	uint_t  pgoffset;
	caddr_t base;
	caddr_t addr = *vaddrp;


	pgoffset = (ulong_t)SBDP_CPU_SRAM_ADDR & MMU_PAGEOFFSET;
	base = addr - pgoffset;
	hat_unload(kas.a_hat, base, ptob(npages), HAT_UNLOAD_UNLOCK);
	vmem_free(heap_arena, base, ptob(npages));

	*vaddrp = 0;
}


static void
sbdp_cpu_shutdown_self(void)
{
	cpu_t		*cp = CPU;
	int		cpuid = cp->cpu_id;
	extern void	flush_windows(void);
	uint64_t	pa = va_to_pa((void *)sbdp_valp);

	stdphys(pa, 8);
	flush_windows();

	(void) spl8();

	stdphys(pa, 6);

	ASSERT(cp->cpu_intr_actv == 0);
	ASSERT(cp->cpu_thread == cp->cpu_idle_thread ||
	    cp->cpu_thread == cp->cpu_startup_thread);

	cp->cpu_flags = CPU_OFFLINE | CPU_QUIESCED | CPU_POWEROFF;

	CPU_SIGNATURE(OS_SIG, SIGST_DETACHED, SIGSUBST_NULL, cpuid);

	stdphys(pa, 7);
	sbdp_cpu_stop_self(pa);

	cmn_err(CE_PANIC, "sbdp_cpu_shutdown_self: CPU %d FAILED TO SHUTDOWN",
	    cpuid);
}

typedef struct {
	int	node;
	int	board;
	int 	non_panther_cpus;
} sbdp_node_walk_t;

static int
sbdp_find_non_panther_cpus(dev_info_t *dip, void *node_args)
{
	int	impl, cpuid, portid;
	int	buflen;
	char	buf[OBP_MAXPROPNAME];
	sbdp_node_walk_t *args = (sbdp_node_walk_t *)node_args;

	if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, OBP_DEVICETYPE, (caddr_t)buf,
	    &buflen) != DDI_PROP_SUCCESS) {
		return (DDI_WALK_CONTINUE);
	}

	if (strcmp(buf, "cpu") != 0) {
		return (DDI_WALK_CONTINUE);
	}

	if ((impl = ddi_getprop(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "implementation#", -1)) == -1) {
		return (DDI_WALK_CONTINUE);
	}

	if ((cpuid = ddi_getprop(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "cpuid", -1)) == -1) {
		return (DDI_WALK_CONTINUE);
	}

	portid = SG_CPUID_TO_PORTID(cpuid);

	/* filter out nodes not on this board */
	if (SG_PORTID_TO_BOARD_NUM(portid) != args->board ||
	    SG_PORTID_TO_NODEID(portid) != args->node) {
		return (DDI_WALK_PRUNECHILD);
	}

	switch (impl) {
	case CHEETAH_IMPL:
	case CHEETAH_PLUS_IMPL:
	case JAGUAR_IMPL:
		args->non_panther_cpus++;
		break;
	case PANTHER_IMPL:
		break;
	default:
		ASSERT(0);
		args->non_panther_cpus++;
		break;
	}

	SBDP_DBG_CPU("cpuid=0x%x, portid=0x%x, impl=0x%x, device_type=%s",
	    cpuid, portid, impl, buf);

	return (DDI_WALK_CONTINUE);
}

int
sbdp_board_non_panther_cpus(int node, int board)
{
	sbdp_node_walk_t arg = {0};

	arg.node = node;
	arg.board = board;

	/*
	 * Root node doesn't have to be held.
	 */
	ddi_walk_devs(ddi_root_node(), sbdp_find_non_panther_cpus,
	    (void *)&arg);

	return (arg.non_panther_cpus);
}