commit 1c7821ae65f3d3d3a7871d3d57d033834649e8d5
parent ac637e2e2d1466d87b5c0e784e4a186a25e87532
Author: parazyd <parazyd@dyne.org>
Date: Thu, 20 Oct 2022 23:26:48 +0200
Add perf-profile for performance profile changes.
Diffstat:
M | Makefile | | | 2 | +- |
M | common.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | common.h | | | 6 | ++++++ |
A | perf-profile.c | | | 324 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 390 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -8,7 +8,7 @@ CFLAGS = -std=c99 -pedantic -Wall -Wextra -Werror -Os
LDFLAGS = -s
# static suid binaries
-SUID_BIN = brightness charge-thresholds
+SUID_BIN = brightness charge-thresholds perf-profile
HDR = arg.h common.h
SRC = common.c
diff --git a/common.c b/common.c
@@ -20,3 +20,62 @@ void die(const char *fmt, ...)
exit(1);
}
+
+void reverse(char *s)
+{
+ int i, j;
+ char c;
+
+ for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
+ c = s[i];
+ s[i] = s[j];
+ s[j] = c;
+ }
+}
+
+void itoa(int n, char *s)
+{
+ int i, sign;
+
+ if ((sign = n) < 0)
+ n = -n;
+
+ i = 0;
+ do {
+ s[i++] = n % 10 + '0';
+ } while ((n /= 10) > 0);
+
+ if (sign < 0)
+ s[i++] = '-';
+
+ s[i] = '\0';
+ reverse(s);
+}
+
+int write_oneshot_str(const char *path, const char *text)
+{
+ FILE *fd;
+
+ if ((fd = fopen(path, "w")) == NULL)
+ return 1;
+
+ fprintf(fd, text);
+ fclose(fd);
+
+ fprintf(stderr, "Wrote into %s: %s\n", path, text);
+ return 0;
+}
+
+int write_oneshot_int(const char *path, int value)
+{
+ FILE *fd;
+
+ if ((fd = fopen(path, "w")) == NULL)
+ return 1;
+
+ fprintf(fd, "%d", value);
+ fclose(fd);
+
+ fprintf(stderr, "Wrote into %s: %d\n", path, value);
+ return 0;
+}
diff --git a/common.h b/common.h
@@ -1,6 +1,12 @@
#ifndef __COMMON_H__
#define __COMMON_H__
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
void die(const char *fmt, ...);
+void itoa(int n, char *s);
+
+int write_oneshot_str(const char *path, const char *text);
+int write_oneshot_int(const char *path, int value);
#endif
diff --git a/perf-profile.c b/perf-profile.c
@@ -0,0 +1,324 @@
+/* suid tool for setting acpi performance profile
+ * GPL-3
+ * https://www.kernel.org/doc/html/latest/userspace-api/sysfs-platform_profile.html
+ * https://mjmwired.net/kernel/Documentation/ABI/testing/sysfs-platform_profile
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/sysinfo.h>
+
+#include "arg.h"
+#include "common.h"
+
+static const char *ACPI_PLPR_PATH = "/sys/firmware/acpi/platform_profile";
+static const char *S76_POW_PROF = "/run/system76-power.profile";
+
+static const char *DIRTY_WRITEBACK = "/proc/sys/vm/dirty_writeback_centisecs";
+static const char *DIRTY_EXPIRE = "/proc/sys/vm/dirty_expire_centisecs";
+
+static const char *SYS_CPU_PREFIX = "/sys/devices/system/cpu/cpu";
+
+static const char *PSTATE_DYNBOOST = "/sys/devices/system/cpu/intel_pstate/hwp_dynamic_boost";
+static const char *PSTATE_MAX_PERF = "/sys/devices/system/cpu/intel_pstate/max_perf_pct";
+static const char *PSTATE_MIN_PERF = "/sys/devices/system/cpu/intel_pstate/min_perf_pct";
+static const char *PSTATE_NO_TURBO = "/sys/devices/system/cpu/intel_pstate/no_turbo";
+
+char *argv0;
+
+enum Profile {
+ LOWPOWER,
+ BALANCED,
+ PERFORMANCE,
+};
+
+static void usage(void)
+{
+ die("usage: %s [-v] low-power|balanced|performance", argv0);
+}
+
+static void set_max_lost_work(int secs)
+{
+ int centisecs = secs * 100;
+
+ if (write_oneshot_int(DIRTY_EXPIRE, centisecs))
+ die("Could not open %s for writing:", DIRTY_EXPIRE);
+
+ if (write_oneshot_int(DIRTY_WRITEBACK, centisecs))
+ die("Could not open %s for writing:", DIRTY_WRITEBACK);
+}
+
+static int get_frequency(int typ, int cpu)
+{
+ /* typ is 0 for min, !0 for max */
+ FILE *fd;
+ char *line = NULL, *path, *rem;
+ char ccpu[1]; /* Will break if cpu > 9 */
+ size_t nread, len;
+ int plen, ret;
+
+ if (typ)
+ rem = "/cpufreq/cpuinfo_max_freq";
+ else
+ rem = "/cpufreq/cpuinfo_min_freq";
+
+
+ itoa(cpu, ccpu);
+
+ plen = strlen(SYS_CPU_PREFIX) + strlen(rem) + 2;
+ path = malloc(plen);
+ memset(path, 0, plen);
+
+ path = strcat(path, SYS_CPU_PREFIX);
+ path = strcat(path, ccpu);
+ path = strcat(path, rem);
+
+ if ((fd = fopen(path, "r")) == NULL) {
+ free(path);
+ die("Could not open cpu%d min/max file for reading:", cpu);
+ }
+
+ free(path);
+
+ nread = getline(&line, &len, fd);
+ (void)nread;
+
+ ret = atoi(line);
+ free(line);
+
+ return ret;
+}
+
+static void set_frequency(int typ, int cpu, int freq)
+{
+ /* typ is 0 for min, !0 for max */
+ char *path, *rem, *ccpu;
+ int plen;
+
+ if (cpu > 9) {
+ ccpu = malloc(2);
+ memset(ccpu, 0, 2);
+ } else {
+ ccpu = malloc(1);
+ memset(ccpu, 0, 1);
+ }
+
+ if (typ)
+ rem = "/cpufreq/scaling_max_freq";
+ else
+ rem = "/cpufreq/scaling_min_freq";
+
+ itoa(cpu, ccpu);
+ plen = strlen(ccpu) + strlen(SYS_CPU_PREFIX) + strlen(rem);
+ path = malloc(plen);
+ memset(path, 0, plen);
+
+ path = strcat(path, SYS_CPU_PREFIX);
+ path = strcat(path, ccpu);
+ path = strcat(path, rem);
+
+ free(ccpu);
+
+ if (write_oneshot_int(path, freq)) {
+ free(path);
+ die("Could not open cpu%d min/max file for writing:", cpu);
+ }
+
+ free(path);
+}
+
+static void set_governor(int cpu, const char *governor)
+{
+ char *path, *ccpu;
+ char *rem = "/cpufreq/scaling_governor";
+ int plen;
+
+ if (cpu > 9) {
+ ccpu = malloc(2);
+ memset(ccpu, 0, 2);
+ } else {
+ ccpu = malloc(1);
+ memset(ccpu, 0, 1);
+ }
+
+ itoa(cpu, ccpu);
+ plen = strlen(ccpu) + strlen(SYS_CPU_PREFIX) + strlen(rem);
+ path = malloc(plen);
+ memset(path, 0, plen);
+
+ path = strcat(path, SYS_CPU_PREFIX);
+ path = strcat(path, ccpu);
+ path = strcat(path, rem);
+
+ free(ccpu);
+
+ if (write_oneshot_str(path, governor)) {
+ free(path);
+ die("Could not open cpu%d governor file:", cpu);
+ }
+
+ free(path);
+}
+
+static void cpufreq_set(enum Profile profile, int max_percent)
+{
+ int i, nproc;
+ int min, max;
+ char *governor;
+
+ /* We assume we have intel_pstate */
+ switch(profile) {
+ case LOWPOWER:
+ case BALANCED:
+ governor = "powersave";
+ break;
+ case PERFORMANCE:
+ governor = "performance";
+ break;
+ }
+
+ /* We look at cpu0 but assume they're all the same */
+ min = get_frequency(0, 0);
+ max = get_frequency(1, 0);
+
+ max = max * MIN(max_percent, 100) / 100;
+
+ nproc = get_nprocs();
+ for (i = 0; i < nproc; i++) {
+ set_frequency(0, i, min);
+ set_frequency(1, i, max);
+ set_governor(i, governor);
+ }
+}
+
+static void set_lowpower(void)
+{
+ set_max_lost_work(15);
+ cpufreq_set(LOWPOWER, 50);
+
+ /* intel_pstate values */
+ if (write_oneshot_int(PSTATE_MIN_PERF, 0))
+ die("Could not open %s for writing:", PSTATE_MIN_PERF);
+
+ if (write_oneshot_int(PSTATE_MAX_PERF, 50))
+ die("Could not open %s for writing:", PSTATE_MAX_PERF);
+
+ if (write_oneshot_int(PSTATE_NO_TURBO, 1))
+ die("Could not open %s for writing:", PSTATE_NO_TURBO);
+}
+
+static void set_balanced(void)
+{
+ set_max_lost_work(15);
+ cpufreq_set(BALANCED, 100);
+
+ /* intel_pstate values */
+ if (write_oneshot_int(PSTATE_DYNBOOST, 1))
+ die("Could not open %s for writing:", PSTATE_DYNBOOST);
+
+ if (write_oneshot_int(PSTATE_MIN_PERF, 0))
+ die("Could not open %s for writing:", PSTATE_MIN_PERF);
+
+ if (write_oneshot_int(PSTATE_MAX_PERF, 100))
+ die("Could not open %s for writing:", PSTATE_MAX_PERF);
+
+ if (write_oneshot_int(PSTATE_NO_TURBO, 0))
+ die("Could not open %s for writing:", PSTATE_NO_TURBO);
+}
+
+static void set_performance(void)
+{
+ set_max_lost_work(15);
+ cpufreq_set(PERFORMANCE, 100);
+
+ /* intel_pstate values */
+ if (write_oneshot_int(PSTATE_DYNBOOST, 1))
+ die("Could not open %s for writing:", PSTATE_DYNBOOST);
+
+ if (write_oneshot_int(PSTATE_MIN_PERF, 0))
+ die("Could not open %s for writing:", PSTATE_MIN_PERF);
+
+ if (write_oneshot_int(PSTATE_MAX_PERF, 100))
+ die("Could not open %s for writing:", PSTATE_MAX_PERF);
+
+ if (write_oneshot_int(PSTATE_NO_TURBO, 0))
+ die("Could not open %s for writing:", PSTATE_NO_TURBO);
+
+ /* TODO: PCI runtime pm off */
+}
+
+int main(int argc, char *argv[])
+{
+ int vflag = 0;
+ int acpi_platform_supported = 0;
+ char *line = NULL;
+ size_t len, nread;
+ FILE *fd;
+
+ ARGBEGIN {
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ exit(1);
+ } ARGEND;
+
+ if (!access(ACPI_PLPR_PATH, F_OK))
+ acpi_platform_supported = 1;
+
+
+ if (vflag) {
+ if (acpi_platform_supported) {
+ if ((fd = fopen(ACPI_PLPR_PATH, "r")) == NULL)
+ die("Could not open %s for reading:", ACPI_PLPR_PATH);
+ } else {
+ if ((fd = fopen(S76_POW_PROF, "r")) == NULL)
+ die("Could not open %s for reading:", S76_POW_PROF);
+ }
+
+ nread = getline(&line, &len, fd);
+ fclose(fd);
+ (void)nread;
+
+ printf("Current profile: %s\n", line);
+ free(line);
+ exit(0);
+ }
+
+ if (argc != 1)
+ usage();
+
+ if (acpi_platform_supported) {
+ if ((fd = fopen(ACPI_PLPR_PATH, "w")) == NULL)
+ die("Could not open %s for writing:", ACPI_PLPR_PATH);
+
+ if (!strcmp(argv[0], "low-power"))
+ fprintf(fd, "low-power");
+ else if (!strcmp(argv[0], "balanced"))
+ fprintf(fd, "balanced");
+ else if (!strcmp(argv[0], "performance"))
+ fprintf(fd, "performance");
+ else {
+ fclose(fd);
+ usage();
+ }
+
+ fclose(fd);
+ printf("Platform profile set to: %s\n", argv[0]);
+ return 0;
+ }
+
+ if (!strcmp(argv[0], "low-power"))
+ set_lowpower();
+ else if (!strcmp(argv[0], "balanced"))
+ set_balanced();
+ else if (!strcmp(argv[0], "performance"))
+ set_performance();
+ else
+ usage();
+
+ return write_oneshot_str(S76_POW_PROF, argv[0]);
+}