Module Name:    src
Committed By:   jruoho
Date:           Sun Jul 18 09:29:13 UTC 2010

Modified Files:
        src/sys/arch/amd64/conf: GENERIC
        src/sys/arch/i386/conf: GENERIC
        src/sys/arch/x86/conf: files.x86
        src/sys/arch/x86/include: machdep.h
        src/sys/arch/x86/x86: x86_machdep.c
        src/sys/dev/acpi: acpi.c files.acpi
Added Files:
        src/sys/arch/x86/acpi: acpi_cpu_md.c
        src/sys/dev/acpi: acpi_cpu.c acpi_cpu.h acpi_cpu_cstate.c

Log Message:
Merge a driver for ACPI CPUs with basic support for processor power states,
also known as C-states. The code is modular and provides an easy way to add
the remaining functionality later (namely throttling and P-states).

Remarks:

  1.    Commented out in the GENERICs; more testing exposure is needed.

  2.    The C3-state is disabled for the time being because it turns off
        timers, among them the local APIC timer. This may not be universally
        true on all x86 processors; define ACPICPU_ENABLE_C3 to test.

  3.    The algorithm used to choose a power state may need tuning. When
        evaluating the appropriate state, the implementation uses the
        previous sleep time as an indicator. Additional hints would include
        for example the system load.

        Also bus master activity is evaluated when choosing a state. The
        usb(4) stack is notorious for such activity even when unused.
        Typically it must be disabled in order to reach the C3-state,
        but it may also prevent the use of C2.

  4.    While no extensive empirical measurements have been carried out, the
        power savings are somewhere between 1-2 W with C1 and C2, depending
        on the processor, firmware, and load. With C3 even up to 4 W can be
        saved.  The less something ticks, the more power is saved.

ok jmcneill@, joerg@, and discussed with various people.


To generate a diff of this commit:
cvs rdiff -u -r1.281 -r1.282 src/sys/arch/amd64/conf/GENERIC
cvs rdiff -u -r1.986 -r1.987 src/sys/arch/i386/conf/GENERIC
cvs rdiff -u -r0 -r1.1 src/sys/arch/x86/acpi/acpi_cpu_md.c
cvs rdiff -u -r1.55 -r1.56 src/sys/arch/x86/conf/files.x86
cvs rdiff -u -r1.2 -r1.3 src/sys/arch/x86/include/machdep.h
cvs rdiff -u -r1.41 -r1.42 src/sys/arch/x86/x86/x86_machdep.c
cvs rdiff -u -r1.206 -r1.207 src/sys/dev/acpi/acpi.c
cvs rdiff -u -r0 -r1.1 src/sys/dev/acpi/acpi_cpu.c \
    src/sys/dev/acpi/acpi_cpu.h src/sys/dev/acpi/acpi_cpu_cstate.c
cvs rdiff -u -r1.75 -r1.76 src/sys/dev/acpi/files.acpi

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/amd64/conf/GENERIC
diff -u src/sys/arch/amd64/conf/GENERIC:1.281 src/sys/arch/amd64/conf/GENERIC:1.282
--- src/sys/arch/amd64/conf/GENERIC:1.281	Wed Jun  2 18:05:28 2010
+++ src/sys/arch/amd64/conf/GENERIC	Sun Jul 18 09:29:11 2010
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.281 2010/06/02 18:05:28 dholland Exp $
+# $NetBSD: GENERIC,v 1.282 2010/07/18 09:29:11 jruoho Exp $
 #
 # GENERIC machine description file
 #
@@ -22,7 +22,7 @@
 
 options 	INCLUDE_CONFIG_FILE	# embed config file in kernel binary
 
-#ident 		"GENERIC-$Revision: 1.281 $"
+#ident 		"GENERIC-$Revision: 1.282 $"
 
 maxusers	64		# estimated number of users
 
@@ -274,6 +274,7 @@
 acpiacad* 	at acpi?		# ACPI AC Adapter
 acpibat* 	at acpi?		# ACPI Battery
 acpibut* 	at acpi?		# ACPI Button
+#acpicpu*	at acpi?		# ACPI CPU
 acpidalb*	at acpi?		# Direct Application Launch Button
 # The ACPI Embedded Controller is generally configured via the special ECDT.
 # This is required as parts of the DSDT can reference the EC before the normal

Index: src/sys/arch/i386/conf/GENERIC
diff -u src/sys/arch/i386/conf/GENERIC:1.986 src/sys/arch/i386/conf/GENERIC:1.987
--- src/sys/arch/i386/conf/GENERIC:1.986	Sat Jun 26 15:17:56 2010
+++ src/sys/arch/i386/conf/GENERIC	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.986 2010/06/26 15:17:56 kefren Exp $
+# $NetBSD: GENERIC,v 1.987 2010/07/18 09:29:12 jruoho Exp $
 #
 # GENERIC machine description file
 #
@@ -22,7 +22,7 @@
 
 options 	INCLUDE_CONFIG_FILE	# embed config file in kernel binary
 
-#ident 		"GENERIC-$Revision: 1.986 $"
+#ident 		"GENERIC-$Revision: 1.987 $"
 
 maxusers	64		# estimated number of users
 
@@ -353,6 +353,7 @@
 acpiacad*	at acpi?		# ACPI AC Adapter
 acpibat*	at acpi?		# ACPI Battery
 acpibut*	at acpi?		# ACPI Button
+#acpicpu*	at acpi?		# ACPI CPU
 acpidalb*	at acpi?		# ACPI Direct Application Launch Button
 # The ACPI Embedded Controller is generally configured via the special ECDT.
 # This is required as parts of the DSDT can reference the EC before the normal

Index: src/sys/arch/x86/conf/files.x86
diff -u src/sys/arch/x86/conf/files.x86:1.55 src/sys/arch/x86/conf/files.x86:1.56
--- src/sys/arch/x86/conf/files.x86:1.55	Thu Jul  8 11:24:59 2010
+++ src/sys/arch/x86/conf/files.x86	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: files.x86,v 1.55 2010/07/08 11:24:59 rmind Exp $
+#	$NetBSD: files.x86,v 1.56 2010/07/18 09:29:12 jruoho Exp $
 
 # options for MP configuration through the MP spec
 defflag opt_mpbios.h MPBIOS MPVERBOSE MPDEBUG MPBIOS_SCANPCI
@@ -88,9 +88,11 @@
 # MP configuration using ACPI
 file 	arch/x86/x86/mpacpi.c		acpi
 
-file	arch/x86/acpi/acpi_wakeup.c	acpi
 file	arch/x86/x86/acpi_machdep.c	acpi
 
+file	arch/x86/acpi/acpi_wakeup.c	acpi
+file	arch/x86/acpi/acpi_cpu_md.c	acpi & acpicpu
+
 file	arch/x86/isa/isa_machdep.c	isa
 
 # Powernow common functions

Index: src/sys/arch/x86/include/machdep.h
diff -u src/sys/arch/x86/include/machdep.h:1.2 src/sys/arch/x86/include/machdep.h:1.3
--- src/sys/arch/x86/include/machdep.h:1.2	Mon Dec 15 22:20:52 2008
+++ src/sys/arch/x86/include/machdep.h	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-/* $NetBSD: machdep.h,v 1.2 2008/12/15 22:20:52 cegger Exp $ */
+/* $NetBSD: machdep.h,v 1.3 2010/07/18 09:29:12 jruoho Exp $ */
 /*
  * Copyright (c) 2000, 2007 The NetBSD Foundation, Inc.
  * All rights reserved.
@@ -34,8 +34,12 @@
 struct btinfo_memmap;
 struct extent;
 
-int initx86_parse_memmap(struct btinfo_memmap *, struct extent *);
-int initx86_fake_memmap(struct extent *);
-int initx86_load_memmap(paddr_t first_avail);
+void	x86_cpu_idle_init(void);
+void	x86_cpu_idle_get(void (**)(void), char *, size_t);
+void	x86_cpu_idle_set(void (*)(void), const char *);
+
+int	initx86_parse_memmap(struct btinfo_memmap *, struct extent *);
+int	initx86_fake_memmap(struct extent *);
+int	initx86_load_memmap(paddr_t first_avail);
 
 #endif	/* _X86_MACHDEP_H_ */

