Add perf-profile for performance profile changes. - system76-tools - collection of utilities for system76 laptops
 (HTM) git clone https://git.parazyd.org/system76-tools
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 1c7821ae65f3d3d3a7871d3d57d033834649e8d5
 (DIR) parent ac637e2e2d1466d87b5c0e784e4a186a25e87532
 (HTM) 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(-)
       ---
 (DIR) 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
 (DIR) 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;
       +}
 (DIR) 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
 (DIR) 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]);
       +}