summaryrefslogtreecommitdiff
path: root/usr/src/uts/i86pc/io/cpudrv/speedstep.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/i86pc/io/cpudrv/speedstep.c')
-rw-r--r--usr/src/uts/i86pc/io/cpudrv/speedstep.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/usr/src/uts/i86pc/io/cpudrv/speedstep.c b/usr/src/uts/i86pc/io/cpudrv/speedstep.c
new file mode 100644
index 0000000000..7d9724c69e
--- /dev/null
+++ b/usr/src/uts/i86pc/io/cpudrv/speedstep.c
@@ -0,0 +1,361 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/x86_archext.h>
+#include <sys/machsystm.h>
+#include <sys/x_call.h>
+#include <sys/acpi/acpi.h>
+#include <sys/acpica.h>
+#include <sys/cpudrv_mach.h>
+#include <sys/speedstep.h>
+#include <sys/cpu_acpi.h>
+#include <sys/cpupm.h>
+#include <sys/dtrace.h>
+#include <sys/sdt.h>
+
+static int speedstep_init(cpudrv_devstate_t *);
+static void speedstep_fini(cpudrv_devstate_t *);
+static int speedstep_power(cpudrv_devstate_t *, uint32_t);
+
+/*
+ * Interfaces for modules implementing Intel's Enhanced SpeedStep.
+ */
+cpudrv_pstate_ops_t speedstep_ops = {
+ "Enhanced SpeedStep Technology",
+ speedstep_init,
+ speedstep_fini,
+ speedstep_power
+};
+
+/*
+ * Error returns
+ */
+#define ESS_RET_SUCCESS 0x00
+#define ESS_RET_NO_PM 0x01
+#define ESS_RET_UNSUP_STATE 0x02
+
+/*
+ * Intel docs indicate that maximum latency of P-state changes should
+ * be on the order of 10mS. When waiting, wait in 100uS increments.
+ */
+#define ESS_MAX_LATENCY_MICROSECS 10000
+#define ESS_LATENCY_WAIT 100
+
+/*
+ * The SpeedStep related Processor Driver Capabilities (_PDC).
+ * See Intel Processor Vendor-Specific ACPI Interface Specification
+ * for details.
+ */
+#define ESS_PDC_REVISION 0x1
+#define ESS_PDC_PS_MSR (1<<0)
+#define ESS_PDC_IO_BEFORE_HALT (1<<1)
+#define ESS_PDC_MP (1<<3)
+#define ESS_PDC_PSD (1<<5)
+
+/*
+ * MSR registers for changing and reading processor power state.
+ */
+#define IA32_PERF_STAT_MSR 0x198
+#define IA32_PERF_CTL_MSR 0x199
+
+#define IA32_CPUID_TSC_CONSTANT 0xF30
+#define IA32_MISC_ENABLE_MSR 0x1A0
+#define IA32_MISC_ENABLE_EST (1<<16)
+#define IA32_MISC_ENABLE_CXE (1<<25)
+/*
+ * Debugging support
+ */
+#ifdef DEBUG
+volatile int ess_debug = 0;
+#define ESSDEBUG(arglist) if (ess_debug) printf arglist;
+#else
+#define ESSDEBUG(arglist)
+#endif
+
+/*
+ * Note that SpeedStep support requires the following _PDC bits be
+ * enabled so that ACPI returns the proper objects. The requirement
+ * that ESS_PDC_IO_BEFORE_HALT be enabled probably seems strange.
+ * Unfortunately, the _PDC bit for this feature has been historically
+ * misassociated with SpeedStep support and some BIOS implementations
+ * erroneously check this bit when evaluating _PSS methods. Enabling
+ * this bit is our only option as the likelihood of a BIOS fix on all
+ * affected platforms is not very good.
+ */
+uint32_t ess_pdccap = ESS_PDC_PS_MSR | ESS_PDC_IO_BEFORE_HALT |
+ ESS_PDC_MP | ESS_PDC_PSD;
+
+/*
+ * Read the status register. How it is read, depends upon the _PCT
+ * APCI object value.
+ */
+static int
+read_status(cpu_acpi_handle_t handle, uint32_t *stat)
+{
+ cpu_acpi_pct_t *pct_stat;
+ uint64_t reg;
+ int ret = 0;
+
+ pct_stat = CPU_ACPI_PCT_STATUS(handle);
+
+ switch (pct_stat->cr_addrspace_id) {
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+ reg = rdmsr(IA32_PERF_STAT_MSR);
+ *stat = reg & 0x1E;
+ ret = 0;
+ break;
+
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+ ret = cpu_acpi_read_port(pct_stat->cr_address, stat,
+ pct_stat->cr_width);
+ break;
+
+ default:
+ DTRACE_PROBE1(ess_status_unsupported_type, uint8_t,
+ pct_stat->cr_addrspace_id);
+ return (-1);
+ }
+
+ DTRACE_PROBE1(ess_status_read, uint32_t, *stat);
+ DTRACE_PROBE1(ess_status_read_err, int, ret);
+
+ return (ret);
+}
+
+/*
+ * Write the ctrl register. How it is written, depends upon the _PCT
+ * APCI object value.
+ */
+static int
+write_ctrl(cpu_acpi_handle_t handle, uint32_t ctrl)
+{
+ cpu_acpi_pct_t *pct_ctrl;
+ uint64_t reg;
+ int ret = 0;
+
+ pct_ctrl = CPU_ACPI_PCT_CTRL(handle);
+
+ switch (pct_ctrl->cr_addrspace_id) {
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+ /*
+ * Read current power state because reserved bits must be
+ * preserved, compose new value, and write it.
+ */
+ reg = rdmsr(IA32_PERF_CTL_MSR);
+ reg &= ~((uint64_t)0xFFFF);
+ reg |= ctrl;
+ wrmsr(IA32_PERF_CTL_MSR, reg);
+ ret = 0;
+ break;
+
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+ ret = cpu_acpi_write_port(pct_ctrl->cr_address, ctrl,
+ pct_ctrl->cr_width);
+ break;
+
+ default:
+ DTRACE_PROBE1(ess_ctrl_unsupported_type, uint8_t,
+ pct_ctrl->cr_addrspace_id);
+ return (-1);
+ }
+
+ DTRACE_PROBE1(ess_ctrl_write, uint32_t, ctrl);
+ DTRACE_PROBE1(ess_ctrl_write_err, int, ret);
+
+ return (ret);
+}
+
+/*
+ * Transition the current processor to the requested state.
+ */
+void
+speedstep_pstate_transition(int *ret, cpudrv_devstate_t *cpudsp,
+ uint32_t req_state)
+{
+ cpudrv_mach_state_t *mach_state = cpudsp->mach_state;
+ cpu_acpi_handle_t handle = mach_state->acpi_handle;
+ cpu_acpi_pstate_t *req_pstate;
+ uint32_t ctrl;
+ uint32_t stat;
+ int i;
+
+ req_pstate = (cpu_acpi_pstate_t *)CPU_ACPI_PSTATES(handle);
+ req_pstate += req_state;
+ DTRACE_PROBE1(ess_transition, uint32_t, CPU_ACPI_FREQ(req_pstate));
+
+ /*
+ * Initiate the processor p-state change.
+ */
+ ctrl = CPU_ACPI_PSTATE_CTRL(req_pstate);
+ if (write_ctrl(handle, ctrl) != 0) {
+ *ret = ESS_RET_UNSUP_STATE;
+ return;
+ }
+
+ /* Wait until switch is complete, but bound the loop just in case. */
+ for (i = CPU_ACPI_PSTATE_TRANSLAT(req_pstate) * 2; i >= 0;
+ i -= ESS_LATENCY_WAIT) {
+ if (read_status(handle, &stat) == 0 &&
+ CPU_ACPI_PSTATE_STAT(req_pstate) == stat)
+ break;
+ drv_usecwait(ESS_LATENCY_WAIT);
+ }
+ if (i >= ESS_MAX_LATENCY_MICROSECS) {
+ DTRACE_PROBE(ess_transition_incomplete);
+ }
+
+ mach_state->pstate = req_state;
+ CPU->cpu_curr_clock =
+ (((uint64_t)CPU_ACPI_FREQ(req_pstate) * 1000000));
+ *ret = ESS_RET_SUCCESS;
+}
+
+static int
+speedstep_power(cpudrv_devstate_t *cpudsp, uint32_t req_state)
+{
+ cpuset_t cpus;
+ int ret;
+
+ CPUSET_ONLY(cpus, cpudsp->cpu_id);
+
+ kpreempt_disable();
+ xc_call((xc_arg_t)&ret, (xc_arg_t)cpudsp, (xc_arg_t)req_state,
+ X_CALL_HIPRI, cpus, (xc_func_t)speedstep_pstate_transition);
+ kpreempt_enable();
+
+ return (ret);
+}
+
+/*
+ * Validate that this processor supports Speedstep and if so,
+ * get the P-state data from ACPI and cache it.
+ */
+static int
+speedstep_init(cpudrv_devstate_t *cpudsp)
+{
+ cpudrv_mach_state_t *mach_state = cpudsp->mach_state;
+ cpu_acpi_handle_t handle = mach_state->acpi_handle;
+ cpu_acpi_pct_t *pct_stat;
+ cpu_t *cp;
+ int dependency;
+
+ ESSDEBUG(("speedstep_init: instance %d\n",
+ ddi_get_instance(cpudsp->dip)));
+
+ /*
+ * Cache the P-state specific ACPI data.
+ */
+ if (cpu_acpi_cache_pstate_data(handle) != 0) {
+ ESSDEBUG(("Failed to cache ACPI data\n"));
+ speedstep_fini(cpudsp);
+ return (ESS_RET_NO_PM);
+ }
+
+ pct_stat = CPU_ACPI_PCT_STATUS(handle);
+ switch (pct_stat->cr_addrspace_id) {
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+ ESSDEBUG(("Transitions will use fixed hardware\n"));
+ break;
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+ ESSDEBUG(("Transitions will use system IO\n"));
+ break;
+ default:
+ cmn_err(CE_WARN, "!_PCT conifgured for unsupported "
+ "addrspace = %d.", pct_stat->cr_addrspace_id);
+ cmn_err(CE_NOTE, "!CPU power management will not function.");
+ speedstep_fini(cpudsp);
+ return (ESS_RET_NO_PM);
+ }
+
+ if (CPU_ACPI_IS_OBJ_CACHED(handle, CPU_ACPI_PSD_CACHED))
+ dependency = CPU_ACPI_PSD(handle).sd_domain;
+ else {
+ mutex_enter(&cpu_lock);
+ cp = cpu[CPU->cpu_id];
+ dependency = cpuid_get_chipid(cp);
+ mutex_exit(&cpu_lock);
+ }
+ cpupm_add_cpu2dependency(cpudsp->dip, dependency);
+
+ ESSDEBUG(("Instance %d succeeded.\n", ddi_get_instance(cpudsp->dip)));
+ return (ESS_RET_SUCCESS);
+}
+
+/*
+ * Free resources allocated by speedstep_init().
+ */
+static void
+speedstep_fini(cpudrv_devstate_t *cpudsp)
+{
+ cpudrv_mach_state_t *mach_state = cpudsp->mach_state;
+ cpu_acpi_handle_t handle = mach_state->acpi_handle;
+
+ cpupm_free_cpu_dependencies();
+ cpu_acpi_free_pstate_data(handle);
+}
+
+boolean_t
+speedstep_supported(uint_t family, uint_t model)
+{
+ struct cpuid_regs cpu_regs;
+ uint64_t reg;
+
+ /* Required features */
+ if (!(x86_feature & X86_CPUID) ||
+ !(x86_feature & X86_MSR)) {
+ return (B_FALSE);
+ }
+
+ /*
+ * We only support family/model combinations which
+ * are P-state TSC invariant.
+ */
+ if (!((family == 0xf && model >= 0x3) ||
+ (family == 0x6 && model >= 0xe))) {
+ return (B_FALSE);
+ }
+
+ /*
+ * Enhanced SpeedStep supported?
+ */
+ cpu_regs.cp_eax = 0x1;
+ (void) __cpuid_insn(&cpu_regs);
+ if (!(cpu_regs.cp_ecx & CPUID_INTC_ECX_EST)) {
+ return (B_FALSE);
+ }
+
+ /*
+ * If Enhanced SpeedStep has not been enabled on the system,
+ * then we probably should not override the BIOS setting.
+ */
+ reg = rdmsr(IA32_MISC_ENABLE_MSR);
+ if (! (reg & IA32_MISC_ENABLE_EST)) {
+ cmn_err(CE_NOTE, "!Enhanced Intel SpeedStep not enabled.");
+ cmn_err(CE_NOTE, "!CPU power management will not function.");
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}