Wednesday, May 01, 2024

Re: >10W idle power usage on framework laptop 12th gen 13inch

On Tue, 30 Apr 2024 18:07:50 +0200,
"Nathaniel Griswold" <nwg@fastmail.com> wrote:
>
> What could be taking so much power? CPUs are idling.

You may try this patch.

It is extention of powersave mode which disabling / enabling CPUs.

It should degradate to single-core mode, but it may contains bugs :)

Right now I'm writing this email from kernel with this patch, on this
mode. Well. It had boot and seems to work.

Anyway, I not sure that I'll run it for long, it had feeling that idle
system is overload, but I'll try.

Regarding estimated life time:

Battery state: high, 66% remaining, 152 minutes life estimate
AC adapter state: not connected
Performance adjustment mode: powersaving (400 MHz)

which is like 2x from usual numbers.

diff --git sys/kern/sched_bsd.c sys/kern/sched_bsd.c
index 25b221c1ee2..8941675a7f9 100644
--- sys/kern/sched_bsd.c
+++ sys/kern/sched_bsd.c
@@ -65,8 +65,11 @@ void update_loadavg(void *);
void schedcpu(void *);
uint32_t decay_aftersleep(uint32_t, uint32_t);

+extern struct cpuset sched_all_cpus;
extern struct cpuset sched_idle_cpus;

+extern int sched_smt;
+
/*
* constants for averages over 1, 5, and 15 minutes when sampling at
* 5 second intervals.
@@ -573,6 +576,7 @@ void (*cpu_setperf)(int);
#define PERFPOL_MANUAL 0
#define PERFPOL_AUTO 1
#define PERFPOL_HIGH 2
+#define PERFPOL_POWERSAVING 4
int perflevel = 100;
int perfpolicy = PERFPOL_AUTO;

@@ -583,7 +587,9 @@ int perfpolicy = PERFPOL_AUTO;
#include <sys/sysctl.h>

void setperf_auto(void *);
+void setperf_powersaving(void *);
struct timeout setperf_to = TIMEOUT_INITIALIZER(setperf_auto, NULL);
+struct timeout setperf_to_powersaving = TIMEOUT_INITIALIZER(setperf_powersaving, NULL);
extern int hw_power;

void
@@ -653,6 +659,77 @@ faster:
timeout_add_msec(&setperf_to, 100);
}

+void
+setperf_powersaving(void *v)
+{
+ static uint64_t *idleticks, *totalticks;
+ static int downbeats;
+ int i, j = 0;
+ int speedup = 0;
+ CPU_INFO_ITERATOR cii;
+ struct cpu_info *ci, *firstidle = NULL, *lastonline = NULL;
+ uint64_t idle, total, allidle = 0, alltotal = 0;
+
+ if (perfpolicy != PERFPOL_POWERSAVING)
+ return;
+
+ if (cpu_setperf == NULL)
+ return;
+
+ if (!idleticks)
+ if (!(idleticks = mallocarray(ncpusfound, sizeof(*idleticks),
+ M_DEVBUF, M_NOWAIT | M_ZERO)))
+ return;
+ if (!totalticks)
+ if (!(totalticks = mallocarray(ncpusfound, sizeof(*totalticks),
+ M_DEVBUF, M_NOWAIT | M_ZERO))) {
+ free(idleticks, M_DEVBUF,
+ sizeof(*idleticks) * ncpusfound);
+ return;
+ }
+ CPU_INFO_FOREACH(cii, ci) {
+ if (!cpu_is_online(ci)) {
+ if (!firstidle && (sched_smt || ci->ci_smt_id == 0))
+ firstidle = ci;
+ continue;
+ }
+ total = 0;
+ for (i = 0; i < CPUSTATES; i++) {
+ total += ci->ci_schedstate.spc_cp_time[i];
+ }
+ total -= totalticks[j];
+ idle = ci->ci_schedstate.spc_cp_time[CP_IDLE] - idleticks[j];
+ if (idle < total / 3)
+ speedup = 1;
+ alltotal += total;
+ allidle += idle;
+ idleticks[j] += idle;
+ totalticks[j] += total;
+ if (j++)
+ lastonline = ci;
+ }
+ if (allidle < alltotal / 3)
+ speedup = 1;
+ if (speedup)
+ /* twice as long here because we check every 200ms */
+ downbeats = 1;
+
+ if (speedup && perflevel != 100) {
+ perflevel = 100;
+ cpu_setperf(perflevel);
+ } else if (speedup && firstidle)
+ cpuset_add(&sched_all_cpus, firstidle);
+ else if (!speedup && perflevel != 0 && --downbeats <= 0) {
+ perflevel = 0;
+ cpu_setperf(perflevel);
+ } else if (!speedup && lastonline)
+ cpuset_del(&sched_all_cpus, lastonline);
+
+ /* every 200ms to have a better resolution of the load */
+ timeout_add_msec(&setperf_to_powersaving, 200);
+}
+
+
int
sysctl_hwsetperf(void *oldp, size_t *oldlenp, void *newp, size_t newlen)
{
@@ -691,6 +768,9 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen)
case PERFPOL_AUTO:
strlcpy(policy, "auto", sizeof(policy));
break;
+ case PERFPOL_POWERSAVING:
+ strlcpy(policy, "powersaving", sizeof(policy));
+ break;
case PERFPOL_HIGH:
strlcpy(policy, "high", sizeof(policy));
break;
@@ -709,6 +789,8 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen)
perfpolicy = PERFPOL_MANUAL;
else if (strcmp(policy, "auto") == 0)
perfpolicy = PERFPOL_AUTO;
+ else if (strcmp(policy, "powersaving") == 0)
+ perfpolicy = PERFPOL_POWERSAVING;
else if (strcmp(policy, "high") == 0)
perfpolicy = PERFPOL_HIGH;
else
@@ -716,6 +798,8 @@ sysctl_hwperfpolicy(void *oldp, size_t *oldlenp, void *newp, size_t newlen)

