xref: /onnv-gate/usr/src/uts/i86pc/os/cpupm/pwrnow.c (revision 12826:fca99d9e3f2f)
18906SEric.Saxe@Sun.COM /*
28906SEric.Saxe@Sun.COM  * CDDL HEADER START
38906SEric.Saxe@Sun.COM  *
48906SEric.Saxe@Sun.COM  * The contents of this file are subject to the terms of the
58906SEric.Saxe@Sun.COM  * Common Development and Distribution License (the "License").
68906SEric.Saxe@Sun.COM  * You may not use this file except in compliance with the License.
78906SEric.Saxe@Sun.COM  *
88906SEric.Saxe@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
98906SEric.Saxe@Sun.COM  * or http://www.opensolaris.org/os/licensing.
108906SEric.Saxe@Sun.COM  * See the License for the specific language governing permissions
118906SEric.Saxe@Sun.COM  * and limitations under the License.
128906SEric.Saxe@Sun.COM  *
138906SEric.Saxe@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
148906SEric.Saxe@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
158906SEric.Saxe@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
168906SEric.Saxe@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
178906SEric.Saxe@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
188906SEric.Saxe@Sun.COM  *
198906SEric.Saxe@Sun.COM  * CDDL HEADER END
208906SEric.Saxe@Sun.COM  */
218906SEric.Saxe@Sun.COM /*
22*12826Skuriakose.kuruvilla@oracle.com  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
238906SEric.Saxe@Sun.COM  */
248906SEric.Saxe@Sun.COM 
258906SEric.Saxe@Sun.COM #include <sys/x86_archext.h>
268906SEric.Saxe@Sun.COM #include <sys/machsystm.h>
278906SEric.Saxe@Sun.COM #include <sys/x_call.h>
288906SEric.Saxe@Sun.COM #include <sys/acpi/acpi.h>
298906SEric.Saxe@Sun.COM #include <sys/acpica.h>
308906SEric.Saxe@Sun.COM #include <sys/pwrnow.h>
318906SEric.Saxe@Sun.COM #include <sys/cpu_acpi.h>
328906SEric.Saxe@Sun.COM #include <sys/cpupm.h>
338906SEric.Saxe@Sun.COM #include <sys/dtrace.h>
348906SEric.Saxe@Sun.COM #include <sys/sdt.h>
358906SEric.Saxe@Sun.COM 
368906SEric.Saxe@Sun.COM static int pwrnow_init(cpu_t *);
378906SEric.Saxe@Sun.COM static void pwrnow_fini(cpu_t *);
388906SEric.Saxe@Sun.COM static void pwrnow_power(cpuset_t, uint32_t);
3910488SMark.Haywood@Sun.COM static void pwrnow_stop(cpu_t *);
408906SEric.Saxe@Sun.COM 
418906SEric.Saxe@Sun.COM /*
428906SEric.Saxe@Sun.COM  * Interfaces for modules implementing AMD's PowerNow!.
438906SEric.Saxe@Sun.COM  */
448906SEric.Saxe@Sun.COM cpupm_state_ops_t pwrnow_ops = {
458906SEric.Saxe@Sun.COM 	"PowerNow! Technology",
468906SEric.Saxe@Sun.COM 	pwrnow_init,
478906SEric.Saxe@Sun.COM 	pwrnow_fini,
4810488SMark.Haywood@Sun.COM 	pwrnow_power,
4910488SMark.Haywood@Sun.COM 	pwrnow_stop
508906SEric.Saxe@Sun.COM };
518906SEric.Saxe@Sun.COM 
528906SEric.Saxe@Sun.COM /*
538906SEric.Saxe@Sun.COM  * Error returns
548906SEric.Saxe@Sun.COM  */
558906SEric.Saxe@Sun.COM #define	PWRNOW_RET_SUCCESS		0x00
568906SEric.Saxe@Sun.COM #define	PWRNOW_RET_NO_PM		0x01
578906SEric.Saxe@Sun.COM #define	PWRNOW_RET_UNSUP_STATE		0x02
588906SEric.Saxe@Sun.COM #define	PWRNOW_RET_TRANS_INCOMPLETE	0x03
598906SEric.Saxe@Sun.COM 
608906SEric.Saxe@Sun.COM #define	PWRNOW_LATENCY_WAIT		10
618906SEric.Saxe@Sun.COM 
628906SEric.Saxe@Sun.COM /*
638906SEric.Saxe@Sun.COM  * MSR registers for changing and reading processor power state.
648906SEric.Saxe@Sun.COM  */
658906SEric.Saxe@Sun.COM #define	PWRNOW_PERF_CTL_MSR		0xC0010062
668906SEric.Saxe@Sun.COM #define	PWRNOW_PERF_STATUS_MSR		0xC0010063
678906SEric.Saxe@Sun.COM 
688906SEric.Saxe@Sun.COM #define	AMD_CPUID_PSTATE_HARDWARE	(1<<7)
698906SEric.Saxe@Sun.COM #define	AMD_CPUID_TSC_CONSTANT		(1<<8)
708906SEric.Saxe@Sun.COM 
718906SEric.Saxe@Sun.COM /*
728906SEric.Saxe@Sun.COM  * Debugging support
738906SEric.Saxe@Sun.COM  */
748906SEric.Saxe@Sun.COM #ifdef	DEBUG
758906SEric.Saxe@Sun.COM volatile int pwrnow_debug = 0;
768906SEric.Saxe@Sun.COM #define	PWRNOW_DEBUG(arglist) if (pwrnow_debug) printf arglist;
778906SEric.Saxe@Sun.COM #else
788906SEric.Saxe@Sun.COM #define	PWRNOW_DEBUG(arglist)
798906SEric.Saxe@Sun.COM #endif
808906SEric.Saxe@Sun.COM 
818906SEric.Saxe@Sun.COM /*
828906SEric.Saxe@Sun.COM  * Write the ctrl register.
838906SEric.Saxe@Sun.COM  */
848906SEric.Saxe@Sun.COM static void
write_ctrl(cpu_acpi_handle_t handle,uint32_t ctrl)858906SEric.Saxe@Sun.COM write_ctrl(cpu_acpi_handle_t handle, uint32_t ctrl)
868906SEric.Saxe@Sun.COM {
878906SEric.Saxe@Sun.COM 	cpu_acpi_pct_t *pct_ctrl;
888906SEric.Saxe@Sun.COM 	uint64_t reg;
898906SEric.Saxe@Sun.COM 
908906SEric.Saxe@Sun.COM 	pct_ctrl = CPU_ACPI_PCT_CTRL(handle);
918906SEric.Saxe@Sun.COM 
928906SEric.Saxe@Sun.COM 	switch (pct_ctrl->cr_addrspace_id) {
938906SEric.Saxe@Sun.COM 	case ACPI_ADR_SPACE_FIXED_HARDWARE:
948906SEric.Saxe@Sun.COM 		reg = ctrl;
958906SEric.Saxe@Sun.COM 		wrmsr(PWRNOW_PERF_CTL_MSR, reg);
968906SEric.Saxe@Sun.COM 		break;
978906SEric.Saxe@Sun.COM 
988906SEric.Saxe@Sun.COM 	default:
998906SEric.Saxe@Sun.COM 		DTRACE_PROBE1(pwrnow_ctrl_unsupported_type, uint8_t,
1008906SEric.Saxe@Sun.COM 		    pct_ctrl->cr_addrspace_id);
1018906SEric.Saxe@Sun.COM 		return;
1028906SEric.Saxe@Sun.COM 	}
1038906SEric.Saxe@Sun.COM 
1048906SEric.Saxe@Sun.COM 	DTRACE_PROBE1(pwrnow_ctrl_write, uint32_t, ctrl);
1058906SEric.Saxe@Sun.COM }
1068906SEric.Saxe@Sun.COM 
1078906SEric.Saxe@Sun.COM /*
1088906SEric.Saxe@Sun.COM  * Transition the current processor to the requested state.
1098906SEric.Saxe@Sun.COM  */
1108906SEric.Saxe@Sun.COM static void
pwrnow_pstate_transition(uint32_t req_state)1118906SEric.Saxe@Sun.COM pwrnow_pstate_transition(uint32_t req_state)
1128906SEric.Saxe@Sun.COM {
1138906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state =
1148906SEric.Saxe@Sun.COM 	    (cpupm_mach_state_t *)CPU->cpu_m.mcpu_pm_mach_state;
1158906SEric.Saxe@Sun.COM 	cpu_acpi_handle_t handle = mach_state->ms_acpi_handle;
1168906SEric.Saxe@Sun.COM 	cpu_acpi_pstate_t *req_pstate;
1178906SEric.Saxe@Sun.COM 	uint32_t ctrl;
1188906SEric.Saxe@Sun.COM 
1198906SEric.Saxe@Sun.COM 	req_pstate = (cpu_acpi_pstate_t *)CPU_ACPI_PSTATES(handle);
1208906SEric.Saxe@Sun.COM 	req_pstate += req_state;
1218906SEric.Saxe@Sun.COM 
1228906SEric.Saxe@Sun.COM 	DTRACE_PROBE1(pwrnow_transition_freq, uint32_t,
1238906SEric.Saxe@Sun.COM 	    CPU_ACPI_FREQ(req_pstate));
1248906SEric.Saxe@Sun.COM 
1258906SEric.Saxe@Sun.COM 	/*
1268906SEric.Saxe@Sun.COM 	 * Initiate the processor p-state change.
1278906SEric.Saxe@Sun.COM 	 */
1288906SEric.Saxe@Sun.COM 	ctrl = CPU_ACPI_PSTATE_CTRL(req_pstate);
1298906SEric.Saxe@Sun.COM 	write_ctrl(handle, ctrl);
1308906SEric.Saxe@Sun.COM 
1318906SEric.Saxe@Sun.COM 	mach_state->ms_pstate.cma_state.pstate = req_state;
1328906SEric.Saxe@Sun.COM 	cpu_set_curr_clock((uint64_t)CPU_ACPI_FREQ(req_pstate) * 1000000);
1338906SEric.Saxe@Sun.COM }
1348906SEric.Saxe@Sun.COM 
1358906SEric.Saxe@Sun.COM static void
pwrnow_power(cpuset_t set,uint32_t req_state)1368906SEric.Saxe@Sun.COM pwrnow_power(cpuset_t set, uint32_t req_state)
1378906SEric.Saxe@Sun.COM {
1388906SEric.Saxe@Sun.COM 	/*
1398906SEric.Saxe@Sun.COM 	 * If thread is already running on target CPU then just
1408906SEric.Saxe@Sun.COM 	 * make the transition request. Otherwise, we'll need to
1418906SEric.Saxe@Sun.COM 	 * make a cross-call.
1428906SEric.Saxe@Sun.COM 	 */
1438906SEric.Saxe@Sun.COM 	kpreempt_disable();
1448906SEric.Saxe@Sun.COM 	if (CPU_IN_SET(set, CPU->cpu_id)) {
1458906SEric.Saxe@Sun.COM 		pwrnow_pstate_transition(req_state);
1468906SEric.Saxe@Sun.COM 		CPUSET_DEL(set, CPU->cpu_id);
1478906SEric.Saxe@Sun.COM 	}
1488906SEric.Saxe@Sun.COM 	if (!CPUSET_ISNULL(set)) {
1499489SJoe.Bonasera@sun.com 		xc_call((xc_arg_t)req_state, NULL, NULL,
1509489SJoe.Bonasera@sun.com 		    CPUSET2BV(set), (xc_func_t)pwrnow_pstate_transition);
1518906SEric.Saxe@Sun.COM 	}
1528906SEric.Saxe@Sun.COM 	kpreempt_enable();
1538906SEric.Saxe@Sun.COM }
1548906SEric.Saxe@Sun.COM 
1558906SEric.Saxe@Sun.COM /*
1568906SEric.Saxe@Sun.COM  * Validate that this processor supports PowerNow! and if so,
1578906SEric.Saxe@Sun.COM  * get the P-state data from ACPI and cache it.
1588906SEric.Saxe@Sun.COM  */
1598906SEric.Saxe@Sun.COM static int
pwrnow_init(cpu_t * cp)1608906SEric.Saxe@Sun.COM pwrnow_init(cpu_t *cp)
1618906SEric.Saxe@Sun.COM {
1628906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state =
1638906SEric.Saxe@Sun.COM 	    (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state;
1648906SEric.Saxe@Sun.COM 	cpu_acpi_handle_t handle = mach_state->ms_acpi_handle;
1658906SEric.Saxe@Sun.COM 	cpu_acpi_pct_t *pct_stat;
1668906SEric.Saxe@Sun.COM 
1678906SEric.Saxe@Sun.COM 	PWRNOW_DEBUG(("pwrnow_init: processor %d\n", cp->cpu_id));
1688906SEric.Saxe@Sun.COM 
1698906SEric.Saxe@Sun.COM 	/*
1708906SEric.Saxe@Sun.COM 	 * Cache the P-state specific ACPI data.
1718906SEric.Saxe@Sun.COM 	 */
1728906SEric.Saxe@Sun.COM 	if (cpu_acpi_cache_pstate_data(handle) != 0) {
17310075SMark.Haywood@Sun.COM 		cmn_err(CE_NOTE, "!PowerNow! support is being "
17410075SMark.Haywood@Sun.COM 		    "disabled due to errors parsing ACPI P-state objects "
17510075SMark.Haywood@Sun.COM 		    "exported by BIOS.");
1768906SEric.Saxe@Sun.COM 		pwrnow_fini(cp);
1778906SEric.Saxe@Sun.COM 		return (PWRNOW_RET_NO_PM);
1788906SEric.Saxe@Sun.COM 	}
1798906SEric.Saxe@Sun.COM 
1808906SEric.Saxe@Sun.COM 	pct_stat = CPU_ACPI_PCT_STATUS(handle);
1818906SEric.Saxe@Sun.COM 	switch (pct_stat->cr_addrspace_id) {
1828906SEric.Saxe@Sun.COM 	case ACPI_ADR_SPACE_FIXED_HARDWARE:
1838906SEric.Saxe@Sun.COM 		PWRNOW_DEBUG(("Transitions will use fixed hardware\n"));
1848906SEric.Saxe@Sun.COM 		break;
1858906SEric.Saxe@Sun.COM 	default:
1868906SEric.Saxe@Sun.COM 		cmn_err(CE_WARN, "!_PCT configured for unsupported "
1878906SEric.Saxe@Sun.COM 		    "addrspace = %d.", pct_stat->cr_addrspace_id);
1888906SEric.Saxe@Sun.COM 		cmn_err(CE_NOTE, "!CPU power management will not function.");
1898906SEric.Saxe@Sun.COM 		pwrnow_fini(cp);
1908906SEric.Saxe@Sun.COM 		return (PWRNOW_RET_NO_PM);
1918906SEric.Saxe@Sun.COM 	}
1928906SEric.Saxe@Sun.COM 
1938906SEric.Saxe@Sun.COM 	cpupm_alloc_domains(cp, CPUPM_P_STATES);
1948906SEric.Saxe@Sun.COM 
1958906SEric.Saxe@Sun.COM 	PWRNOW_DEBUG(("Processor %d succeeded.\n", cp->cpu_id))
1968906SEric.Saxe@Sun.COM 	return (PWRNOW_RET_SUCCESS);
1978906SEric.Saxe@Sun.COM }
1988906SEric.Saxe@Sun.COM 
1998906SEric.Saxe@Sun.COM /*
2008906SEric.Saxe@Sun.COM  * Free resources allocated by pwrnow_init().
2018906SEric.Saxe@Sun.COM  */
2028906SEric.Saxe@Sun.COM static void
pwrnow_fini(cpu_t * cp)2038906SEric.Saxe@Sun.COM pwrnow_fini(cpu_t *cp)
2048906SEric.Saxe@Sun.COM {
2058906SEric.Saxe@Sun.COM 	cpupm_mach_state_t *mach_state =
2068906SEric.Saxe@Sun.COM 	    (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
2078906SEric.Saxe@Sun.COM 	cpu_acpi_handle_t handle = mach_state->ms_acpi_handle;
2088906SEric.Saxe@Sun.COM 
2098906SEric.Saxe@Sun.COM 	cpupm_free_domains(&cpupm_pstate_domains);
2108906SEric.Saxe@Sun.COM 	cpu_acpi_free_pstate_data(handle);
2118906SEric.Saxe@Sun.COM }
2128906SEric.Saxe@Sun.COM 
2138906SEric.Saxe@Sun.COM boolean_t
pwrnow_supported()2148906SEric.Saxe@Sun.COM pwrnow_supported()
2158906SEric.Saxe@Sun.COM {
2168906SEric.Saxe@Sun.COM 	struct cpuid_regs cpu_regs;
2178906SEric.Saxe@Sun.COM 
2188906SEric.Saxe@Sun.COM 	/* Required features */
219*12826Skuriakose.kuruvilla@oracle.com 	if (!is_x86_feature(x86_featureset, X86FSET_CPUID) ||
220*12826Skuriakose.kuruvilla@oracle.com 	    !is_x86_feature(x86_featureset, X86FSET_MSR)) {
2218906SEric.Saxe@Sun.COM 		PWRNOW_DEBUG(("No CPUID or MSR support."));
2228906SEric.Saxe@Sun.COM 		return (B_FALSE);
2238906SEric.Saxe@Sun.COM 	}
2248906SEric.Saxe@Sun.COM 
2258906SEric.Saxe@Sun.COM 	/*
2268906SEric.Saxe@Sun.COM 	 * Get the Advanced Power Management Information.
2278906SEric.Saxe@Sun.COM 	 */
2288906SEric.Saxe@Sun.COM 	cpu_regs.cp_eax = 0x80000007;
2298906SEric.Saxe@Sun.COM 	(void) __cpuid_insn(&cpu_regs);
2308906SEric.Saxe@Sun.COM 
2318906SEric.Saxe@Sun.COM 	/*
2328906SEric.Saxe@Sun.COM 	 * We currently only support CPU power management of
2338906SEric.Saxe@Sun.COM 	 * processors that are P-state TSC invariant
2348906SEric.Saxe@Sun.COM 	 */
2358906SEric.Saxe@Sun.COM 	if (!(cpu_regs.cp_edx & AMD_CPUID_TSC_CONSTANT)) {
2368906SEric.Saxe@Sun.COM 		PWRNOW_DEBUG(("No support for CPUs that are not P-state "
2378906SEric.Saxe@Sun.COM 		    "TSC invariant.\n"));
2388906SEric.Saxe@Sun.COM 		return (B_FALSE);
2398906SEric.Saxe@Sun.COM 	}
2408906SEric.Saxe@Sun.COM 
2418906SEric.Saxe@Sun.COM 	/*
2428906SEric.Saxe@Sun.COM 	 * We only support the "Fire and Forget" style of PowerNow! (i.e.,
2438906SEric.Saxe@Sun.COM 	 * single MSR write to change speed).
2448906SEric.Saxe@Sun.COM 	 */
2458906SEric.Saxe@Sun.COM 	if (!(cpu_regs.cp_edx & AMD_CPUID_PSTATE_HARDWARE)) {
2468906SEric.Saxe@Sun.COM 		PWRNOW_DEBUG(("Hardware P-State control is not supported.\n"));
2478906SEric.Saxe@Sun.COM 		return (B_FALSE);
2488906SEric.Saxe@Sun.COM 	}
2498906SEric.Saxe@Sun.COM 	return (B_TRUE);
2508906SEric.Saxe@Sun.COM }
25110488SMark.Haywood@Sun.COM 
25210488SMark.Haywood@Sun.COM static void
pwrnow_stop(cpu_t * cp)25310488SMark.Haywood@Sun.COM pwrnow_stop(cpu_t *cp)
25410488SMark.Haywood@Sun.COM {
25510488SMark.Haywood@Sun.COM 	cpupm_mach_state_t *mach_state =
25610488SMark.Haywood@Sun.COM 	    (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
25710488SMark.Haywood@Sun.COM 	cpu_acpi_handle_t handle = mach_state->ms_acpi_handle;
25810488SMark.Haywood@Sun.COM 
25910488SMark.Haywood@Sun.COM 	cpupm_remove_domains(cp, CPUPM_P_STATES, &cpupm_pstate_domains);
26010488SMark.Haywood@Sun.COM 	cpu_acpi_free_pstate_data(handle);
26110488SMark.Haywood@Sun.COM }
262