Module Name: src
Committed By: jmcneill
Date: Mon Dec 30 19:17:21 UTC 2024
Modified Files:
src/sys/arch/aarch64/include: cpu.h
src/sys/arch/arm/acpi: cpu_acpi.c
Log Message:
arm64: Enable support for low power idle CPU states on ACPI platforms.
The ACPI CPU driver parses the _LPI package on each CPU and builds a
table of supported low power states. A custom cpu_idle() implementation
is registered that uses the time previously spent idle to select an
entry method for low power on the next idle entry.
A boot option, "nolpi", can be used to ignore _LPI and use the normal
WFI idle method.
This decreases the battery discharge rate on my Snapdragon X1E laptop from
~17W to ~10W when idle.
To generate a diff of this commit:
cvs rdiff -u -r1.52 -r1.53 src/sys/arch/aarch64/include/cpu.h
cvs rdiff -u -r1.16 -r1.17 src/sys/arch/arm/acpi/cpu_acpi.c
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/aarch64/include/cpu.h
diff -u src/sys/arch/aarch64/include/cpu.h:1.52 src/sys/arch/aarch64/include/cpu.h:1.53
--- src/sys/arch/aarch64/include/cpu.h:1.52 Tue Dec 10 11:27:28 2024
+++ src/sys/arch/aarch64/include/cpu.h Mon Dec 30 19:17:21 2024
@@ -1,4 +1,4 @@
-/* $NetBSD: cpu.h,v 1.52 2024/12/10 11:27:28 jmcneill Exp $ */
+/* $NetBSD: cpu.h,v 1.53 2024/12/30 19:17:21 jmcneill Exp $ */
/*-
* Copyright (c) 2014, 2020 The NetBSD Foundation, Inc.
@@ -99,6 +99,21 @@ struct aarch64_cache_info {
struct aarch64_cache_unit dcache;
};
+struct aarch64_low_power_idle {
+ uint32_t min_res; /* minimum residency */
+ uint32_t wakeup_latency; /* worst case */
+ uint32_t save_restore_flags;
+#define LPI_SAVE_RESTORE_CORE __BIT(0)
+#define LPI_SAVE_RESTORE_TRACE __BIT(1)
+#define LPI_SAVE_RESTORE_GICR __BIT(2)
+#define LPI_SAVE_RESTORE_GICD __BIT(3)
+ uint32_t reg_addr;
+#define LPI_REG_ADDR_WFI 0xffffffff
+
+ char *name;
+ struct evcnt events;
+};
+
struct cpu_info {
struct cpu_data ci_data;
device_t ci_dev;
@@ -166,6 +181,11 @@ struct cpu_info {
/* ACPI */
uint32_t ci_acpiid; /* ACPI Processor Unique ID */
+ /* ACPI low power idle */
+ uint32_t ci_nlpi;
+ struct aarch64_low_power_idle *ci_lpi;
+ uint64_t ci_last_idle;
+
/* cached system registers */
uint64_t ci_sctlr_el1;
uint64_t ci_sctlr_el2;
Index: src/sys/arch/arm/acpi/cpu_acpi.c
diff -u src/sys/arch/arm/acpi/cpu_acpi.c:1.16 src/sys/arch/arm/acpi/cpu_acpi.c:1.17
--- src/sys/arch/arm/acpi/cpu_acpi.c:1.16 Sun Jun 30 17:58:08 2024
+++ src/sys/arch/arm/acpi/cpu_acpi.c Mon Dec 30 19:17:21 2024
@@ -1,4 +1,4 @@
-/* $NetBSD: cpu_acpi.c,v 1.16 2024/06/30 17:58:08 jmcneill Exp $ */
+/* $NetBSD: cpu_acpi.c,v 1.17 2024/12/30 19:17:21 jmcneill Exp $ */
/*-
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -33,7 +33,7 @@
#include "opt_multiprocessor.h"
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: cpu_acpi.c,v 1.16 2024/06/30 17:58:08 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: cpu_acpi.c,v 1.17 2024/12/30 19:17:21 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
@@ -41,11 +41,13 @@ __KERNEL_RCSID(0, "$NetBSD: cpu_acpi.c,v
#include <sys/device.h>
#include <sys/interrupt.h>
#include <sys/kcpuset.h>
+#include <sys/kmem.h>
#include <sys/reboot.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_srat.h>
+#include <external/bsd/acpica/dist/include/amlresrc.h>
#include <arm/armreg.h>
#include <arm/cpu.h>
@@ -55,6 +57,8 @@ __KERNEL_RCSID(0, "$NetBSD: cpu_acpi.c,v
#include <arm/arm/psci.h>
+#define LPI_IDLE_FACTOR 3
+
#if NTPROF > 0
#include <dev/tprof/tprof_armv8.h>
#endif
@@ -62,6 +66,9 @@ __KERNEL_RCSID(0, "$NetBSD: cpu_acpi.c,v
static int cpu_acpi_match(device_t, cfdata_t, void *);
static void cpu_acpi_attach(device_t, device_t, void *);
+static void cpu_acpi_probe_lpi(device_t, struct cpu_info *ci);
+void cpu_acpi_lpi_idle(void);
+
#if NTPROF > 0
static void cpu_acpi_tprof_init(device_t);
#endif
@@ -147,12 +154,183 @@ cpu_acpi_attach(device_t parent, device_
/* Attach the CPU */
cpu_attach(self, mpidr);
+ /* Probe for low-power idle states. */
+ cpu_acpi_probe_lpi(self, ci);
+
#if NTPROF > 0
if (cpu_mpidr_aff_read() == mpidr && armv8_pmu_detect())
config_interrupts(self, cpu_acpi_tprof_init);
#endif
}
+static void
+cpu_acpi_probe_lpi(device_t dev, struct cpu_info *ci)
+{
+ ACPI_HANDLE hdl;
+ ACPI_BUFFER buf;
+ ACPI_OBJECT *obj, *lpi;
+ ACPI_STATUS rv;
+ uint32_t levelid;
+ uint32_t numlpi;
+ uint32_t n;
+ int enable_lpi;
+
+ if (get_bootconf_option(boot_args, "nolpi",
+ BOOTOPT_TYPE_BOOLEAN, &enable_lpi) &&
+ !enable_lpi) {
+ return;
+ }
+
+ hdl = acpi_match_cpu_info(ci);
+ if (hdl == NULL) {
+ return;
+ }
+ rv = AcpiGetHandle(hdl, "_LPI", &hdl);
+ if (ACPI_FAILURE(rv)) {
+ return;
+ }
+ rv = acpi_eval_struct(hdl, NULL, &buf);
+ if (ACPI_FAILURE(rv)) {
+ return;
+ }
+
+ obj = buf.Pointer;
+ if (obj->Type != ACPI_TYPE_PACKAGE ||
+ obj->Package.Count < 3 ||
+ obj->Package.Elements[1].Type != ACPI_TYPE_INTEGER ||
+ obj->Package.Elements[2].Type != ACPI_TYPE_INTEGER) {
+ goto out;
+ }
+ levelid = obj->Package.Elements[1].Integer.Value;
+ if (levelid != 0) {
+ /* We depend on platform coordination for now. */
+ goto out;
+ }
+ numlpi = obj->Package.Elements[2].Integer.Value;
+ if (obj->Package.Count < 3 + numlpi || numlpi == 0) {
+ goto out;
+ }
+ ci->ci_lpi = kmem_zalloc(sizeof(*ci->ci_lpi) * numlpi, KM_SLEEP);
+ for (n = 0; n < numlpi; n++) {
+ lpi = &obj->Package.Elements[3 + n];
+ if (lpi->Type != ACPI_TYPE_PACKAGE ||
+ lpi->Package.Count < 10 ||
+ lpi->Package.Elements[0].Type != ACPI_TYPE_INTEGER ||
+ lpi->Package.Elements[1].Type != ACPI_TYPE_INTEGER ||
+ lpi->Package.Elements[2].Type != ACPI_TYPE_INTEGER ||
+ lpi->Package.Elements[3].Type != ACPI_TYPE_INTEGER ||
+ !(lpi->Package.Elements[6].Type == ACPI_TYPE_BUFFER ||
+ lpi->Package.Elements[6].Type == ACPI_TYPE_INTEGER)) {
+ continue;
+ }
+
+ if ((lpi->Package.Elements[2].Integer.Value & 1) == 0) {
+ /* LPI state is not enabled */
+ continue;
+ }
+
+ ci->ci_lpi[ci->ci_nlpi].min_res
+ = lpi->Package.Elements[0].Integer.Value;
+ ci->ci_lpi[ci->ci_nlpi].wakeup_latency =
+ lpi->Package.Elements[1].Integer.Value;
+ ci->ci_lpi[ci->ci_nlpi].save_restore_flags =
+ lpi->Package.Elements[3].Integer.Value;
+ if (ci->ci_lpi[ci->ci_nlpi].save_restore_flags != 0) {
+ /* Not implemented yet */
+ continue;
+ }
+ if (lpi->Package.Elements[6].Type == ACPI_TYPE_INTEGER) {
+ ci->ci_lpi[ci->ci_nlpi].reg_addr =
+ lpi->Package.Elements[6].Integer.Value;
+ } else {
+ ACPI_GENERIC_ADDRESS addr;
+
+ KASSERT(lpi->Package.Elements[6].Type ==
+ ACPI_TYPE_BUFFER);
+
+ if (lpi->Package.Elements[6].Buffer.Length <
+ sizeof(AML_RESOURCE_GENERIC_REGISTER)) {
+ continue;
+ }
+ memcpy(&addr, lpi->Package.Elements[6].Buffer.Pointer +
+ sizeof(AML_RESOURCE_LARGE_HEADER), sizeof(addr));
+ ci->ci_lpi[ci->ci_nlpi].reg_addr = addr.Address;
+ }
+
+ if (lpi->Package.Elements[9].Type == ACPI_TYPE_STRING) {
+ ci->ci_lpi[ci->ci_nlpi].name =
+ kmem_asprintf("LPI state %s",
+ lpi->Package.Elements[9].String.Pointer);
+ } else {
+ ci->ci_lpi[ci->ci_nlpi].name =
+ kmem_asprintf("LPI state %u", n + 1);
+ }
+
+ aprint_verbose_dev(ci->ci_dev,
+ "%s: min res %u, wakeup latency %u, flags %#x, "
+ "register %#x\n",
+ ci->ci_lpi[ci->ci_nlpi].name,
+ ci->ci_lpi[ci->ci_nlpi].min_res,
+ ci->ci_lpi[ci->ci_nlpi].wakeup_latency,
+ ci->ci_lpi[ci->ci_nlpi].save_restore_flags,
+ ci->ci_lpi[ci->ci_nlpi].reg_addr);
+
+ evcnt_attach_dynamic(&ci->ci_lpi[ci->ci_nlpi].events,
+ EVCNT_TYPE_MISC, NULL, ci->ci_cpuname,
+ ci->ci_lpi[ci->ci_nlpi].name);
+
+ ci->ci_nlpi++;
+ }
+
+ if (ci->ci_nlpi > 0) {
+ extern void (*arm_cpu_idle)(void);
+ arm_cpu_idle = cpu_acpi_lpi_idle;
+ }
+
+out:
+ ACPI_FREE(buf.Pointer);
+}
+
+static inline void
+cpu_acpi_idle(uint32_t addr)
+{
+ if (addr == LPI_REG_ADDR_WFI) {
+ asm volatile("dsb sy; wfi");
+ } else {
+ psci_cpu_suspend(addr);
+ }
+}
+
+void
+cpu_acpi_lpi_idle(void)
+{
+ struct cpu_info *ci = curcpu();
+ struct timeval start, end;
+ int n;
+
+ DISABLE_INTERRUPT();
+
+ microuptime(&start);
+ for (n = ci->ci_nlpi - 1; n >= 0; n--) {
+ if (ci->ci_last_idle >
+ LPI_IDLE_FACTOR * ci->ci_lpi[n].min_res) {
+ cpu_acpi_idle(ci->ci_lpi[n].reg_addr);
+ ci->ci_lpi[n].events.ev_count++;
+ break;
+ }
+ }
+ if (n == -1) {
+ /* Nothing in _LPI, let's just WFI. */
+ cpu_acpi_idle(LPI_REG_ADDR_WFI);
+ }
+ microuptime(&end);
+ timersub(&end, &start, &end);
+
+ ci->ci_last_idle = end.tv_sec * 1000000 + end.tv_usec;
+
+ ENABLE_INTERRUPT();
+}
+
#if NTPROF > 0
static struct cpu_info *
cpu_acpi_find_processor(UINT32 uid)