Index: src/sys/arch/x86/x86/x86_machdep.c
diff -u src/sys/arch/x86/x86/x86_machdep.c:1.41 src/sys/arch/x86/x86/x86_machdep.c:1.42
--- src/sys/arch/x86/x86/x86_machdep.c:1.41	Wed Jun  2 09:43:12 2010
+++ src/sys/arch/x86/x86/x86_machdep.c	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: x86_machdep.c,v 1.41 2010/06/02 09:43:12 joerg Exp $	*/
+/*	$NetBSD: x86_machdep.c,v 1.42 2010/07/18 09:29:12 jruoho Exp $	*/
 
 /*-
  * Copyright (c) 2002, 2006, 2007 YAMAMOTO Takashi,
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: x86_machdep.c,v 1.41 2010/06/02 09:43:12 joerg Exp $");
+__KERNEL_RCSID(0, "$NetBSD: x86_machdep.c,v 1.42 2010/07/18 09:29:12 jruoho Exp $");
 
 #include "opt_modular.h"
 #include "opt_physmem.h"
@@ -337,19 +337,32 @@
 {
 #ifndef XEN
 	if ((cpu_feature[1] & CPUID2_MONITOR) == 0 ||
-	    cpu_vendor == CPUVENDOR_AMD) {
-		strlcpy(x86_cpu_idle_text, "halt", sizeof(x86_cpu_idle_text));
-		x86_cpu_idle = x86_cpu_idle_halt;
-	} else {
-		strlcpy(x86_cpu_idle_text, "mwait", sizeof(x86_cpu_idle_text));
-		x86_cpu_idle = x86_cpu_idle_mwait;
-	}
+	    cpu_vendor == CPUVENDOR_AMD)
+		x86_cpu_idle_set(x86_cpu_idle_halt, "halt");
+	else
+		x86_cpu_idle_set(x86_cpu_idle_mwait, "mwait");
 #else
-	strlcpy(x86_cpu_idle_text, "xen", sizeof(x86_cpu_idle_text));
-	x86_cpu_idle = x86_cpu_idle_xen;
+	x86_cpu_idle_set(x86_cpu_idle_xen, "xen");
 #endif
 }
 
+void
+x86_cpu_idle_get(void (**func)(void), char *text, size_t len)
+{
+
+	*func = x86_cpu_idle;
+
+	(void)strlcpy(text, x86_cpu_idle_text, len);
+}
+
+void
+x86_cpu_idle_set(void (*func)(void), const char *text)
+{
+
+	x86_cpu_idle = func;
+
+	(void)strlcpy(x86_cpu_idle_text, text, sizeof(x86_cpu_idle_text));
+}
 
 #ifndef XEN
 

Index: src/sys/dev/acpi/acpi.c
diff -u src/sys/dev/acpi/acpi.c:1.206 src/sys/dev/acpi/acpi.c:1.207
--- src/sys/dev/acpi/acpi.c:1.206	Sat Jul 10 13:08:09 2010
+++ src/sys/dev/acpi/acpi.c	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-/*	$NetBSD: acpi.c,v 1.206 2010/07/10 13:08:09 jruoho Exp $	*/
+/*	$NetBSD: acpi.c,v 1.207 2010/07/18 09:29:12 jruoho Exp $	*/
 
 /*-
  * Copyright (c) 2003, 2007 The NetBSD Foundation, Inc.
@@ -65,7 +65,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: acpi.c,v 1.206 2010/07/10 13:08:09 jruoho Exp $");
+__KERNEL_RCSID(0, "$NetBSD: acpi.c,v 1.207 2010/07/18 09:29:12 jruoho Exp $");
 
 #include "opt_acpi.h"
 #include "opt_pcifixup.h"
@@ -1001,8 +1001,7 @@
 		/*
 		 * Handled internally.
 		 */
-		if (di->Type == ACPI_TYPE_POWER ||
-		    di->Type == ACPI_TYPE_PROCESSOR)
+		if (di->Type == ACPI_TYPE_POWER)
 			continue;
 
 		/*

Index: src/sys/dev/acpi/files.acpi
diff -u src/sys/dev/acpi/files.acpi:1.75 src/sys/dev/acpi/files.acpi:1.76
--- src/sys/dev/acpi/files.acpi:1.75	Mon May 31 20:32:29 2010
+++ src/sys/dev/acpi/files.acpi	Sun Jul 18 09:29:12 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: files.acpi,v 1.75 2010/05/31 20:32:29 pgoyette Exp $
+#	$NetBSD: files.acpi,v 1.76 2010/07/18 09:29:12 jruoho Exp $
 
 include "dev/acpi/acpica/files.acpica"
 
@@ -58,6 +58,12 @@
 attach	acpibat at acpinodebus
 file	dev/acpi/acpi_bat.c		acpibat
 
+# ACPI CPU
+device	acpicpu
+attach	acpicpu at acpinodebus
+file	dev/acpi/acpi_cpu.c		acpicpu
+file	dev/acpi/acpi_cpu_cstate.c	acpicpu
+
 # ACPI Thermal Zone
 device	acpitz: sysmon_envsys
 attach	acpitz at acpinodebus

Added files:

Index: src/sys/arch/x86/acpi/acpi_cpu_md.c
diff -u /dev/null src/sys/arch/x86/acpi/acpi_cpu_md.c:1.1
--- /dev/null	Sun Jul 18 09:29:13 2010
+++ src/sys/arch/x86/acpi/acpi_cpu_md.c	Sun Jul 18 09:29:13 2010
@@ -0,0 +1,187 @@
+/* $NetBSD */
+
+/*-
+ * 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_md.c,v 1.1 2010/07/18 09:29:13 jruoho Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kcore.h>
+
+#include <x86/cpu.h>
+#include <x86/cpuvar.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;
+
+extern uint32_t	cpus_running;
+
+uint32_t
+acpicpu_md_cap(void)
+{
+	struct cpu_info *ci = curcpu();
+	uint32_t val = 0;
+
+	if (cpu_vendor != CPUVENDOR_INTEL)
+		return val;
+
+	/*
+	 * Basic SMP C-states (required for _CST).
+	 */
+	val |= ACPICPU_PDC_C_C1PT | ACPICPU_PDC_C_C2C3;
+
+        /*
+	 * If MONITOR/MWAIT is available, announce
+	 * support for native instructions in all C-states.
+	 */
+        if ((ci->ci_feat_val[1] & CPUID2_MONITOR) != 0)
+		val |= ACPICPU_PDC_C_C1_FFH | ACPICPU_PDC_C_C2C3_FFH;
+
+	return val;
+}
+
+uint32_t
+acpicpu_md_quirks(void)
+{
+	struct cpu_info *ci = curcpu();
+	uint32_t val = 0;
+
+	if (acpicpu_md_cpus_running() == 1)
+		val |= ACPICPU_FLAG_C_BM;
+
+	if ((ci->ci_feat_val[1] & CPUID2_MONITOR) != 0)
+		val |= ACPICPU_FLAG_C_MWAIT;
+
+	switch (cpu_vendor) {
+
+	case CPUVENDOR_INTEL:
+		val |= ACPICPU_FLAG_C_BM | ACPICPU_FLAG_C_ARB;
+
+		/*
+		 * Bus master arbitration is not
+		 * needed on some recent Intel CPUs.
+		 */
+		if (CPUID2FAMILY(ci->ci_signature) > 15)
+			val &= ~ACPICPU_FLAG_C_ARB;
+
+		if (CPUID2FAMILY(ci->ci_signature) == 6 &&
+		    CPUID2MODEL(ci->ci_signature) >= 15)
+			val &= ~ACPICPU_FLAG_C_ARB;
+
+		break;
+
+	case CPUVENDOR_AMD:
+
+		/*
+		 * XXX: Deal with the AMD C1E extension here.
+		 */
+		break;
+	}
+
+	return val;
+}
+
+uint32_t
+acpicpu_md_cpus_running(void)
+{
+
+	return popcount32(cpus_running);
+}
+
+int
+acpicpu_md_idle_init(void)
+{
+	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();
+
+	return 0;
+}
+
+int
+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();
+
+	DELAY(10000);
+
+	return 0;
+}
+
+int
+acpicpu_md_idle_stop(void)
+{
+
+	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();
+
+	DELAY(10000);
+
+	return 0;
+}
+
+void
+acpicpu_md_idle_enter(int method, int state)
+{
+
+	KASSERT(native_idle != NULL);
+
+	switch (method) {
+
+	case ACPICPU_C_STATE_FFH:
+		x86_mwait((state - 1) << 4, 0);
+		break;
+
+	case ACPICPU_C_STATE_HALT:
+		x86_stihlt();
+		break;
+
+	default:
+		(*native_idle)();
+		break;
+	}
+}

