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)

Reply via email to