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;
+}

Reply via email to