OpenSolaris_b135/uts/sun4v/os/mach_mp_states.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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <sys/cpuvar.h>
#include <sys/cpu_module.h>
#include <sys/machsystm.h>
#include <sys/archsystm.h>
#include <sys/prom_plat.h>
#include <sys/hypervisor_api.h>
#include <sys/hsvc.h>

extern uint64_t xc_tick_limit;
extern uint64_t xc_tick_jump_limit;

extern void cpu_intrq_unregister_powerdown(uint64_t doneflag_va);

/*
 * set_idle_cpu is called from idle() when a CPU becomes idle.
 */
/*ARGSUSED*/
void
set_idle_cpu(int cpun)
{
}

/*
 * unset_idle_cpu is called from idle() when a CPU is no longer idle.
 */
/*ARGSUSED*/
void
unset_idle_cpu(int cpun)
{
}

/*
 * Stop a CPU based on its cpuid, using the cpu_stop hypervisor call.
 * Since this requires that the hypervisor force a remote CPU to stop,
 * the assumption is made that this should take roughly the same amount
 * of time as a executing a cross-call.  Consequently, the xcall
 * timeout is used to determine when to give up waiting for the CPU to
 * stop.
 *
 * Attempts to stop a CPU already in the stopped or error state will
 * silently succeed. Zero is returned on success and a non-negative
 * errno value is returned on failure.
 */
int
stopcpu_bycpuid(int cpuid)
{
	uint64_t	loop_cnt;
	uint64_t	state;
	uint64_t	rv;
	uint64_t	major = 0;
	uint64_t	minor = 0;
	uint64_t	cpu_stop_time_limit;
	extern uint64_t	xc_func_time_limit;

	ASSERT(MUTEX_HELD(&cpu_lock));

	/*
	 * Check the state of the CPU up front to see if an
	 * attempt to stop it is even necessary.
	 */
	if (hv_cpu_state(cpuid, &state) != H_EOK)
		return (EINVAL);

	/* treat stopped and error state the same */
	if (state != CPU_STATE_RUNNING) {
		/* nothing to do */
		return (0);
	}

	/*
	 * The HV API to stop a CPU is only supported in
	 * version 1.1 and later of the core group. If an
	 * older version of the HV is in use, return not
	 * supported.
	 */
	if (hsvc_version(HSVC_GROUP_CORE, &major, &minor) != 0)
		return (EINVAL);

	ASSERT(major != 0);

	if ((major == 1) && (minor < 1))
		return (ENOTSUP);

	/* use the mondo timeout if it has been initialized */
	cpu_stop_time_limit = xc_func_time_limit;

	/*
	 * If called early in boot before the mondo time limit
	 * is set, use a reasonable timeout based on the the
	 * clock frequency of the current CPU.
	 */
	if (cpu_stop_time_limit == 0)
		cpu_stop_time_limit = cpunodes[CPU->cpu_id].clock_freq;

	/* should only fail if called too early in boot */
	ASSERT(cpu_stop_time_limit > 0);

	loop_cnt = 0;

	/*
	 * Attempt to stop the CPU, retrying if it is busy.
	 */
	while (loop_cnt++ < cpu_stop_time_limit) {

		if ((rv = hv_cpu_stop(cpuid)) != H_EWOULDBLOCK)
			break;
	}

	if (loop_cnt == cpu_stop_time_limit)
		return (ETIMEDOUT);

	if (rv != H_EOK)
		return (EINVAL);

	/*
	 * Verify that the CPU has reached the stopped state.
	 */
	while (loop_cnt++ < cpu_stop_time_limit) {

		if (hv_cpu_state(cpuid, &state) != H_EOK)
			return (EINVAL);

		/* treat stopped and error state the same */
		if (state != CPU_STATE_RUNNING)
			break;
	}

	return ((loop_cnt == cpu_stop_time_limit) ? ETIMEDOUT : 0);
}

/*
 * X-trap to the target to unregister its interrupt and error queues
 * and put it in a safe place just before the CPU is stopped. After
 * unregistering its queues, the target CPU must not return from the
 * trap to priv or user context. Ensure that the interrupt CPU unregister
 * succeeded.
 */
void
xt_cpu_unreg_powerdown(struct cpu *cpup)
{
	uint8_t volatile not_done;
	uint64_t starttick, endtick, tick, lasttick;
	processorid_t cpuid = cpup->cpu_id;

	kpreempt_disable();

	/*
	 * Sun4v uses a queue for receiving mondos. Successful
	 * transmission of a mondo only indicates that the mondo
	 * has been written into the queue.
	 *
	 * Set the not_done flag to 1 before sending the cross
	 * trap and wait until the other cpu resets it to 0.
	 */

	not_done = 1;

	xt_one_unchecked(cpuid, (xcfunc_t *)cpu_intrq_unregister_powerdown,
	    (uint64_t)&not_done, 0);

	starttick = lasttick = gettick();
	endtick = starttick + xc_tick_limit;

	while (not_done) {

		tick = gettick();

		/*
		 * If there is a big jump between the current tick
		 * count and lasttick, we have probably hit a break
		 * point. Adjust endtick accordingly to avoid panic.
		 */
		if (tick > (lasttick + xc_tick_jump_limit)) {
			endtick += (tick - lasttick);
		}

		lasttick = tick;
		if (tick > endtick) {
			cmn_err(CE_CONT, "Cross trap timeout at cpu id %x\n",
			    cpuid);
			cmn_err(CE_WARN, "xt_intrq_unreg_powerdown: timeout");
		}
	}

	kpreempt_enable();
}

int
plat_cpu_poweroff(struct cpu *cp)
{
	int		rv = 0;
	int		status;
	processorid_t	cpuid = cp->cpu_id;

	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.
	 *
	 * 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);

	/*
	 * Send a cross trap to the cpu to unregister its interrupt
	 * error queues.
	 */
	xt_cpu_unreg_powerdown(cp);

	cp->cpu_flags = CPU_OFFLINE | CPU_QUIESCED | CPU_POWEROFF;

	/* call into the Hypervisor to stop the CPU */
	if ((status = stopcpu_bycpuid(cpuid)) != 0) {
		rv = -1;
	}

	start_cpus();

	if (rv != 0) {
		cmn_err(CE_WARN, "failed to stop cpu %d (%d)", cpuid, status);
		/* mark the CPU faulted so that it cannot be onlined */
		cp->cpu_flags = CPU_OFFLINE | CPU_QUIESCED | CPU_FAULTED;
	}

	return (rv);
}

int
plat_cpu_poweron(struct cpu *cp)
{
	extern void	restart_other_cpu(int);

	ASSERT(MUTEX_HELD(&cpu_lock));

	cp->cpu_flags &= ~CPU_POWEROFF;

	restart_other_cpu(cp->cpu_id);

	return (0);
}