Index: src/sys/dev/acpi/acpi_cpu.c
diff -u /dev/null src/sys/dev/acpi/acpi_cpu.c:1.1
--- /dev/null	Sun Jul 18 09:29:13 2010
+++ src/sys/dev/acpi/acpi_cpu.c	Sun Jul 18 09:29:12 2010
@@ -0,0 +1,567 @@
+/* $NetBSD */
+
+/*-
+ * 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.c,v 1.1 2010/07/18 09:29:12 jruoho Exp $");
+
+#include <sys/param.h>
+#include <sys/cpu.h>
+#include <sys/kernel.h>
+#include <sys/kmem.h>
+#include <sys/module.h>
+#include <sys/once.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_cpu.h>
+
+#include <machine/acpi_machdep.h>
+
+#define _COMPONENT	  ACPI_BUS_COMPONENT
+ACPI_MODULE_NAME	  ("acpi_cpu")
+
+static int		  acpicpu_match(device_t, cfdata_t, void *);
+static void		  acpicpu_attach(device_t, device_t, void *);
+static int		  acpicpu_detach(device_t, int);
+static int		  acpicpu_once_attach(void);
+static int		  acpicpu_once_detach(void);
+
+static int		  acpicpu_object(ACPI_HANDLE, struct acpicpu_object *);
+static cpuid_t		  acpicpu_id(uint32_t);
+static uint32_t		  acpicpu_cap(struct acpicpu_softc *);
+static ACPI_OBJECT	 *acpicpu_cap_init(void);
+static ACPI_STATUS	  acpicpu_cap_pdc(ACPI_HANDLE);
+static ACPI_STATUS	  acpicpu_cap_osc(ACPI_HANDLE, uint32_t *);
+static const char	 *acpicpu_cap_oscerr(uint32_t);
+static void		  acpicpu_notify(ACPI_HANDLE, uint32_t, void *);
+static bool		  acpicpu_suspend(device_t, const pmf_qual_t *);
+static bool		  acpicpu_resume(device_t, const pmf_qual_t *);
+
+kmutex_t		  acpicpu_mtx;
+struct acpicpu_softc	**acpicpu_sc = NULL;
+
+static const char * const acpicpu_hid[] = {
+	"ACPI0007",
+	NULL
+};
+
+CFATTACH_DECL_NEW(acpicpu, sizeof(struct acpicpu_softc),
+    acpicpu_match, acpicpu_attach, acpicpu_detach, NULL);
+
+static int
+acpicpu_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+
+	if (aa->aa_node->ad_type != ACPI_TYPE_PROCESSOR)
+		return 0;
+
+	if (acpi_match_hid(aa->aa_node->ad_devinfo, acpicpu_hid) != 0)
+		return 1;
+
+	return acpicpu_object(aa->aa_node->ad_handle, NULL);
+}
+
+static void
+acpicpu_attach(device_t parent, device_t self, void *aux)
+{
+	struct acpicpu_softc *sc = device_private(self);
+	struct acpi_attach_args *aa = aux;
+	static ONCE_DECL(once_attach);
+	int rv;
+
+	rv = acpicpu_object(aa->aa_node->ad_handle, &sc->sc_object);
+
+	if (rv == 0)
+		return;
+
+	rv = RUN_ONCE(&once_attach, acpicpu_once_attach);
+
+	if (rv != 0)
+		return;
+
+	KASSERT(acpicpu_sc != NULL);
+
+	mutex_enter(&acpicpu_mtx);
+
+	sc->sc_dev = self;
+	sc->sc_iot = aa->aa_iot;
+	sc->sc_node = aa->aa_node;
+	sc->sc_cpuid = acpicpu_id(sc->sc_object.ao_procid);
+
+	if (sc->sc_cpuid == 0xFFFFFF) {
+		mutex_exit(&acpicpu_mtx);
+		aprint_error(": invalid CPU ID\n");
+		return;
+	}
+
+	if (acpicpu_sc[sc->sc_cpuid] != NULL) {
+		mutex_exit(&acpicpu_mtx);
+		aprint_error(": already probed\n");
+		return;
+	}
+
+	acpicpu_sc[sc->sc_cpuid] = sc;
+	mutex_exit(&acpicpu_mtx);
+
+	sc->sc_cap = acpicpu_cap(sc);
+	sc->sc_flags |= acpicpu_md_quirks();
+
+	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");
+
+	/*
+	 * We should claim the bus space. However, we do this only
+	 * to announce that the space is in use. This is unnecessary
+	 * if system I/O type C-states are not used. Many systems also
+	 * report invalid values in the processor object. Finally, this
+	 * is known to conflict with other devices. But as is noted in
+	 * ichlpcib(4), we can continue our I/O without bus_space(9).
+	 */
+	if (sc->sc_object.ao_pblklen == 6 && sc->sc_object.ao_pblkaddr != 0) {
+
+		rv = bus_space_map(sc->sc_iot, sc->sc_object.ao_pblkaddr,
+		    sc->sc_object.ao_pblklen, 0, &sc->sc_ioh);
+
+		if (rv != 0)
+			sc->sc_ioh = 0;
+	}
+
+	acpicpu_cstate_attach(self);
+
+	(void)acpi_register_notify(sc->sc_node, acpicpu_notify);
+	(void)config_finalize_register(self, acpicpu_cstate_start);
+	(void)pmf_device_register(self, acpicpu_suspend, acpicpu_resume);
+}
+
+static int
+acpicpu_detach(device_t self, int flags)
+{
+	struct acpicpu_softc *sc = device_private(self);
+	const bus_addr_t addr = sc->sc_object.ao_pblkaddr;
+	static ONCE_DECL(once_detach);
+	int rv = 0;
+
+	acpi_deregister_notify(sc->sc_node);
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C) != 0)
+		rv = acpicpu_cstate_detach(self);
+
+	if (rv != 0)
+		return rv;
+
+	rv = RUN_ONCE(&once_detach, acpicpu_once_detach);
+
+	if (rv != 0)
+		return rv;
+
+	if (sc->sc_ioh != 0)
+		bus_space_unmap(sc->sc_iot, sc->sc_ioh, addr);
+
+	return 0;
+}
+
+static int
+acpicpu_once_attach(void)
+{
+	struct acpicpu_softc *sc;
+	unsigned int i;
+
+	acpicpu_sc = kmem_zalloc(maxcpus * sizeof(*sc), KM_SLEEP);
+
+	if (acpicpu_sc == NULL)
+		return ENOMEM;
+
+	for (i = 0; i < maxcpus; i++)
+		acpicpu_sc[i] = NULL;
+
+	mutex_init(&acpicpu_mtx, MUTEX_DEFAULT, IPL_VM);
+
+	return 0;
+}
+
+static int
+acpicpu_once_detach(void)
+{
+	struct acpicpu_softc *sc;
+
+	KASSERT(acpicpu_sc != NULL);
+
+	mutex_destroy(&acpicpu_mtx);
+	kmem_free(acpicpu_sc, maxcpus * sizeof(*sc));
+	acpicpu_sc = NULL;
+
+	return 0;
+}
+
+static int
+acpicpu_object(ACPI_HANDLE hdl, struct acpicpu_object *ao)
+{
+	ACPI_OBJECT *obj;
+	ACPI_BUFFER buf;
+	ACPI_STATUS rv;
+
+	rv = acpi_eval_struct(hdl, NULL, &buf);
+
+	if (ACPI_FAILURE(rv))
+		return 0;
+
+	obj = buf.Pointer;
+
+	if (obj->Type != ACPI_TYPE_PROCESSOR) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	if (obj->Processor.ProcId > (uint32_t)maxcpus) {
+		rv = AE_LIMIT;
+		goto out;
+	}
+
+	KDASSERT((uint64_t)obj->Processor.PblkAddress < UINT32_MAX);
+
+	if (ao != NULL) {
+		ao->ao_procid = obj->Processor.ProcId;
+		ao->ao_pblklen = obj->Processor.PblkLength;
+		ao->ao_pblkaddr = obj->Processor.PblkAddress;
+	}
+
+out:
+	if (buf.Pointer != NULL)
+		ACPI_FREE(buf.Pointer);
+
+	return ACPI_FAILURE(rv) ? 0 : 1;
+}
+
+static cpuid_t
+acpicpu_id(uint32_t id)
+{
+	CPU_INFO_ITERATOR cii;
+	struct cpu_info *ci;
+
+	for (CPU_INFO_FOREACH(cii, ci)) {
+
+		if (id == ci->ci_cpuid)
+			return id;
+	}
+
+	return 0xFFFFFF;
+}
+
+static uint32_t
+acpicpu_cap(struct acpicpu_softc *sc)
+{
+	uint32_t cap[3] = { 0 };
+	ACPI_STATUS rv;
+	int err;
+
+	/*
+	 * Set machine-dependent processor capabilities.
+	 *
+	 * The _PDC was deprecated in ACPI 3.0 in favor of the _OSC,
+	 * but firmware may expect that we evaluate it nevertheless.
+	 */
+	rv = acpicpu_cap_pdc(sc->sc_node->ad_handle);
+
+	if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND)
+		aprint_error_dev(sc->sc_dev, "failed to evaluate _PDC: "
+		    "%s\n", AcpiFormatException(rv));
+
+	rv = acpicpu_cap_osc(sc->sc_node->ad_handle, cap);
+
+	if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND)
+		aprint_error_dev(sc->sc_dev, "failed to evaluate _OSC: "
+		    "%s\n", AcpiFormatException(rv));
+
+	if (ACPI_SUCCESS(rv)) {
+
+		err = cap[0] & ~__BIT(0);
+
+		if (err != 0) {
+			aprint_error_dev(sc->sc_dev, "errors in "
+			    "_OSC: %s\n", acpicpu_cap_oscerr(err));
+			cap[2] = 0;
+		}
+	}
+
+	return cap[2];
+}
+
+static ACPI_OBJECT *
+acpicpu_cap_init(void)
+{
+	static uint32_t cap[3];
+	static ACPI_OBJECT obj;
+
+	cap[0] = ACPICPU_PDC_REVID;
+	cap[1] = 1;
+	cap[2] = acpicpu_md_cap();
+
+	obj.Type = ACPI_TYPE_BUFFER;
+	obj.Buffer.Length = sizeof(cap);
+	obj.Buffer.Pointer = (uint8_t *)cap;
+
+	return &obj;
+}
+
+static ACPI_STATUS
+acpicpu_cap_pdc(ACPI_HANDLE hdl)
+{
+	ACPI_OBJECT_LIST arg_list;
+
+	arg_list.Count = 1;
+	arg_list.Pointer = acpicpu_cap_init();
+
+	return AcpiEvaluateObject(hdl, "_PDC", &arg_list, NULL);
+}
+
+static ACPI_STATUS
+acpicpu_cap_osc(ACPI_HANDLE hdl, uint32_t *val)
+{
+	ACPI_OBJECT_LIST arg_list;
+	ACPI_OBJECT *cap, *obj;
+	ACPI_OBJECT arg[4];
+	ACPI_BUFFER buf;
+	ACPI_STATUS rv;
+
+	/* Intel. */
+	static uint8_t cpu_oscuuid[16] = {
+		0x16, 0xA6, 0x77, 0x40, 0x0C, 0x29, 0xBE, 0x47,
+		0x9E, 0xBD, 0xD8, 0x70, 0x58, 0x71, 0x39, 0x53
+	};
+
+	cap = acpicpu_cap_init();
+
+	arg_list.Count = 4;
+	arg_list.Pointer = arg;
+
+	arg[0].Type = ACPI_TYPE_BUFFER;
+	arg[0].Buffer.Length = sizeof(cpu_oscuuid);
+	arg[0].Buffer.Pointer = cpu_oscuuid;
+
+	arg[1].Type = ACPI_TYPE_INTEGER;
+	arg[1].Integer.Value = ACPICPU_PDC_REVID;
+
+	arg[2].Type = ACPI_TYPE_INTEGER;
+	arg[2].Integer.Value = cap->Buffer.Length / sizeof(uint32_t);
+
+	arg[3] = *cap;
+
+	buf.Pointer = NULL;
+	buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
+
+	rv = AcpiEvaluateObject(hdl, "_OSC", &arg_list, &buf);
+
+	if (ACPI_FAILURE(rv))
+		return rv;
+
+	obj = buf.Pointer;
+
+	if (obj->Type != ACPI_TYPE_BUFFER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	if (obj->Buffer.Length != cap->Buffer.Length) {
+		rv = AE_BUFFER_OVERFLOW;
+		goto out;
+	}
+
+	(void)memcpy(val, obj->Buffer.Pointer, obj->Buffer.Length);
+
+out:
+	if (buf.Pointer != NULL)
+		ACPI_FREE(buf.Pointer);
+
+	return rv;
+}
+
+static const char *
+acpicpu_cap_oscerr(uint32_t err)
+{
+
+	KASSERT((err & __BIT(0)) == 0);
+
+	if ((err & __BIT(1)) != 0)
+		return "_OSC failure";
+
+	if ((err & __BIT(2)) != 0)
+		return "unrecognized UUID";
+
+	if ((err & __BIT(3)) != 0)
+		return "unrecognized revision";
+
+	if ((err & __BIT(4)) != 0)
+		return "capabilities masked";
+
+	return "unknown error";
+}
+
+static void
+acpicpu_notify(ACPI_HANDLE hdl, uint32_t evt, void *aux)
+{
+	ACPI_OSD_EXEC_CALLBACK func;
+	struct acpicpu_softc *sc;
+	device_t self = aux;
+
+	sc = device_private(self);
+
+	switch (evt) {
+
+	case ACPICPU_C_NOTIFY:
+
+		if ((sc->sc_flags & ACPICPU_FLAG_C) == 0)
+			return;
+
+		func = acpicpu_cstate_callback;
+		break;
+
+	case ACPICPU_P_NOTIFY:
+
+		if ((sc->sc_flags & ACPICPU_FLAG_P) == 0)
+			return;
+
+		func = NULL;
+		break;
+
+	case ACPICPU_T_NOTIFY:
+
+		if ((sc->sc_flags & ACPICPU_FLAG_T) == 0)
+			return;
+
+		func = NULL;
+		break;
+
+	default:
+		aprint_error_dev(sc->sc_dev,  "unknown notify: 0x%02X\n", evt);
+		return;
+	}
+
+	(void)AcpiOsExecute(OSL_NOTIFY_HANDLER, func, sc->sc_dev);
+}
+
+static bool
+acpicpu_suspend(device_t self, const pmf_qual_t *qual)
+{
+	struct acpicpu_softc *sc = device_private(self);
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C) != 0)
+		(void)acpicpu_cstate_suspend(self);
+
+	return true;
+}
+
+static bool
+acpicpu_resume(device_t self, const pmf_qual_t *qual)
+{
+	struct acpicpu_softc *sc = device_private(self);
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C) != 0)
+		(void)acpicpu_cstate_resume(self);
+
+	return true;
+}
+
+#ifdef _MODULE
+
+MODULE(MODULE_CLASS_DRIVER, acpicpu, NULL);
+CFDRIVER_DECL(acpicpu, DV_DULL, NULL);
+
+static int acpicpuloc[] = { -1 };
+extern struct cfattach acpicpu_ca;
+
+static struct cfparent acpiparent = {
+	"acpinodebus", NULL, DVUNIT_ANY
+};
+
+static struct cfdata acpicpu_cfdata[] = {
+	{
+		.cf_name = "acpicpu",
+		.cf_atname = "acpicpu",
+		.cf_unit = 0,
+		.cf_fstate = FSTATE_STAR,
+		.cf_loc = acpicpuloc,
+		.cf_flags = 0,
+		.cf_pspec = &acpiparent,
+	},
+
+	{ NULL, NULL, 0, 0, NULL, 0, NULL }
+};
+
+static int
+acpicpu_modcmd(modcmd_t cmd, void *context)
+{
+	int err;
+
+	switch (cmd) {
+
+	case MODULE_CMD_INIT:
+
+		err = config_cfdriver_attach(&acpicpu_cd);
+
+		if (err != 0)
+			return err;
+
+		err = config_cfattach_attach("acpicpu", &acpicpu_ca);
+
+		if (err != 0) {
+			config_cfdriver_detach(&acpicpu_cd);
+			return err;
+		}
+
+		err = config_cfdata_attach(acpicpu_cfdata, 1);
+
+		if (err != 0) {
+			config_cfattach_detach("acpicpu", &acpicpu_ca);
+			config_cfdriver_detach(&acpicpu_cd);
+			return err;
+		}
+
+		return 0;
+
+	case MODULE_CMD_FINI:
+
+		err = config_cfdata_detach(acpicpu_cfdata);
+
+		if (err != 0)
+			return err;
+
+		config_cfattach_detach("acpicpu", &acpicpu_ca);
+		config_cfdriver_detach(&acpicpu_cd);
+
+		return 0;
+
+	default:
+		return ENOTTY;
+	}
+}
+
+#endif	/* _MODULE */
Index: src/sys/dev/acpi/acpi_cpu.h
diff -u /dev/null src/sys/dev/acpi/acpi_cpu.h:1.1
--- /dev/null	Sun Jul 18 09:29:13 2010
+++ src/sys/dev/acpi/acpi_cpu.h	Sun Jul 18 09:29:13 2010
@@ -0,0 +1,140 @@
+/* $NetBSD */
+
+/*-
+ * 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.
+ */
+
+#ifndef _SYS_DEV_ACPI_ACPI_CPU_H
+#define _SYS_DEV_ACPI_ACPI_CPU_H
+
+/*
+ * The following _PDC values are based on:
+ *
+ *   Intel Processor-Specific ACPI Interface
+ *   Specification, September 2006, Revision 005.
+ */
+#define ACPICPU_PDC_REVID         0x1
+#define ACPICPU_PDC_SMP           0xA
+#define ACPICPU_PDC_MSR           0x1
+
+#define ACPICPU_PDC_P_FFH         __BIT(0)	/* SpeedStep MSRs            */
+#define ACPICPU_PDC_C_C1_HALT     __BIT(1)	/* C1 "I/O then halt"        */
+#define ACPICPU_PDC_T_FFH         __BIT(2)	/* OnDemand throttling MSRs  */
+#define ACPICPU_PDC_C_C1PT        __BIT(3)	/* SMP C1, Px, and Tx (same) */
+#define ACPICPU_PDC_C_C2C3        __BIT(4)	/* SMP C2 and C3 (same)      */
+#define ACPICPU_PDC_P_SW          __BIT(5)	/* SMP Px (different)        */
+#define ACPICPU_PDC_C_SW          __BIT(6)	/* SMP Cx (different)        */
+#define ACPICPU_PDC_T_SW          __BIT(7)	/* SMP Tx (different)        */
+#define ACPICPU_PDC_C_C1_FFH      __BIT(8)	/* SMP C1 native beyond halt */
+#define ACPICPU_PDC_C_C2C3_FFH    __BIT(9)	/* SMP C2 and C2 native      */
+#define ACPICPU_PDC_P_HW          __BIT(11)	/* Px hardware coordination  */
+
+/*
+ * Notify values.
+ */
+#define ACPICPU_P_NOTIFY	 0x80		/* _PPC */
+#define ACPICPU_C_NOTIFY	 0x81		/* _CST */
+#define ACPICPU_T_NOTIFY	 0x82		/* _TPC */
+
+/*
+ * C-states.
+ */
+#define ACPICPU_C_C2_LATENCY_MAX 100		/* us */
+#define ACPICPU_C_C3_LATENCY_MAX 1000		/* us */
+
+#define ACPICPU_C_CSD_SW_ALL	 0xFC
+#define ACPICPU_C_CSD_SW_ANY	 0xFD
+#define ACPICPU_C_CSD_HW_ALL	 0xFE
+
+#define ACPICPU_C_STATE_HALT	 0x01
+#define ACPICPU_C_STATE_FFH	 0x02
+#define ACPICPU_C_STATE_SYSIO	 0x03
+
+/*
+ * Global flags in the softc.
+ */
+#define ACPICPU_FLAG_C		 __BIT(0)
+#define ACPICPU_FLAG_P		 __BIT(1)
+#define ACPICPU_FLAG_T		 __BIT(2)
+#define ACPICPU_FLAG_C_CST	 __BIT(3)
+#define ACPICPU_FLAG_C_FADT	 __BIT(4)
+#define ACPICPU_FLAG_C_BM	 __BIT(5)
+#define ACPICPU_FLAG_C_ARB	 __BIT(6)
+#define ACPICPU_FLAG_C_NOC3	 __BIT(7)
+#define ACPICPU_FLAG_C_MWAIT	 __BIT(8)
+#define ACPICPU_FLAG_C_C1E	 __BIT(9)
+
+struct acpicpu_cstate {
+	uint64_t		 cs_stat;
+	uint64_t		 cs_addr;
+	uint32_t		 cs_power;	/* mW */
+	uint32_t		 cs_latency;	/* us */
+	int			 cs_method;
+};
+
+struct acpicpu_csd {
+	uint32_t		 csd_domain;
+	uint32_t		 csd_coord;
+	uint32_t		 csd_ncpu;
+	uint32_t		 csd_index;
+};
+
+struct acpicpu_object {
+	uint32_t		 ao_procid;
+	uint32_t		 ao_pblklen;
+	uint32_t		 ao_pblkaddr;
+};
+
+struct acpicpu_softc {
+	device_t		 sc_dev;
+	struct acpi_devnode	*sc_node;
+	struct acpicpu_cstate	 sc_cstate[ACPI_C_STATE_COUNT];
+	struct acpicpu_object	 sc_object;
+	bus_space_tag_t		 sc_iot;
+	bus_space_handle_t	 sc_ioh;
+	uint32_t		 sc_sleep;
+	uint32_t		 sc_cpuid;
+	uint32_t		 sc_cap;
+	uint32_t		 sc_flags;
+};
+
+void		acpicpu_cstate_attach(device_t);
+int		acpicpu_cstate_detach(device_t);
+int		acpicpu_cstate_start(device_t);
+bool		acpicpu_cstate_suspend(device_t);
+bool		acpicpu_cstate_resume(device_t);
+void		acpicpu_cstate_callback(void *);
+void		acpicpu_cstate_idle(void);
+
+uint32_t	acpicpu_md_cap(void);
+uint32_t	acpicpu_md_quirks(void);
+uint32_t	acpicpu_md_cpus_running(void);
+int		acpicpu_md_idle_init(void);
+int		acpicpu_md_idle_start(void);
+int		acpicpu_md_idle_stop(void);
+void		acpicpu_md_idle_enter(int, int);
+
+#endif	/* !_SYS_DEV_ACPI_ACPI_CPU_H */
Index: src/sys/dev/acpi/acpi_cpu_cstate.c
diff -u /dev/null src/sys/dev/acpi/acpi_cpu_cstate.c:1.1
--- /dev/null	Sun Jul 18 09:29:13 2010
+++ src/sys/dev/acpi/acpi_cpu_cstate.c	Sun Jul 18 09:29:13 2010
@@ -0,0 +1,859 @@
+/* $NetBSD */
+
+/*-
+ * 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_cstate.c,v 1.1 2010/07/18 09:29:13 jruoho Exp $");
+
+#include <sys/param.h>
+#include <sys/cpu.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/once.h>
+#include <sys/timetc.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcidevs.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpi_cpu.h>
+#include <dev/acpi/acpi_timer.h>
+
+#include <machine/acpi_machdep.h>
+
+/*
+ * This is AML_RESOURCE_GENERIC_REGISTER,
+ * included here separately for convenience.
+ */
+struct acpicpu_reg {
+	uint8_t		 reg_desc;
+	uint16_t	 reg_reslen;
+	uint8_t		 reg_spaceid;
+	uint8_t		 reg_bitwidth;
+	uint8_t		 reg_bitoffset;
+	uint8_t		 reg_accesssize;
+	uint64_t	 reg_addr;
+} __packed;
+
+static void		 acpicpu_cstate_attach_print(struct acpicpu_softc *);
+static ACPI_STATUS	 acpicpu_cstate_cst(struct acpicpu_softc *);
+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_csd *);
+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 *);
+static int		 acpicpu_cstate_latency(struct acpicpu_softc *);
+static bool		 acpicpu_cstate_bm_check(void);
+static void		 acpicpu_cstate_idle_enter(struct acpicpu_softc *,int);
+
+extern kmutex_t		      acpicpu_mtx;
+extern struct acpicpu_softc **acpicpu_sc;
+extern int		      acpi_suspended;
+
+void
+acpicpu_cstate_attach(device_t self)
+{
+	struct acpicpu_softc *sc = device_private(self);
+	ACPI_STATUS rv;
+
+	/*
+	 * Either use the preferred _CST or resort to FADT.
+	 */
+	rv = acpicpu_cstate_cst(sc);
+
+	switch (rv) {
+
+	case AE_OK:
+		sc->sc_flags |= ACPICPU_FLAG_C | ACPICPU_FLAG_C_CST;
+		acpicpu_cstate_cst_bios();
+		break;
+
+	default:
+		sc->sc_flags |= ACPICPU_FLAG_C | ACPICPU_FLAG_C_FADT;
+		acpicpu_cstate_fadt(sc);
+		break;
+	}
+
+	acpicpu_cstate_quirks(sc);
+	acpicpu_cstate_attach_print(sc);
+}
+
+void
+acpicpu_cstate_attach_print(struct acpicpu_softc *sc)
+{
+	struct acpicpu_cstate *cs;
+	struct acpicpu_csd csd;
+	const char *method;
+	ACPI_STATUS rv;
+	int i;
+
+	(void)memset(&csd, 0, sizeof(struct acpicpu_csd));
+
+	rv = acpicpu_cstate_csd(sc->sc_node->ad_handle, &csd);
+
+	if (ACPI_SUCCESS(rv)) {
+		aprint_debug_dev(sc->sc_dev, "C%u:  _CSD, "
+		    "domain 0x%02x / 0x%02x, type 0x%02x\n",
+		    csd.csd_index, csd.csd_domain,
+		    csd.csd_ncpu, csd.csd_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];
+
+		if (cs->cs_method == 0)
+			continue;
+
+		switch (cs->cs_method) {
+
+		case ACPICPU_C_STATE_HALT:
+			method = "HALT";
+			break;
+
+		case ACPICPU_C_STATE_FFH:
+			method = "FFH";
+			break;
+
+		case ACPICPU_C_STATE_SYSIO:
+			method = "SYSIO";
+			break;
+
+		default:
+			panic("NOTREACHED");
+		}
+
+		aprint_debug_dev(sc->sc_dev, "C%d: %5s, "
+		    "latency %4u, power %4u, addr 0x%06x\n", i, method,
+		    cs->cs_latency, cs->cs_power, (uint32_t)cs->cs_addr);
+	}
+}
+
+int
+acpicpu_cstate_detach(device_t self)
+{
+	static ONCE_DECL(once_detach);
+
+	return RUN_ONCE(&once_detach, acpicpu_md_idle_stop);
+}
+
+int
+acpicpu_cstate_start(device_t self)
+{
+	struct acpicpu_softc *sc = device_private(self);
+	static ONCE_DECL(once_start);
+	static ONCE_DECL(once_save);
+	int rv;
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C) == 0)
+		return 0;
+
+	/*
+	 * Save the existing idle-mechanism and claim the idle_loop(9).
+	 * This should be called after all ACPI CPUs have been attached.
+	 */
+	rv = RUN_ONCE(&once_save, acpicpu_md_idle_init);
+
+	if (rv != 0)
+		return rv;
+
+	return RUN_ONCE(&once_start, acpicpu_md_idle_start);
+}
+
+bool
+acpicpu_cstate_suspend(device_t self)
+{
+
+	return true;
+}
+
+bool
+acpicpu_cstate_resume(device_t self)
+{
+	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);
+
+	return true;
+}
+
+void
+acpicpu_cstate_callback(void *aux)
+{
+	struct acpicpu_softc *sc;
+	device_t self = aux;
+
+	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;
+	}
+
+	(void)acpicpu_md_idle_stop();
+	(void)acpicpu_cstate_cst(sc);
+	(void)acpicpu_md_idle_start();
+}
+
+static ACPI_STATUS
+acpicpu_cstate_cst(struct acpicpu_softc *sc)
+{
+	ACPI_OBJECT *elm, *obj;
+	ACPI_BUFFER buf;
+	ACPI_STATUS rv;
+	uint32_t i, n;
+	uint8_t count;
+
+	rv = acpi_eval_struct(sc->sc_node->ad_handle, "_CST", &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;
+	}
+
+	elm = obj->Package.Elements;
+
+	if (elm[0].Type != ACPI_TYPE_INTEGER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	n = elm[0].Integer.Value;
+
+	if (n != obj->Package.Count - 1) {
+		rv = AE_BAD_VALUE;
+		goto out;
+	}
+
+	if (n > ACPI_C_STATES_MAX) {
+		rv = AE_LIMIT;
+		goto out;
+	}
+
+	(void)memset(sc->sc_cstate, 0,
+	    sizeof(*sc->sc_cstate) * ACPI_C_STATE_COUNT);
+
+	for (count = 0, i = 1; i <= n; i++) {
+
+		elm = &obj->Package.Elements[i];
+		rv = acpicpu_cstate_cst_add(sc, elm);
+
+		if (ACPI_SUCCESS(rv))
+			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 "
+		    "_CST: %s\n", AcpiFormatException(rv));
+
+	return rv;
+}
+
+static ACPI_STATUS
+acpicpu_cstate_cst_add(struct acpicpu_softc *sc, ACPI_OBJECT *elm)
+{
+	const struct acpicpu_object *ao = &sc->sc_object;
+	struct acpicpu_cstate *cs = sc->sc_cstate;
+	struct acpicpu_cstate state;
+	struct acpicpu_reg *reg;
+	ACPI_STATUS rv = AE_OK;
+	ACPI_OBJECT *obj;
+	uint32_t type;
+	int i;
+
+	(void)memset(&state, 0, sizeof(*cs));
+
+	if (elm->Type != ACPI_TYPE_PACKAGE) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	if (elm->Package.Count != 4) {
+		rv = AE_LIMIT;
+		goto out;
+	}
+
+	/*
+	 * Type.
+	 */
+	obj = &elm->Package.Elements[1];
+
+	if (obj->Type != ACPI_TYPE_INTEGER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	type = obj->Integer.Value;
+
+	/*
+	 * Latency.
+	 */
+	obj = &elm->Package.Elements[2];
+
+	if (obj->Type != ACPI_TYPE_INTEGER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	state.cs_latency = obj->Integer.Value;
+
+	/*
+	 * Power.
+	 */
+	obj = &elm->Package.Elements[3];
+
+	if (obj->Type != ACPI_TYPE_INTEGER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	state.cs_power = obj->Integer.Value;
+
+	/*
+	 * Register.
+	 */
+	obj = &elm->Package.Elements[0];
+
+	if (obj->Type != ACPI_TYPE_BUFFER) {
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	/*
+	 * "When specifically directed by the CPU manufacturer, the
+	 *  system firmware may define an interface as functional
+	 *  fixed hardware by supplying a special address space
+	 *  identifier, FfixedHW (0x7F), in the address space ID
+	 *  field for register definitions (ACPI 3.0, p. 46)".
+	 */
+	reg = (struct acpicpu_reg *)obj->Buffer.Pointer;
+
+	switch (reg->reg_spaceid) {
+
+	case ACPI_ADR_SPACE_SYSTEM_IO:
+		state.cs_method = ACPICPU_C_STATE_SYSIO;
+
+		if (reg->reg_addr == 0) {
+			rv = AE_AML_ILLEGAL_ADDRESS;
+			goto out;
+		}
+
+		if (reg->reg_bitwidth != 8) {
+			rv = AE_AML_BAD_RESOURCE_LENGTH;
+			goto out;
+		}
+
+		break;
+
+	case ACPI_ADR_SPACE_FIXED_HARDWARE:
+		state.cs_method = ACPICPU_C_STATE_FFH;
+
+		switch (type) {
+
+		case ACPI_STATE_C1:
+
+			if ((sc->sc_flags & ACPICPU_FLAG_C_MWAIT) == 0)
+				state.cs_method = ACPICPU_C_STATE_HALT;
+
+			break;
+
+		default:
+
+			if ((sc->sc_flags & ACPICPU_FLAG_C_MWAIT) == 0) {
+				rv = AE_AML_BAD_RESOURCE_VALUE;
+				goto out;
+			}
+		}
+
+		break;
+
+	default:
+		rv = AE_AML_INVALID_SPACE_ID;
+		goto out;
+	}
+
+	state.cs_addr = reg->reg_addr;
+
+	CTASSERT(ACPICPU_C_C2_LATENCY_MAX == 100);
+	CTASSERT(ACPICPU_C_C3_LATENCY_MAX == 1000);
+
+	CTASSERT(ACPI_STATE_C0 == 0 && ACPI_STATE_C1 == 1);
+	CTASSERT(ACPI_STATE_C2 == 2 && ACPI_STATE_C3 == 3);
+
+	switch (type) {
+
+	case ACPI_STATE_C1:
+		i = 1;
+		break;
+
+	case ACPI_STATE_C2:
+
+		if (state.cs_latency > ACPICPU_C_C2_LATENCY_MAX) {
+			rv = AE_BAD_VALUE;
+			goto out;
+		}
+
+		i = 2;
+		break;
+
+	case ACPI_STATE_C3:
+
+		if (state.cs_latency > ACPICPU_C_C3_LATENCY_MAX) {
+			rv = AE_BAD_VALUE;
+			goto out;
+		}
+
+		i = 3;
+		break;
+
+	default:
+		rv = AE_TYPE;
+		goto out;
+	}
+
+	/*
+	 * Check only that the address is in the mapped space.
+	 * Systems are allowed to change it when operating
+	 * with _CST (see ACPI 4.0, pp. 94-95). For instance,
+	 * the offset of P_LVL3 may change depending on whether
+	 * acpiacad(4) is connected or disconnected.
+	 */
+	if (state.cs_addr > ao->ao_pblkaddr + ao->ao_pblklen) {
+		rv = AE_BAD_ADDRESS;
+		goto out;
+	}
+
+	if (cs[i].cs_method != 0) {
+		rv = AE_ALREADY_EXISTS;
+		goto out;
+	}
+
+#ifndef ACPICPU_ENABLE_C3
+	/*
+	 * XXX: The local APIC timer (as well as TSC) is typically
+	 *	stopped in C3, causing the timer interrupt to fire
+	 *	haphazardly, depending on how long the system slept.
+	 *	For now, we disable the C3 state unconditionally.
+	 */
+	if (i == ACPI_STATE_C3) {
+		sc->sc_flags |= ACPICPU_FLAG_C_NOC3;
+		goto out;
+	}
+#endif
+
+	cs[i].cs_addr = state.cs_addr;
+	cs[i].cs_power = state.cs_power;
+	cs[i].cs_latency = state.cs_latency;
+	cs[i].cs_method = state.cs_method;
+
+out:
+	if (ACPI_FAILURE(rv))
+		aprint_verbose_dev(sc->sc_dev,
+		    "invalid _CST: %s\n", AcpiFormatException(rv));
+
+	return rv;
+}
+
+static void
+acpicpu_cstate_cst_bios(void)
+{
+	const uint8_t val = AcpiGbl_FADT.CstControl;
+	const uint32_t addr = AcpiGbl_FADT.SmiCommand;
+
+	if (addr == 0)
+		return;
+
+	(void)AcpiOsWritePort(addr, val, 8);
+}
+
+static ACPI_STATUS
+acpicpu_cstate_csd(ACPI_HANDLE hdl, struct acpicpu_csd *csd)
+{
+	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;
+	}
+
+	csd->csd_domain = elm[2].Integer.Value;
+	csd->csd_coord  = elm[3].Integer.Value;
+	csd->csd_ncpu   = elm[4].Integer.Value;
+	csd->csd_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)
+{
+	struct acpicpu_cstate *cs = sc->sc_cstate;
+
+	(void)memset(cs, 0, sizeof(*cs) * ACPI_C_STATE_COUNT);
+
+	/*
+	 * All x86 processors should support C1 (a.k.a. HALT).
+	 */
+	if ((AcpiGbl_FADT.Flags & ACPI_FADT_C1_SUPPORTED) != 0)
+		cs[ACPI_STATE_C1].cs_method = ACPICPU_C_STATE_HALT;
+
+	if ((acpicpu_md_cpus_running() > 1) &&
+	    (AcpiGbl_FADT.Flags & ACPI_FADT_C2_MP_SUPPORTED) == 0)
+		return;
+
+	cs[ACPI_STATE_C2].cs_method = ACPICPU_C_STATE_SYSIO;
+	cs[ACPI_STATE_C3].cs_method = ACPICPU_C_STATE_SYSIO;
+
+	cs[ACPI_STATE_C2].cs_latency = AcpiGbl_FADT.C2Latency;
+	cs[ACPI_STATE_C3].cs_latency = AcpiGbl_FADT.C3Latency;
+
+	cs[ACPI_STATE_C2].cs_addr = sc->sc_object.ao_pblkaddr + 4;
+	cs[ACPI_STATE_C3].cs_addr = sc->sc_object.ao_pblkaddr + 5;
+
+	/*
+	 * The P_BLK length should always be 6. If it
+	 * is not, reduce functionality accordingly.
+	 * Sanity check also FADT's latency levels.
+	 */
+	if (sc->sc_object.ao_pblklen < 5)
+		cs[ACPI_STATE_C2].cs_method = 0;
+
+	if (sc->sc_object.ao_pblklen < 6)
+		cs[ACPI_STATE_C3].cs_method = 0;
+
+	if (AcpiGbl_FADT.C2Latency > ACPICPU_C_C2_LATENCY_MAX)
+		cs[ACPI_STATE_C2].cs_method = 0;
+
+	if (AcpiGbl_FADT.C3Latency > ACPICPU_C_C3_LATENCY_MAX)
+		cs[ACPI_STATE_C3].cs_method = 0;
+
+#ifndef ACPICPU_ENABLE_C3
+	cs[ACPI_STATE_C3].cs_method = 0;
+	sc->sc_flags |= ACPICPU_FLAG_C_NOC3;	/* XXX. */
+#endif
+}
+
+static void
+acpicpu_cstate_quirks(struct acpicpu_softc *sc)
+{
+	const uint32_t reg = AcpiGbl_FADT.Pm2ControlBlock;
+	const uint32_t len = AcpiGbl_FADT.Pm2ControlLength;
+	struct pci_attach_args pa;
+
+	/*
+	 * Check bus master arbitration.
+	 */
+	if (reg != 0 && len != 0)
+		sc->sc_flags |= ACPICPU_FLAG_C_ARB;
+	else {
+		/*
+		 * Disable C3 entirely if WBINVD is not present.
+		 */
+		if ((AcpiGbl_FADT.Flags & ACPI_FADT_WBINVD) == 0)
+			sc->sc_flags |= ACPICPU_FLAG_C_NOC3;
+		else {
+			/*
+			 * If WBINVD is present, but not functioning
+			 * properly according to FADT, flush all CPU
+			 * caches before entering the C3 state.
+			 */
+			if ((AcpiGbl_FADT.Flags & ACPI_FADT_WBINVD_FLUSH) == 0)
+				sc->sc_flags &= ~ACPICPU_FLAG_C_BM;
+		}
+	}
+
+	/*
+	 * There are several erratums for PIIX4.
+	 */
+	if (pci_find_device(&pa, acpicpu_cstate_quirks_piix4) != 0)
+		sc->sc_flags |= ACPICPU_FLAG_C_NOC3;
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C_NOC3) != 0)
+		sc->sc_cstate[ACPI_STATE_C3].cs_method = 0;
+}
+
+static int
+acpicpu_cstate_quirks_piix4(struct pci_attach_args *pa)
+{
+
+	if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL)
+		return 0;
+
+	if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82371AB_ISA ||
+	    PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82440MX_PMC)
+		return 1;
+
+	return 0;
+}
+
+static int
+acpicpu_cstate_latency(struct acpicpu_softc *sc)
+{
+	static const uint32_t cs_factor = 3;
+	struct acpicpu_cstate *cs;
+	int i;
+
+	for (i = ACPI_STATE_C3; i > 0; i--) {
+
+		cs = &sc->sc_cstate[i];
+
+		if (__predict_false(cs->cs_method == 0))
+			continue;
+
+		/*
+		 * Choose a state if we have previously slept
+		 * longer than the worst case latency of the
+		 * state times an arbitrary multiplier.
+		 */
+		if (sc->sc_sleep > cs->cs_latency * cs_factor)
+			return i;
+	}
+
+	return ACPI_STATE_C1;
+}
+
+/*
+ * The main idle loop.
+ */
+void
+acpicpu_cstate_idle(void)
+{
+        struct cpu_info *ci = curcpu();
+	struct acpicpu_softc *sc;
+	int state;
+
+	if (__predict_false(ci->ci_want_resched) != 0)
+		return;
+
+	KASSERT(acpicpu_sc != NULL);
+	KASSERT(ci->ci_cpuid < maxcpus);
+	KASSERT(ci->ci_ilevel == IPL_NONE);
+
+	if (__predict_false(acpi_suspended != 0)) {
+		acpicpu_md_idle_enter(0, 0);
+		return;
+	}
+
+	sc = acpicpu_sc[ci->ci_cpuid];
+
+	/*
+	 * If all CPUs do not have an ACPI counterpart,
+	 * the softc may be NULL. In this case use C1.
+	 */
+	if (__predict_false(sc == NULL)) {
+		acpicpu_md_idle_enter(0, 0);
+		return;
+	}
+
+	acpi_md_OsDisableInterrupt();
+	state = acpicpu_cstate_latency(sc);
+
+	/*
+	 * Check for bus master activity. Note that
+	 * particularly usb(4) causes high activity,
+	 * which may prevent the use of C3 states.
+	 */
+	if (acpicpu_cstate_bm_check() != false) {
+
+		state--;
+
+		if (__predict_false(sc->sc_cstate[state].cs_method == 0))
+			state = ACPI_STATE_C1;
+	}
+
+	KASSERT(state != ACPI_STATE_C0);
+
+	if (state != ACPI_STATE_C3) {
+		acpicpu_cstate_idle_enter(sc, state);
+		return;
+	}
+
+	/*
+	 * On all recent (Intel) CPUs caches are shared
+	 * by CPUs and bus master control is required to
+	 * keep these coherent while in C3. Flushing the
+	 * CPU caches is only the last resort.
+	 */
+	if ((sc->sc_flags & ACPICPU_FLAG_C_BM) == 0)
+		ACPI_FLUSH_CPU_CACHE();
+
+	/*
+	 * Some chipsets may not return back to C0
+	 * from C3 if bus master wake is not enabled.
+	 */
+	if ((sc->sc_flags & ACPICPU_FLAG_C_BM) != 0)
+		(void)AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_RLD, 1);
+
+	/*
+	 * It may be necessary to disable bus master arbitration
+	 * to ensure that bus master cycles do not occur while
+	 * sleeping in C3 (see ACPI 4.0, section 8.1.4).
+	 */
+	if ((sc->sc_flags & ACPICPU_FLAG_C_ARB) != 0)
+		(void)AcpiWriteBitRegister(ACPI_BITREG_ARB_DISABLE, 1);
+
+	acpicpu_cstate_idle_enter(sc, state);
+
+	/*
+	 * Disable bus master wake and re-enable the arbiter.
+	 */
+	if ((sc->sc_flags & ACPICPU_FLAG_C_BM) != 0)
+		(void)AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_RLD, 0);
+
+	if ((sc->sc_flags & ACPICPU_FLAG_C_ARB) != 0)
+		(void)AcpiWriteBitRegister(ACPI_BITREG_ARB_DISABLE, 0);
+}
+
+static void
+acpicpu_cstate_idle_enter(struct acpicpu_softc *sc, int state)
+{
+	struct acpicpu_cstate *cs = &sc->sc_cstate[state];
+	uint32_t end, start, val;
+
+	start = acpitimer_read_safe(NULL);
+
+	switch (cs->cs_method) {
+
+	case ACPICPU_C_STATE_FFH:
+	case ACPICPU_C_STATE_HALT:
+		acpicpu_md_idle_enter(cs->cs_method, state);
+		break;
+
+	case ACPICPU_C_STATE_SYSIO:
+		(void)AcpiOsReadPort(cs->cs_addr, &val, 8);
+		break;
+
+	default:
+		acpicpu_md_idle_enter(0, 0);
+		break;
+	}
+
+	cs->cs_stat++;
+
+	end = acpitimer_read_safe(NULL);
+	sc->sc_sleep = hztoms(acpitimer_delta(end, start)) * 1000;
+
+	acpi_md_OsEnableInterrupt();
+}
+
+static bool
+acpicpu_cstate_bm_check(void)
+{
+	uint32_t val = 0;
+	ACPI_STATUS rv;
+
+	rv = AcpiReadBitRegister(ACPI_BITREG_BUS_MASTER_STATUS, &val);
+
+	if (ACPI_FAILURE(rv) || val == 0)
+		return false;
+
+	(void)AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_STATUS, 1);
+
+	return true;
+}

Reply via email to