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