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)