if (perfpolicy == PERFPOL_AUTO) {
timeout_add_msec(&setperf_to, 200);
+ } else if (perfpolicy == PERFPOL_POWERSAVING) {
+ timeout_add_msec(&setperf_to_powersaving, 200);
} else if (perfpolicy == PERFPOL_HIGH) {
perflevel = 100;
cpu_setperf(perflevel);
diff --git usr.sbin/apmd/apm-proto.h usr.sbin/apmd/apm-proto.h
index 867d0afbd70..166618e996f 100644
--- usr.sbin/apmd/apm-proto.h
+++ usr.sbin/apmd/apm-proto.h
@@ -38,6 +38,7 @@ enum apm_action {
SETPERF_LOW,
SETPERF_HIGH,
SETPERF_AUTO,
+ SETPERF_POWERSAVING,
};

enum apm_state {
@@ -51,6 +52,7 @@ enum apm_perfmode {
PERF_NONE = -1,
PERF_MANUAL,
PERF_AUTO,
+ PERF_POWERSAVING,
};

struct apm_command {
diff --git usr.sbin/apmd/apmd.8 usr.sbin/apmd/apmd.8
index 7c02bb10c8a..4647ed3e126 100644
--- usr.sbin/apmd/apmd.8
+++ usr.sbin/apmd/apmd.8
@@ -34,7 +34,7 @@
.Nd Advanced Power Management daemon
.Sh SYNOPSIS
.Nm apmd
-.Op Fl AadHLs
+.Op Fl AadHLPs
.Op Fl f Ar devname
.Op Fl S Ar sockname
.Op Fl t Ar seconds
@@ -94,6 +94,9 @@ Start
in manual performance adjustment mode, initialising
.Va hw.setperf
to 0.
+.It Fl P
+.Nm
+in auto mode use powersaving policy when on battery.
.It Fl S Ar sockname
Specify an alternate socket name,
.Ar sockname .
diff --git usr.sbin/apmd/apmd.c usr.sbin/apmd/apmd.c
index f29d5c9a081..b6d8c5e8aa6 100644
--- usr.sbin/apmd/apmd.c
+++ usr.sbin/apmd/apmd.c
@@ -58,6 +58,7 @@
#define AUTO_HIBERNATE 2

int debug = 0;
+int use_powersaving = 0;

extern char *__progname;

@@ -100,7 +101,7 @@ void
usage(void)
{
fprintf(stderr,
- "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds] "
+ "usage: %s [-AadHLPs] [-f devname] [-S sockname] [-t seconds] "
"[-Z percent] [-z percent]\n", __progname);
exit(1);
}
@@ -139,6 +140,10 @@ power_status(int fd, int force, struct apm_power_info *pinfo)
static struct apm_power_info last;
int acon = 0, priority = LOG_NOTICE;

+ int perfpol_mib[] = { CTL_HW, HW_PERFPOLICY };
+ char perfpol[32];
+ size_t perfpol_sz = sizeof(perfpol);
+
if (fd == -1) {
if (pinfo) {
bstate.battery_state = 255;
@@ -151,11 +156,19 @@ power_status(int fd, int force, struct apm_power_info *pinfo)
return 0;
}

+ if (sysctl(perfpol_mib, 2, perfpol, &perfpol_sz, NULL, 0) == -1)
+ logmsg(LOG_INFO, "cannot read hw.perfpolicy");
+
if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
/* various conditions under which we report status: something changed
* enough since last report, or asked to force a print */
- if (bstate.ac_state == APM_AC_ON)
+ if (bstate.ac_state == APM_AC_ON) {
acon = 1;
+ if(use_powersaving && strcmp(perfpol, "powersaving") == 0)
+ setperfpolicy("auto");
+ } else if(use_powersaving && strcmp(perfpol, "auto") == 0)
+ setperfpolicy("powersaving");
+
if (bstate.battery_state == APM_BATT_CRITICAL &&
bstate.battery_state != last.battery_state)
priority = LOG_EMERG;
@@ -282,6 +295,11 @@ handle_client(int sock_fd, int ctl_fd)
logmsg(LOG_NOTICE, "setting hw.perfpolicy to high");
setperfpolicy("high");
break;
+ case SETPERF_POWERSAVING:
+ reply.newstate = NORMAL;
+ logmsg(LOG_NOTICE, "setting hw.perfpolicy to powersaving");
+ setperfpolicy("powersaving");
+ break;
case SETPERF_AUTO:
reply.newstate = NORMAL;
logmsg(LOG_NOTICE, "setting hw.perfpolicy to auto");
@@ -299,8 +317,10 @@ handle_client(int sock_fd, int ctl_fd)
if (strcmp(perfpol, "manual") == 0 ||
strcmp(perfpol, "high") == 0) {
reply.perfmode = PERF_MANUAL;
- } else if (strcmp(perfpol, "auto") == 0)
+ } else if (strcmp(perfpol, "auto") == 0) {
reply.perfmode = PERF_AUTO;
+ } else if (strcmp(perfpol, "powersaving") == 0)
+ reply.perfmode = PERF_POWERSAVING;
}

if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) == -1) {
@@ -423,7 +443,7 @@ main(int argc, char *argv[])
struct kevent ev[2];
int doperf = PERF_NONE;

- while ((ch = getopt(argc, argv, "aACdHLsf:t:S:z:Z:")) != -1)
+ while ((ch = getopt(argc, argv, "aACdHLPsf:t:S:z:Z:")) != -1)
switch(ch) {
case 'a':
noacsleep = 1;
@@ -459,6 +479,9 @@ main(int argc, char *argv[])
doperf = PERF_MANUAL;
setperfpolicy("low");
break;
+ case 'P':
+ use_powersaving = 1;
+ break;
case 'H':
if (doperf != PERF_NONE)
usage();
diff --git usr.sbin/apmd/apmsubr.c usr.sbin/apmd/apmsubr.c
index 6504fe823bb..a1dbcfb2c61 100644
--- usr.sbin/apmd/apmsubr.c
+++ usr.sbin/apmd/apmsubr.c
@@ -79,6 +79,8 @@ perf_mode(int mode)
return "manual";
case PERF_AUTO:
return "auto";
+ case PERF_POWERSAVING:
+ return "powersaving";
default:
return "invalid";
}


--
wbr, Kirill

No comments:

Post a Comment