Module Name: src
Committed By: jruoho
Date: Sun Aug 8 16:58:42 UTC 2010
Modified Files:
src/sys/arch/x86/acpi: acpi_cpu_md.c
src/sys/arch/x86/include: cpuvar.h
src/sys/arch/x86/x86: est.c
src/sys/dev/acpi: TODO acpi_cpu.c acpi_cpu.h acpi_cpu_cstate.c
src/sys/modules/acpicpu: Makefile
Added Files:
src/sys/dev/acpi: acpi_cpu_pstate.c
Log Message:
Merge P-state support for acpicpu(4).
Remarks:
1. All processors (x86 or not) for which the vendor has implemented
ACPI I/O access routines are supported. Native instructions are
currently supported only for Intel's "Enhanced Speedstep". Code for
"PowerNow!" (AMD) will be merged later. Native support for VIA's
"PowerSaver" will be investigated.
2. Backwards compatibility with existing userland code is maintained.
Comparable to the case with cpu_idle(9), the ACPI CPU driver
installs alternative functions for the existing sysctl(8) controls.
The "native" behavior (if any) is restored upon detachment.
3. The dynamic nature of ACPI-provided P-states needs more investigation.
The maximum frequency induced (but not forced) by the firmware may
change dynamically. Currently, the sysctl(8) controls error out with
a value larger than the dynamic maximum. The code itself does not
however yet react to the notifications from the firmware by changing
the frequencies in-place. Presumably the system administrator should
be able to choose whether to use dynamic or static frequencies.
To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 src/sys/arch/x86/acpi/acpi_cpu_md.c
cvs rdiff -u -r1.34 -r1.35 src/sys/arch/x86/include/cpuvar.h
cvs rdiff -u -r1.15 -r1.16 src/sys/arch/x86/x86/est.c
cvs rdiff -u -r1.11 -r1.12 src/sys/dev/acpi/TODO src/sys/dev/acpi/acpi_cpu.c
cvs rdiff -u -r1.8 -r1.9 src/sys/dev/acpi/acpi_cpu.h
cvs rdiff -u -r1.14 -r1.15 src/sys/dev/acpi/acpi_cpu_cstate.c
cvs rdiff -u -r0 -r1.1 src/sys/dev/acpi/acpi_cpu_pstate.c
cvs rdiff -u -r1.1 -r1.2 src/sys/modules/acpicpu/Makefile
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/arch/x86/acpi/acpi_cpu_md.c
diff -u src/sys/arch/x86/acpi/acpi_cpu_md.c:1.4 src/sys/arch/x86/acpi/acpi_cpu_md.c:1.5
--- src/sys/arch/x86/acpi/acpi_cpu_md.c:1.4 Wed Aug 4 16:16:55 2010
+++ src/sys/arch/x86/acpi/acpi_cpu_md.c Sun Aug 8 16:58:41 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi_cpu_md.c,v 1.4 2010/08/04 16:16:55 jruoho Exp $ */
+/* $NetBSD: acpi_cpu_md.c,v 1.5 2010/08/08 16:58:41 jruoho Exp $ */
/*-
* Copyright (c) 2010 Jukka Ruohonen <[email protected]>
@@ -27,25 +27,33 @@
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi_cpu_md.c,v 1.4 2010/08/04 16:16:55 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi_cpu_md.c,v 1.5 2010/08/08 16:58:41 jruoho Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kcore.h>
+#include <sys/sysctl.h>
#include <sys/xcall.h>
#include <x86/cpu.h>
+#include <x86/cpufunc.h>
+#include <x86/cputypes.h>
#include <x86/cpuvar.h>
+#include <x86/cpu_msr.h>
#include <x86/machdep.h>
-#include <x86/cputypes.h>
#include <dev/acpi/acpica.h>
#include <dev/acpi/acpi_cpu.h>
-static char native_idle_text[16];
-void (*native_idle)(void) = NULL;
+static char native_idle_text[16];
+void (*native_idle)(void) = NULL;
-extern uint32_t cpus_running;
+static int acpicpu_md_pstate_sysctl_get(SYSCTLFN_PROTO);
+static int acpicpu_md_pstate_sysctl_set(SYSCTLFN_PROTO);
+static int acpicpu_md_pstate_sysctl_all(SYSCTLFN_PROTO);
+
+extern uint32_t cpus_running;
+extern struct acpicpu_softc **acpicpu_sc;
uint32_t
acpicpu_md_cap(void)
@@ -68,6 +76,12 @@
if ((ci->ci_feat_val[1] & CPUID2_MONITOR) != 0)
val |= ACPICPU_PDC_C_C1_FFH | ACPICPU_PDC_C_C2C3_FFH;
+ /*
+ * Set native P-states if EST is available.
+ */
+ if ((ci->ci_feat_val[1] & CPUID2_EST) != 0)
+ val |= ACPICPU_PDC_P_FFH;
+
return val;
}
@@ -81,13 +95,17 @@
val |= ACPICPU_FLAG_C_BM;
if ((ci->ci_feat_val[1] & CPUID2_MONITOR) != 0)
- val |= ACPICPU_FLAG_C_MWAIT;
+ val |= ACPICPU_FLAG_C_FFH;
switch (cpu_vendor) {
case CPUVENDOR_INTEL:
+
val |= ACPICPU_FLAG_C_BM | ACPICPU_FLAG_C_ARB;
+ if ((ci->ci_feat_val[1] & CPUID2_EST) != 0)
+ val |= ACPICPU_FLAG_P_FFH;
+
/*
* Bus master arbitration is not
* needed on some recent Intel CPUs.
@@ -124,8 +142,6 @@
{
const size_t size = sizeof(native_idle_text);
- KASSERT(native_idle == NULL);
-
x86_disable_intr();
x86_cpu_idle_get(&native_idle, native_idle_text, size);
x86_enable_intr();
@@ -137,9 +153,6 @@
acpicpu_md_idle_start(void)
{
- KASSERT(native_idle != NULL);
- KASSERT(native_idle_text[0] != '\0');
-
x86_disable_intr();
x86_cpu_idle_set(acpicpu_cstate_idle, "acpi");
x86_enable_intr();
@@ -152,9 +165,6 @@
{
uint64_t xc;
- KASSERT(native_idle != NULL);
- KASSERT(native_idle_text[0] != '\0');
-
x86_disable_intr();
x86_cpu_idle_set(native_idle, native_idle_text);
x86_enable_intr();
@@ -201,3 +211,234 @@
break;
}
}
+
+int
+acpicpu_md_pstate_start(void)
+{
+
+ switch (cpu_vendor) {
+
+ case CPUVENDOR_INTEL:
+ est_sysctl_get = acpicpu_md_pstate_sysctl_get;
+ est_sysctl_set = acpicpu_md_pstate_sysctl_set;
+ est_sysctl_all = acpicpu_md_pstate_sysctl_all;
+ break;
+
+ default:
+ return ENODEV;
+ }
+
+ return 0;
+}
+
+int
+acpicpu_md_pstate_stop(void)
+{
+
+ switch (cpu_vendor) {
+
+ case CPUVENDOR_INTEL:
+ est_sysctl_get = NULL;
+ est_sysctl_set = NULL;
+ est_sysctl_all = NULL;
+ break;
+
+ default:
+ return ENODEV;
+ }
+
+ return 0;
+}
+
+static int
+acpicpu_md_pstate_sysctl_get(SYSCTLFN_ARGS)
+{
+ struct cpu_info *ci = curcpu();
+ struct acpicpu_softc *sc;
+ struct sysctlnode node;
+ uint32_t freq;
+ int err;
+
+ /*
+ * We can use any ACPI CPU to manipulate the
+ * frequencies. In MP environments all CPUs
+ * are mandated to support the same number of
+ * P-states and each state must have identical
+ * parameters across CPUs.
+ */
+ sc = acpicpu_sc[ci->ci_acpiid];
+
+ if (sc == NULL)
+ return ENXIO;
+
+ err = acpicpu_pstate_get(sc, &freq);
+
+ if (err != 0)
+ return err;
+
+ node = *rnode;
+ node.sysctl_data = &freq;
+
+ err = sysctl_lookup(SYSCTLFN_CALL(&node));
+
+ if (err != 0 || newp == NULL)
+ return err;
+
+ return 0;
+}
+
+static int
+acpicpu_md_pstate_sysctl_set(SYSCTLFN_ARGS)
+{
+ struct cpu_info *ci = curcpu();
+ struct acpicpu_softc *sc;
+ struct sysctlnode node;
+ uint32_t freq;
+ int err;
+
+ sc = acpicpu_sc[ci->ci_acpiid];
+
+ if (sc == NULL)
+ return ENXIO;
+
+ err = acpicpu_pstate_get(sc, &freq);
+
+ if (err != 0)
+ return err;
+
+ node = *rnode;
+ node.sysctl_data = &freq;
+
+ err = sysctl_lookup(SYSCTLFN_CALL(&node));
+
+ if (err != 0 || newp == NULL)
+ return err;
+
+ err = acpicpu_pstate_set(sc, freq);
+
+ if (err != 0)
+ return err;
+
+ return 0;
+}
+
+static int
+acpicpu_md_pstate_sysctl_all(SYSCTLFN_ARGS)
+{
+ struct cpu_info *ci = curcpu();
+ struct acpicpu_softc *sc;
+ struct sysctlnode node;
+ char buf[1024];
+ size_t len;
+ uint32_t i;
+ int err;
+
+ sc = acpicpu_sc[ci->ci_acpiid];
+
+ if (sc == NULL)
+ return ENXIO;
+
+ (void)memset(&buf, 0, sizeof(buf));
+
+ mutex_enter(&sc->sc_mtx);
+
+ for (len = 0, i = sc->sc_pstate_max; i < sc->sc_pstate_count; i++) {
+
+ if (sc->sc_pstate[i].ps_freq == 0)
+ continue;
+
+ len += snprintf(buf + len, sizeof(buf) - len, "%u%s",
+ sc->sc_pstate[i].ps_freq,
+ i < (sc->sc_pstate_count - 1) ? " " : "");
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ node = *rnode;
+ node.sysctl_data = buf;
+
+ err = sysctl_lookup(SYSCTLFN_CALL(&node));
+
+ if (err != 0 || newp == NULL)
+ return err;
+
+ return 0;
+}
+
+int
+acpicpu_md_pstate_get(struct acpicpu_softc *sc, uint32_t *freq)
+{
+ struct acpicpu_pstate *ps;
+ uint64_t val;
+ uint32_t i;
+
+ switch (cpu_vendor) {
+
+ case CPUVENDOR_INTEL:
+
+ val = rdmsr(MSR_PERF_STATUS);
+ val = val & 0xffff;
+
+ mutex_enter(&sc->sc_mtx);
+
+ for (i = sc->sc_pstate_max; i < sc->sc_pstate_count; i++) {
+
+ ps = &sc->sc_pstate[i];
+
+ if (ps->ps_freq == 0)
+ continue;
+
+ if (val == ps->ps_status) {
+ mutex_exit(&sc->sc_mtx);
+ *freq = ps->ps_freq;
+ return 0;
+ }
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ return EIO;
+
+ default:
+ return ENODEV;
+ }
+
+ return 0;
+}
+
+int
+acpicpu_md_pstate_set(struct acpicpu_pstate *ps)
+{
+ struct msr_rw_info msr;
+ uint64_t xc, val;
+ int i;
+
+ switch (cpu_vendor) {
+
+ case CPUVENDOR_INTEL:
+ msr.msr_read = true;
+ msr.msr_type = MSR_PERF_CTL;
+ msr.msr_value = ps->ps_control;
+ msr.msr_mask = 0xffffULL;
+ break;
+
+ default:
+ return ENODEV;
+ }
+
+ xc = xc_broadcast(0, (xcfunc_t)x86_msr_xcall, &msr, NULL);
+ xc_wait(xc);
+
+ for (i = val = 0; i < ACPICPU_P_STATE_RETRY; i++) {
+
+ val = rdmsr(MSR_PERF_STATUS);
+ val = val & 0xffff;
+
+ if (val == ps->ps_status)
+ return 0;
+
+ DELAY(ps->ps_latency);
+ }
+
+ return EAGAIN;
+}
Index: src/sys/arch/x86/include/cpuvar.h
diff -u src/sys/arch/x86/include/cpuvar.h:1.34 src/sys/arch/x86/include/cpuvar.h:1.35
--- src/sys/arch/x86/include/cpuvar.h:1.34 Wed Aug 4 10:02:11 2010
+++ src/sys/arch/x86/include/cpuvar.h Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: cpuvar.h,v 1.34 2010/08/04 10:02:11 jruoho Exp $ */
+/* $NetBSD: cpuvar.h,v 1.35 2010/08/08 16:58:42 jruoho Exp $ */
/*-
* Copyright (c) 2000, 2007 The NetBSD Foundation, Inc.
@@ -66,6 +66,8 @@
#ifndef _X86_CPUVAR_H_
#define _X86_CPUVAR_H_
+#include <sys/sysctl.h>
+
struct cpu_functions {
int (*start)(struct cpu_info *, paddr_t);
int (*stop)(struct cpu_info *);
@@ -140,6 +142,10 @@
int p4_get_bus_clock(struct cpu_info *);
#endif
+extern int (*est_sysctl_get)(SYSCTLFN_PROTO);
+extern int (*est_sysctl_set)(SYSCTLFN_PROTO);
+extern int (*est_sysctl_all)(SYSCTLFN_PROTO);
+
void cpu_get_tsc_freq(struct cpu_info *);
void pat_init(struct cpu_info *);
Index: src/sys/arch/x86/x86/est.c
diff -u src/sys/arch/x86/x86/est.c:1.15 src/sys/arch/x86/x86/est.c:1.16
--- src/sys/arch/x86/x86/est.c:1.15 Sat Aug 7 22:31:57 2010
+++ src/sys/arch/x86/x86/est.c Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: est.c,v 1.15 2010/08/07 22:31:57 jym Exp $ */
+/* $NetBSD: est.c,v 1.16 2010/08/08 16:58:42 jruoho Exp $ */
/*
* Copyright (c) 2003 Michael Eriksson.
* All rights reserved.
@@ -81,7 +81,7 @@
/* #define EST_DEBUG */
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: est.c,v 1.15 2010/08/07 22:31:57 jym Exp $");
+__KERNEL_RCSID(0, "$NetBSD: est.c,v 1.16 2010/08/08 16:58:42 jruoho Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@@ -998,21 +998,76 @@
static const struct fqlist *est_fqlist; /* not NULL if functional */
static uint16_t *fake_table; /* guessed est_cpu table */
+static char *freq_names;
static struct fqlist fake_fqlist;
static int est_node_target, est_node_current;
static const char est_desc[] = "Enhanced SpeedStep";
static int lvendor, bus_clock;
-static int est_sysctl_helper(SYSCTLFN_PROTO);
static int est_init_once(void);
static void est_init_main(int);
+static int est_sysctl_helper(SYSCTLFN_PROTO);
+static int est_sysctl_helper_get(SYSCTLFN_PROTO);
+static int est_sysctl_helper_set(SYSCTLFN_PROTO);
+static int est_sysctl_helper_all(SYSCTLFN_PROTO);
+
+int (*est_sysctl_get)(SYSCTLFN_PROTO) = NULL;
+int (*est_sysctl_set)(SYSCTLFN_PROTO) = NULL;
+int (*est_sysctl_all)(SYSCTLFN_PROTO) = NULL;
+
+static int
+est_sysctl_helper_get(SYSCTLFN_ARGS)
+{
+
+ if (est_sysctl_get != NULL)
+ return (*est_sysctl_get)(SYSCTLFN_CALL(rnode));
+
+ return est_sysctl_helper(SYSCTLFN_CALL(rnode));
+}
+
+static int
+est_sysctl_helper_set(SYSCTLFN_ARGS)
+{
+
+ if (est_sysctl_set != NULL)
+ return (*est_sysctl_set)(SYSCTLFN_CALL(rnode));
+
+ return est_sysctl_helper(SYSCTLFN_CALL(rnode));
+}
+
+static int
+est_sysctl_helper_all(SYSCTLFN_ARGS)
+{
+ struct sysctlnode node;
+ int err;
+
+ if (est_sysctl_all != NULL)
+ return (*est_sysctl_all)(SYSCTLFN_CALL(rnode));
+
+ if (freq_names == NULL)
+ return ENXIO;
+
+ node = *rnode;
+ node.sysctl_data = freq_names;
+
+ err = sysctl_lookup(SYSCTLFN_CALL(&node));
+
+ if (err != 0 || newp == NULL)
+ return err;
+
+ return 0;
+}
+
static int
est_sysctl_helper(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int fq, oldfq, error;
+ if (freq_names == NULL)
+ return ENXIO;
+
if (est_fqlist == NULL)
return EOPNOTSUPP;
@@ -1086,7 +1141,6 @@
uint8_t crhi, crlo, crcur;
int i, mv, rc;
size_t len, freq_len;
- char *freq_names;
if (CPUID2FAMILY(curcpu()->ci_signature) == 15)
bus_clock = p4_get_bus_clock(curcpu());
@@ -1278,24 +1332,28 @@
if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
EST_TARGET_CTLFLAG, CTLTYPE_INT, "target", NULL,
- est_sysctl_helper, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ est_sysctl_helper_set, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
goto err;
+
est_node_target = node->sysctl_num;
if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
0, CTLTYPE_INT, "current", NULL,
- est_sysctl_helper, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ est_sysctl_helper_get, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
goto err;
+
est_node_current = node->sysctl_num;
if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
- 0, CTLTYPE_STRING, "available", NULL,
- NULL, 0, freq_names, freq_len, CTL_CREATE, CTL_EOL)) != 0)
+ CTLFLAG_READONLY, CTLTYPE_STRING, "available", NULL,
+ est_sysctl_helper_all, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
goto err;
return;
err:
free(freq_names, M_SYSCTLDATA);
+ freq_names = NULL;
+
aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
}
Index: src/sys/dev/acpi/TODO
diff -u src/sys/dev/acpi/TODO:1.11 src/sys/dev/acpi/TODO:1.12
--- src/sys/dev/acpi/TODO:1.11 Wed Dec 21 08:48:25 2005
+++ src/sys/dev/acpi/TODO Sun Aug 8 16:58:42 2010
@@ -5,8 +5,4 @@
an ISA device. http://mail-index.netbsd.org/tech-kern/2005/11/11/0011.html
has a more detailed analysis.
-* Import the ACPI Processor driver from FreeBSD or write our own.
- This is useful for CPUs that supports _Px and _Cx states and helps
- longer battery life.
-
- sekiya, 21 December 2005
Index: src/sys/dev/acpi/acpi_cpu.c
diff -u src/sys/dev/acpi/acpi_cpu.c:1.11 src/sys/dev/acpi/acpi_cpu.c:1.12
--- src/sys/dev/acpi/acpi_cpu.c:1.11 Wed Aug 4 10:02:12 2010
+++ src/sys/dev/acpi/acpi_cpu.c Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi_cpu.c,v 1.11 2010/08/04 10:02:12 jruoho Exp $ */
+/* $NetBSD: acpi_cpu.c,v 1.12 2010/08/08 16:58:42 jruoho Exp $ */
/*-
* Copyright (c) 2010 Jukka Ruohonen <[email protected]>
@@ -27,7 +27,7 @@
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi_cpu.c,v 1.11 2010/08/04 10:02:12 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi_cpu.c,v 1.12 2010/08/08 16:58:42 jruoho Exp $");
#include <sys/param.h>
#include <sys/cpu.h>
@@ -138,10 +138,7 @@
mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_NONE);
aprint_naive("\n");
- aprint_normal(": ACPI CPU");
- aprint_verbose(", cap 0x%02x, addr 0x%06x, len 0x%02x",
- sc->sc_cap, sc->sc_object.ao_pblkaddr, sc->sc_object.ao_pblklen);
- aprint_normal("\n");
+ aprint_normal(": ACPI CPU\n");
/*
* We should claim the bus space. However, we do this only
@@ -158,10 +155,16 @@
}
acpicpu_cstate_attach(self);
+ acpicpu_pstate_attach(self);
- (void)acpi_register_notify(sc->sc_node, acpicpu_notify);
(void)config_finalize_register(self, acpicpu_cstate_start);
+ (void)config_finalize_register(self, acpicpu_pstate_start);
+
+ (void)acpi_register_notify(sc->sc_node, acpicpu_notify);
(void)pmf_device_register(self, acpicpu_suspend, acpicpu_resume);
+
+ aprint_debug_dev(sc->sc_dev, "cap 0x%02x, "
+ "flags 0x%06x\n", sc->sc_cap, sc->sc_flags);
}
static int
@@ -180,6 +183,12 @@
if (rv != 0)
return rv;
+ if ((sc->sc_flags & ACPICPU_FLAG_P) != 0)
+ rv = acpicpu_pstate_detach(self);
+
+ if (rv != 0)
+ return rv;
+
rv = RUN_ONCE(&once_detach, acpicpu_once_detach);
if (rv != 0)
@@ -450,7 +459,7 @@
if ((sc->sc_flags & ACPICPU_FLAG_P) == 0)
return;
- func = NULL;
+ func = acpicpu_pstate_callback;
break;
case ACPICPU_T_NOTIFY:
@@ -479,6 +488,9 @@
if ((sc->sc_flags & ACPICPU_FLAG_C) != 0)
(void)acpicpu_cstate_suspend(self);
+ if ((sc->sc_flags & ACPICPU_FLAG_P) != 0)
+ (void)acpicpu_pstate_suspend(self);
+
return true;
}
@@ -492,6 +504,9 @@
if ((sc->sc_flags & ACPICPU_FLAG_C) != 0)
(void)acpicpu_cstate_resume(self);
+ if ((sc->sc_flags & ACPICPU_FLAG_P) != 0)
+ (void)acpicpu_pstate_resume(self);
+
return true;
}
Index: src/sys/dev/acpi/acpi_cpu.h
diff -u src/sys/dev/acpi/acpi_cpu.h:1.8 src/sys/dev/acpi/acpi_cpu.h:1.9
--- src/sys/dev/acpi/acpi_cpu.h:1.8 Fri Jul 30 06:11:14 2010
+++ src/sys/dev/acpi/acpi_cpu.h Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi_cpu.h,v 1.8 2010/07/30 06:11:14 jruoho Exp $ */
+/* $NetBSD: acpi_cpu.h,v 1.9 2010/08/08 16:58:42 jruoho Exp $ */
/*-
* Copyright (c) 2010 Jukka Ruohonen <[email protected]>
@@ -33,8 +33,17 @@
/*
* The following _PDC values are based on:
*
- * Intel Processor-Specific ACPI Interface
- * Specification, September 2006, Revision 005.
+ * Intel Corporation: Intel Processor-Specific ACPI
+ * Interface Specification, September 2006, Revision 005.
+ *
+ * http://download.intel.com/technology/IAPC/acpi/downloads/30222305.pdf
+ *
+ * For other relevant reading, see for instance:
+ *
+ * Advanced Micro Devices: Using ACPI to Report APML P-State
+ * Limit Changes to Operating Systems and VMM's. August 7, 2009.
+ *
+ * http://developer.amd.com/Assets/ACPI-APML-PState-rev12.pdf
*/
#define ACPICPU_PDC_REVID 0x1
#define ACPICPU_PDC_SMP 0xA
@@ -73,11 +82,10 @@
#define ACPICPU_C_STATE_SYSIO 0x03
/*
- * Cross-CPU dependency coordination.
+ * P-states.
*/
-#define ACPICPU_DEP_SW_ALL 0xFC
-#define ACPICPU_DEP_SW_ANY 0xFD
-#define ACPICPU_DEP_HW_ALL 0xFE
+#define ACPICPU_P_STATE_RETRY 100
+#define ACPICPU_P_STATE_UNKNOWN 0x0
/*
* Flags.
@@ -92,9 +100,12 @@
#define ACPICPU_FLAG_C_BM_STS __BIT(6) /* Bus master check required */
#define ACPICPU_FLAG_C_ARB __BIT(7) /* Bus master arbitration */
#define ACPICPU_FLAG_C_NOC3 __BIT(8) /* C3 disabled (quirk) */
-#define ACPICPU_FLAG_C_MWAIT __BIT(9) /* MONITOR/MWAIT supported */
+#define ACPICPU_FLAG_C_FFH __BIT(9) /* MONITOR/MWAIT supported */
#define ACPICPU_FLAG_C_C1E __BIT(10) /* AMD C1E detected */
+#define ACPICPU_FLAG_P_PPC __BIT(11) /* Dynamic freq. with _PPC */
+#define ACPICPU_FLAG_P_FFH __BIT(12) /* EST etc. supported */
+
/*
* This is AML_RESOURCE_GENERIC_REGISTER,
* included here separately for convenience.
@@ -118,11 +129,14 @@
int cs_flags;
};
-struct acpicpu_dep {
- uint32_t dep_domain;
- uint32_t dep_coord;
- uint32_t dep_ncpu;
- uint32_t dep_index;
+struct acpicpu_pstate {
+ uint64_t ps_stat;
+ uint32_t ps_freq; /* MHz */
+ uint32_t ps_power; /* mW */
+ uint32_t ps_latency; /* us */
+ uint32_t ps_latency_bm; /* us */
+ uint32_t ps_control;
+ uint32_t ps_status;
};
struct acpicpu_object {
@@ -139,6 +153,13 @@
struct acpicpu_cstate sc_cstate[ACPI_C_STATE_COUNT];
uint32_t sc_cstate_sleep;
+ struct acpicpu_pstate *sc_pstate;
+ struct acpicpu_reg sc_pstate_control;
+ struct acpicpu_reg sc_pstate_status;
+ uint32_t sc_pstate_current;
+ uint32_t sc_pstate_count;
+ uint32_t sc_pstate_max;
+
kmutex_t sc_mtx;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
@@ -157,6 +178,15 @@
void acpicpu_cstate_callback(void *);
void acpicpu_cstate_idle(void);
+void acpicpu_pstate_attach(device_t);
+int acpicpu_pstate_detach(device_t);
+int acpicpu_pstate_start(device_t);
+bool acpicpu_pstate_suspend(device_t);
+bool acpicpu_pstate_resume(device_t);
+void acpicpu_pstate_callback(void *);
+int acpicpu_pstate_get(struct acpicpu_softc *, uint32_t *);
+int acpicpu_pstate_set(struct acpicpu_softc *, uint32_t);
+
uint32_t acpicpu_md_cap(void);
uint32_t acpicpu_md_quirks(void);
uint32_t acpicpu_md_cpus_running(void);
@@ -164,5 +194,9 @@
int acpicpu_md_idle_start(void);
int acpicpu_md_idle_stop(void);
void acpicpu_md_idle_enter(int, int);
+int acpicpu_md_pstate_start(void);
+int acpicpu_md_pstate_stop(void);
+int acpicpu_md_pstate_get(struct acpicpu_softc *, uint32_t *);
+int acpicpu_md_pstate_set(struct acpicpu_pstate *);
#endif /* !_SYS_DEV_ACPI_ACPI_CPU_H */
Index: src/sys/dev/acpi/acpi_cpu_cstate.c
diff -u src/sys/dev/acpi/acpi_cpu_cstate.c:1.14 src/sys/dev/acpi/acpi_cpu_cstate.c:1.15
--- src/sys/dev/acpi/acpi_cpu_cstate.c:1.14 Wed Aug 4 10:02:12 2010
+++ src/sys/dev/acpi/acpi_cpu_cstate.c Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: acpi_cpu_cstate.c,v 1.14 2010/08/04 10:02:12 jruoho Exp $ */
+/* $NetBSD: acpi_cpu_cstate.c,v 1.15 2010/08/08 16:58:42 jruoho Exp $ */
/*-
* Copyright (c) 2010 Jukka Ruohonen <[email protected]>
@@ -27,7 +27,7 @@
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi_cpu_cstate.c,v 1.14 2010/08/04 10:02:12 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi_cpu_cstate.c,v 1.15 2010/08/08 16:58:42 jruoho Exp $");
#include <sys/param.h>
#include <sys/cpu.h>
@@ -55,7 +55,6 @@
static ACPI_STATUS acpicpu_cstate_cst_add(struct acpicpu_softc *,
ACPI_OBJECT *);
static void acpicpu_cstate_cst_bios(void);
-static ACPI_STATUS acpicpu_cstate_csd(ACPI_HANDLE, struct acpicpu_dep *);
static void acpicpu_cstate_fadt(struct acpicpu_softc *);
static void acpicpu_cstate_quirks(struct acpicpu_softc *);
static int acpicpu_cstate_quirks_piix4(struct pci_attach_args *);
@@ -107,39 +106,9 @@
acpicpu_cstate_attach_print(struct acpicpu_softc *sc)
{
struct acpicpu_cstate *cs;
- struct acpicpu_dep dep;
- const char *method;
- ACPI_STATUS rv;
+ const char *str;
int i;
- (void)memset(&dep, 0, sizeof(struct acpicpu_dep));
-
- rv = acpicpu_cstate_csd(sc->sc_node->ad_handle, &dep);
-
- if (ACPI_SUCCESS(rv)) {
- aprint_debug_dev(sc->sc_dev, "C%u: _CSD, "
- "domain 0x%02x / 0x%02x, type 0x%02x\n",
- dep.dep_index, dep.dep_domain,
- dep.dep_ncpu, dep.dep_coord);
- }
-
- aprint_debug_dev(sc->sc_dev, "Cx: %5s",
- (sc->sc_flags & ACPICPU_FLAG_C_FADT) != 0 ? "FADT" : "_CST");
-
- if ((sc->sc_flags & ACPICPU_FLAG_C_BM) != 0)
- aprint_debug(", BM control");
-
- if ((sc->sc_flags & ACPICPU_FLAG_C_ARB) != 0)
- aprint_debug(", BM arbitration");
-
- if ((sc->sc_flags & ACPICPU_FLAG_C_C1E) != 0)
- aprint_debug(", C1E");
-
- if ((sc->sc_flags & ACPICPU_FLAG_C_NOC3) != 0)
- aprint_debug(", C3 disabled (quirk)");
-
- aprint_debug("\n");
-
for (i = 0; i < ACPI_C_STATE_COUNT; i++) {
cs = &sc->sc_cstate[i];
@@ -150,24 +119,24 @@
switch (cs->cs_method) {
case ACPICPU_C_STATE_HALT:
- method = "HALT";
+ str = "HALT";
break;
case ACPICPU_C_STATE_FFH:
- method = "FFH";
+ str = "FFH";
break;
case ACPICPU_C_STATE_SYSIO:
- method = "SYSIO";
+ str = "SYSIO";
break;
default:
panic("NOTREACHED");
}
- aprint_debug_dev(sc->sc_dev, "C%d: %5s, "
- "latency %4u, power %4u, addr 0x%06x, flags 0x%02x\n",
- i, method, cs->cs_latency, cs->cs_power,
+ aprint_verbose_dev(sc->sc_dev, "C%d: %5s, "
+ "lat %3u us, pow %5u mW, addr 0x%06x, flags 0x%02x\n",
+ i, str, cs->cs_latency, cs->cs_power,
(uint32_t)cs->cs_addr, cs->cs_flags);
}
}
@@ -227,8 +196,6 @@
static const ACPI_OSD_EXEC_CALLBACK func = acpicpu_cstate_callback;
struct acpicpu_softc *sc = device_private(self);
- KASSERT((sc->sc_flags & ACPICPU_FLAG_C) != 0);
-
if ((sc->sc_flags & ACPICPU_FLAG_C_CST) != 0)
(void)AcpiOsExecute(OSL_NOTIFY_HANDLER, func, sc->sc_dev);
@@ -243,8 +210,6 @@
sc = device_private(self);
- KASSERT((sc->sc_flags & ACPICPU_FLAG_C) != 0);
-
if ((sc->sc_flags & ACPICPU_FLAG_C_FADT) != 0) {
KASSERT((sc->sc_flags & ACPICPU_FLAG_C_CST) == 0);
return;
@@ -450,14 +415,14 @@
case ACPI_STATE_C1:
- if ((sc->sc_flags & ACPICPU_FLAG_C_MWAIT) == 0)
+ if ((sc->sc_flags & ACPICPU_FLAG_C_FFH) == 0)
state.cs_method = ACPICPU_C_STATE_HALT;
break;
default:
- if ((sc->sc_flags & ACPICPU_FLAG_C_MWAIT) == 0) {
+ if ((sc->sc_flags & ACPICPU_FLAG_C_FFH) == 0) {
rv = AE_AML_BAD_RESOURCE_VALUE;
goto out;
}
@@ -513,65 +478,6 @@
(void)AcpiOsWritePort(addr, val, 8);
}
-static ACPI_STATUS
-acpicpu_cstate_csd(ACPI_HANDLE hdl, struct acpicpu_dep *dep)
-{
- ACPI_OBJECT *elm, *obj;
- ACPI_BUFFER buf;
- ACPI_STATUS rv;
- int i, n;
-
- /*
- * Query the optional _CSD for heuristics.
- */
- rv = acpi_eval_struct(hdl, "_CSD", &buf);
-
- if (ACPI_FAILURE(rv))
- return rv;
-
- obj = buf.Pointer;
-
- if (obj->Type != ACPI_TYPE_PACKAGE) {
- rv = AE_TYPE;
- goto out;
- }
-
- n = obj->Package.Count;
-
- if (n != 6) {
- rv = AE_LIMIT;
- goto out;
- }
-
- elm = obj->Package.Elements;
-
- for (i = 0; i < n; i++) {
-
- if (elm[i].Type != ACPI_TYPE_INTEGER) {
- rv = AE_TYPE;
- goto out;
- }
-
- KDASSERT((uint64_t)elm[i].Integer.Value <= UINT32_MAX);
- }
-
- if (elm[0].Integer.Value != 6 || elm[1].Integer.Value != 0) {
- rv = AE_BAD_DATA;
- goto out;
- }
-
- dep->dep_domain = elm[2].Integer.Value;
- dep->dep_coord = elm[3].Integer.Value;
- dep->dep_ncpu = elm[4].Integer.Value;
- dep->dep_index = elm[5].Integer.Value;
-
-out:
- if (buf.Pointer != NULL)
- ACPI_FREE(buf.Pointer);
-
- return rv;
-}
-
static void
acpicpu_cstate_fadt(struct acpicpu_softc *sc)
{
Index: src/sys/modules/acpicpu/Makefile
diff -u src/sys/modules/acpicpu/Makefile:1.1 src/sys/modules/acpicpu/Makefile:1.2
--- src/sys/modules/acpicpu/Makefile:1.1 Sun Jul 18 09:37:50 2010
+++ src/sys/modules/acpicpu/Makefile Sun Aug 8 16:58:42 2010
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.1 2010/07/18 09:37:50 jruoho Exp $
+# $NetBSD: Makefile,v 1.2 2010/08/08 16:58:42 jruoho Exp $
.include "../Makefile.inc"
@@ -6,7 +6,7 @@
.PATH: ${S}/arch/x86/acpi
KMOD= acpicpu
-SRCS= acpi_cpu.c acpi_cpu_cstate.c acpi_cpu_md.c
+SRCS= acpi_cpu.c acpi_cpu_cstate.c acpi_cpu_pstate.c acpi_cpu_md.c
WARNS= 4
Added files:
Index: src/sys/dev/acpi/acpi_cpu_pstate.c
diff -u /dev/null src/sys/dev/acpi/acpi_cpu_pstate.c:1.1
--- /dev/null Sun Aug 8 16:58:42 2010
+++ src/sys/dev/acpi/acpi_cpu_pstate.c Sun Aug 8 16:58:42 2010
@@ -0,0 +1,659 @@
+/* $NetBSD: acpi_cpu_pstate.c,v 1.1 2010/08/08 16:58:42 jruoho Exp $ */
+
+/*-
+ * Copyright (c) 2010 Jukka Ruohonen <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: acpi_cpu_pstate.c,v 1.1 2010/08/08 16:58:42 jruoho Exp $");
+
+#include <sys/param.h>
+#include <sys/kmem.h>
+#include <sys/once.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_cpu.h>
+
+#define _COMPONENT ACPI_BUS_COMPONENT
+ACPI_MODULE_NAME ("acpi_cpu_pstate")
+
+static void acpicpu_pstate_attach_print(struct acpicpu_softc *);
+static ACPI_STATUS acpicpu_pstate_pss(struct acpicpu_softc *sc);
+static ACPI_STATUS acpicpu_pstate_pss_add(struct acpicpu_pstate *,
+ ACPI_OBJECT *);
+static ACPI_STATUS acpicpu_pstate_pct(struct acpicpu_softc *);
+static int acpicpu_pstate_max(struct acpicpu_softc *);
+static void acpicpu_pstate_change(struct acpicpu_softc *);
+static void acpicpu_pstate_bios(void);
+
+void
+acpicpu_pstate_attach(device_t self)
+{
+ struct acpicpu_softc *sc = device_private(self);
+ ACPI_STATUS rv;
+
+ /*
+ * XXX: Only Intel is currently supported.
+ */
+ if (sc->sc_cap == 0)
+ return;
+
+ rv = acpicpu_pstate_pss(sc);
+
+ if (ACPI_FAILURE(rv))
+ return;
+
+ rv = acpicpu_pstate_pct(sc);
+
+ if (ACPI_FAILURE(rv))
+ return;
+
+ rv = acpicpu_pstate_max(sc);
+
+ if (rv == 0)
+ sc->sc_flags |= ACPICPU_FLAG_P_PPC;
+
+ sc->sc_flags |= ACPICPU_FLAG_P;
+
+ acpicpu_pstate_bios();
+ acpicpu_pstate_attach_print(sc);
+}
+
+static void
+acpicpu_pstate_attach_print(struct acpicpu_softc *sc)
+{
+ const uint8_t method = sc->sc_pstate_control.reg_spaceid;
+ struct acpicpu_pstate *ps;
+ const char *str;
+ uint32_t i;
+
+ str = (method != ACPI_ADR_SPACE_SYSTEM_IO) ? "FFH" : "SYSIO";
+
+ for (i = 0; i < sc->sc_pstate_count; i++) {
+
+ ps = &sc->sc_pstate[i];
+
+ if (ps->ps_freq == 0)
+ continue;
+
+ aprint_verbose_dev(sc->sc_dev, "P%d: %5s, "
+ "lat %3u us, pow %5u mW, %4u MHz\n",
+ i, str, ps->ps_latency, ps->ps_power, ps->ps_freq);
+ }
+}
+
+int
+acpicpu_pstate_detach(device_t self)
+{
+ struct acpicpu_softc *sc = device_private(self);
+ static ONCE_DECL(once_detach);
+ size_t size;
+ int rv;
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P) == 0)
+ return 0;
+
+ rv = RUN_ONCE(&once_detach, acpicpu_md_pstate_stop);
+
+ if (rv != 0)
+ return rv;
+
+ size = sc->sc_pstate_count * sizeof(*sc->sc_pstate);
+
+ if (sc->sc_pstate != NULL)
+ kmem_free(sc->sc_pstate, size);
+
+ sc->sc_flags &= ~ACPICPU_FLAG_P;
+
+ return 0;
+}
+
+int
+acpicpu_pstate_start(device_t self)
+{
+ struct acpicpu_softc *sc = device_private(self);
+ static ONCE_DECL(once_start);
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P) == 0)
+ return 0;
+
+ return RUN_ONCE(&once_start, acpicpu_md_pstate_start);
+}
+
+bool
+acpicpu_pstate_suspend(device_t self)
+{
+
+ return true;
+}
+
+bool
+acpicpu_pstate_resume(device_t self)
+{
+ static const ACPI_OSD_EXEC_CALLBACK func = acpicpu_pstate_callback;
+ struct acpicpu_softc *sc = device_private(self);
+
+ KASSERT((sc->sc_flags & ACPICPU_FLAG_P) != 0);
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P_PPC) != 0)
+ (void)AcpiOsExecute(OSL_NOTIFY_HANDLER, func, sc->sc_dev);
+
+ return true;
+}
+
+void
+acpicpu_pstate_callback(void *aux)
+{
+ struct acpicpu_softc *sc;
+ device_t self = aux;
+ uint32_t old, new;
+
+ sc = device_private(self);
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P_PPC) == 0)
+ return;
+
+ mutex_enter(&sc->sc_mtx);
+
+ old = sc->sc_pstate_max;
+ acpicpu_pstate_change(sc);
+ new = sc->sc_pstate_max;
+
+ mutex_exit(&sc->sc_mtx);
+
+#if 0
+ if (old != new) {
+
+ /*
+ * If the maximum changed, proactively
+ * raise or lower the target frequency.
+ */
+ acpicpu_pstate_set(sc, sc->sc_pstate[new].ps_freq);
+
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO, "frequency changed from "
+ "%u MHz to %u MHz\n", sc->sc_pstate[old].ps_freq,
+ sc->sc_pstate[sc->sc_pstate_max].ps_freq));
+ }
+#endif
+}
+
+ACPI_STATUS
+acpicpu_pstate_pss(struct acpicpu_softc *sc)
+{
+ struct acpicpu_pstate *ps;
+ ACPI_OBJECT *obj;
+ ACPI_BUFFER buf;
+ ACPI_STATUS rv;
+ uint32_t count;
+ uint32_t i, j;
+
+ rv = acpi_eval_struct(sc->sc_node->ad_handle, "_PSS", &buf);
+
+ if (ACPI_FAILURE(rv))
+ return rv;
+
+ obj = buf.Pointer;
+
+ if (obj->Type != ACPI_TYPE_PACKAGE) {
+ rv = AE_TYPE;
+ goto out;
+ }
+
+ sc->sc_pstate_count = obj->Package.Count;
+
+ if (sc->sc_pstate_count == 0) {
+ rv = AE_NOT_EXIST;
+ goto out;
+ }
+
+ if (sc->sc_pstate_count > 0xFF) {
+ rv = AE_LIMIT;
+ goto out;
+ }
+
+ sc->sc_pstate = kmem_zalloc(sc->sc_pstate_count *
+ sizeof(struct acpicpu_pstate), KM_SLEEP);
+
+ if (sc->sc_pstate == NULL) {
+ rv = AE_NO_MEMORY;
+ goto out;
+ }
+
+ for (count = i = 0; i < sc->sc_pstate_count; i++) {
+
+ ps = &sc->sc_pstate[i];
+ rv = acpicpu_pstate_pss_add(ps, &obj->Package.Elements[i]);
+
+ if (ACPI_FAILURE(rv))
+ continue;
+
+ for (j = 0; j < i; j++) {
+
+ if (ps->ps_freq >= sc->sc_pstate[j].ps_freq) {
+ ps->ps_freq = 0;
+ break;
+ }
+ }
+
+ if (ps->ps_freq != 0)
+ count++;
+ }
+
+ rv = (count != 0) ? AE_OK : AE_NOT_EXIST;
+
+out:
+ if (buf.Pointer != NULL)
+ ACPI_FREE(buf.Pointer);
+
+ if (ACPI_FAILURE(rv))
+ aprint_error_dev(sc->sc_dev, "failed to evaluate "
+ "_PSS: %s\n", AcpiFormatException(rv));
+
+ return rv;
+}
+
+static ACPI_STATUS
+acpicpu_pstate_pss_add(struct acpicpu_pstate *ps, ACPI_OBJECT *obj)
+{
+ ACPI_OBJECT *elm;
+ uint32_t val[6];
+ uint32_t *p;
+ int i;
+
+ if (obj->Type != ACPI_TYPE_PACKAGE)
+ return AE_TYPE;
+
+ if (obj->Package.Count != 6)
+ return AE_BAD_DATA;
+
+ elm = obj->Package.Elements;
+
+ for (i = 0; i < 6; i++) {
+
+ if (elm[i].Type != ACPI_TYPE_INTEGER)
+ return AE_TYPE;
+
+ if (elm[i].Integer.Value > UINT32_MAX)
+ return AE_AML_NUMERIC_OVERFLOW;
+
+ val[i] = elm[i].Integer.Value;
+ }
+
+ if (val[0] == 0 || val[0] >= 0xFFFF)
+ return AE_BAD_DECIMAL_CONSTANT;
+
+ CTASSERT(sizeof(val) == sizeof(struct acpicpu_pstate) -
+ offsetof(struct acpicpu_pstate, ps_freq));
+
+ p = &ps->ps_freq;
+
+ for (i = 0; i < 6; i++, p++)
+ *p = val[i];
+
+ /*
+ * The latency is typically around 10 usec
+ * on Intel CPUs. Use that as the minimum.
+ */
+ if (ps->ps_latency < 10)
+ ps->ps_latency = 10;
+
+ return AE_OK;
+}
+
+ACPI_STATUS
+acpicpu_pstate_pct(struct acpicpu_softc *sc)
+{
+ static const size_t size = sizeof(struct acpicpu_reg);
+ struct acpicpu_reg *reg[2];
+ ACPI_OBJECT *elm, *obj;
+ ACPI_BUFFER buf;
+ ACPI_STATUS rv;
+ uint8_t width;
+ int i;
+
+ rv = acpi_eval_struct(sc->sc_node->ad_handle, "_PCT", &buf);
+
+ if (ACPI_FAILURE(rv))
+ return rv;
+
+ obj = buf.Pointer;
+
+ if (obj->Type != ACPI_TYPE_PACKAGE) {
+ rv = AE_TYPE;
+ goto out;
+ }
+
+ if (obj->Package.Count != 2) {
+ rv = AE_LIMIT;
+ goto out;
+ }
+
+ for (i = 0; i < 2; i++) {
+
+ elm = &obj->Package.Elements[i];
+
+ if (elm->Type != ACPI_TYPE_BUFFER) {
+ rv = AE_TYPE;
+ goto out;
+ }
+
+ if (size > elm->Buffer.Length) {
+ rv = AE_AML_BAD_RESOURCE_LENGTH;
+ goto out;
+ }
+
+ reg[i] = (struct acpicpu_reg *)elm->Buffer.Pointer;
+
+ switch (reg[i]->reg_spaceid) {
+
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+
+ if (reg[i]->reg_addr == 0) {
+ rv = AE_AML_ILLEGAL_ADDRESS;
+ goto out;
+ }
+
+ width = reg[i]->reg_bitwidth;
+
+ if (width != 8 && width != 16 && width != 32) {
+ rv = AE_AML_BAD_RESOURCE_VALUE;
+ goto out;
+ }
+
+ break;
+
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P_FFH) == 0) {
+ rv = AE_AML_BAD_RESOURCE_VALUE;
+ goto out;
+ }
+
+ break;
+
+ default:
+ rv = AE_AML_INVALID_SPACE_ID;
+ goto out;
+ }
+ }
+
+ if (reg[0]->reg_spaceid != reg[1]->reg_spaceid) {
+ rv = AE_AML_INVALID_SPACE_ID;
+ goto out;
+ }
+
+ (void)memcpy(&sc->sc_pstate_control, reg[0], size); /* PERF_CTRL */
+ (void)memcpy(&sc->sc_pstate_status, reg[1], size); /* PERF_STATUS */
+
+out:
+ if (buf.Pointer != NULL)
+ ACPI_FREE(buf.Pointer);
+
+ if (ACPI_FAILURE(rv))
+ aprint_error_dev(sc->sc_dev, "failed to evaluate "
+ "_PCT: %s\n", AcpiFormatException(rv));
+
+ return rv;
+}
+
+static int
+acpicpu_pstate_max(struct acpicpu_softc *sc)
+{
+ ACPI_INTEGER val;
+ ACPI_STATUS rv;
+
+ /*
+ * Evaluate the currently highest P-state that can be used.
+ * If available, we can use either this state or any lower
+ * power (i.e. higher numbered) state from the _PSS object.
+ */
+ rv = acpi_eval_integer(sc->sc_node->ad_handle, "_PPC", &val);
+
+ sc->sc_pstate_max = 0;
+
+ if (ACPI_FAILURE(rv))
+ return 1;
+
+ if (val > (uint64_t)sc->sc_pstate_count)
+ return 1;
+
+ if (sc->sc_pstate[val].ps_freq == 0)
+ return 1;
+
+ sc->sc_pstate_max = val; /* XXX: sysctl(8) knob? */
+
+ return 0;
+}
+
+static void
+acpicpu_pstate_change(struct acpicpu_softc *sc)
+{
+ ACPI_OBJECT_LIST arg;
+ ACPI_OBJECT obj[2];
+
+ arg.Count = 2;
+ arg.Pointer = obj;
+
+ obj[0].Type = ACPI_TYPE_INTEGER;
+ obj[1].Type = ACPI_TYPE_INTEGER;
+
+ obj[0].Integer.Value = ACPICPU_P_NOTIFY;
+ obj[1].Integer.Value = acpicpu_pstate_max(sc);
+
+ (void)AcpiEvaluateObject(sc->sc_node->ad_handle, "_OST", &arg, NULL);
+}
+
+static void
+acpicpu_pstate_bios(void)
+{
+ const uint8_t val = AcpiGbl_FADT.PstateControl;
+ const uint32_t addr = AcpiGbl_FADT.SmiCommand;
+
+ if (addr == 0)
+ return;
+
+ (void)AcpiOsWritePort(addr, val, 8);
+}
+
+int
+acpicpu_pstate_get(struct acpicpu_softc *sc, uint32_t *freq)
+{
+ const uint8_t method = sc->sc_pstate_control.reg_spaceid;
+ struct acpicpu_pstate *ps = NULL;
+ uint32_t i, val = 0;
+ uint64_t addr;
+ uint8_t width;
+ int rv;
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P) == 0) {
+ rv = ENODEV;
+ goto fail;
+ }
+
+ if (sc->sc_pstate_current != ACPICPU_P_STATE_UNKNOWN) {
+ *freq = sc->sc_pstate_current;
+ return 0;
+ }
+
+ switch (method) {
+
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+
+ rv = acpicpu_md_pstate_get(sc, freq);
+
+ if (rv != 0)
+ goto fail;
+
+ break;
+
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+
+ addr = sc->sc_pstate_status.reg_addr;
+ width = sc->sc_pstate_status.reg_bitwidth;
+
+ (void)AcpiOsReadPort(addr, &val, width);
+
+ if (val == 0) {
+ rv = EIO;
+ goto fail;
+ }
+
+ mutex_enter(&sc->sc_mtx);
+
+ for (i = sc->sc_pstate_max; i < sc->sc_pstate_count; i++) {
+
+ if (sc->sc_pstate[i].ps_freq == 0)
+ continue;
+
+ if (val == sc->sc_pstate[i].ps_status) {
+ ps = &sc->sc_pstate[i];
+ break;
+ }
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ if (ps == NULL) {
+ rv = EIO;
+ goto fail;
+ }
+
+ *freq = ps->ps_freq;
+ break;
+
+ default:
+ rv = ENOTTY;
+ goto fail;
+ }
+
+ sc->sc_pstate_current = *freq;
+
+ return 0;
+
+fail:
+ aprint_error_dev(sc->sc_dev, "failed "
+ "to get frequency (err %d)\n", rv);
+
+ *freq = sc->sc_pstate_current = ACPICPU_P_STATE_UNKNOWN;
+
+ return rv;
+}
+
+int
+acpicpu_pstate_set(struct acpicpu_softc *sc, uint32_t freq)
+{
+ const uint8_t method = sc->sc_pstate_control.reg_spaceid;
+ struct acpicpu_pstate *ps = NULL;
+ uint32_t i, val;
+ uint64_t addr;
+ uint8_t width;
+ int rv;
+
+ if ((sc->sc_flags & ACPICPU_FLAG_P) == 0) {
+ rv = ENODEV;
+ goto fail;
+ }
+
+ mutex_enter(&sc->sc_mtx);
+
+ for (i = sc->sc_pstate_max; i < sc->sc_pstate_count; i++) {
+
+ if (sc->sc_pstate[i].ps_freq == 0)
+ continue;
+
+ if (sc->sc_pstate[i].ps_freq == freq) {
+ ps = &sc->sc_pstate[i];
+ break;
+ }
+ }
+
+ mutex_exit(&sc->sc_mtx);
+
+ if (ps == NULL) {
+ rv = EINVAL;
+ goto fail;
+ }
+
+ switch (method) {
+
+ case ACPI_ADR_SPACE_FIXED_HARDWARE:
+
+ rv = acpicpu_md_pstate_set(ps);
+
+ if (rv != 0)
+ goto fail;
+
+ break;
+
+ case ACPI_ADR_SPACE_SYSTEM_IO:
+
+ addr = sc->sc_pstate_control.reg_addr;
+ width = sc->sc_pstate_control.reg_bitwidth;
+
+ (void)AcpiOsWritePort(addr, ps->ps_control, width);
+
+ addr = sc->sc_pstate_status.reg_addr;
+ width = sc->sc_pstate_status.reg_bitwidth;
+
+ /*
+ * Some systems take longer to respond
+ * than the reported worst-case latency.
+ */
+ for (i = val = 0; i < ACPICPU_P_STATE_RETRY; i++) {
+
+ (void)AcpiOsReadPort(addr, &val, width);
+
+ if (val == ps->ps_status)
+ break;
+
+ DELAY(ps->ps_latency);
+ }
+
+ if (i == ACPICPU_P_STATE_RETRY) {
+ rv = EAGAIN;
+ goto fail;
+ }
+
+ break;
+
+ default:
+ rv = ENOTTY;
+ goto fail;
+ }
+
+ ps->ps_stat++;
+ sc->sc_pstate_current = freq;
+
+ return 0;
+
+fail:
+ aprint_error_dev(sc->sc_dev, "failed to set "
+ "frequency to %u (err %d)\n", freq, rv);
+
+ sc->sc_pstate_current = ACPICPU_P_STATE_UNKNOWN;
+
+ return rv;
+}