From 4bf6bd502d2f0dd4606fe359d8c1023d5a66e5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Y=C3=BCksek?= Date: Sat, 23 Jan 2021 21:00:58 -0500 Subject: [PATCH 1/5] Add support for Intel turbo frequency throttling through intel_pstate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support controlling the maximum turbo frequency percentage on Intel CPUs. Under heavy loads with 100% turbo frequency, spinning the fans at their maximum speed does not prevent the CPU from reaching temperatures above 90C and beyond. Reducing the effective clock frequency of the CPU is an effective way to control its temperature. The proposed intel_pstate controller tries to maintain the turbo frequency at the maximunm possible value that still keeps the CPU temperature below the user-specified 'max_temp' value. Every time the temperature crosses the max_temp threshold, the controller reduces the maximum frequency percentage by 4%. When the temperature drops, the controller increases the turbo frequency by 1%. To account for the jitter in temperature readings, the controller only increases the temperature when the drop is greater than or equual to 3 degrees Celcius. If the temperature drops below 'high_temp', the maximum turbo frequency percentage is set back to 100%. The values 4%, 1%, and 3C are chosen empirically and the code can be updated later on to make these user-configurable as well through mbpfan.conf. Signed-off-by: Göktürk Yüksek --- mbpfan.conf | 1 + src/daemon.c | 4 ++ src/intel_pstate.c | 148 +++++++++++++++++++++++++++++++++++++++++++++ src/intel_pstate.h | 38 ++++++++++++ src/mbpfan.c | 37 ++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 src/intel_pstate.c create mode 100644 src/intel_pstate.h diff --git a/mbpfan.conf b/mbpfan.conf index 39d4b57..daa7d4c 100644 --- a/mbpfan.conf +++ b/mbpfan.conf @@ -16,3 +16,4 @@ low_temp = 63 # try ranges 55-63, default is 63 high_temp = 66 # try ranges 58-66, default is 66 max_temp = 86 # take highest number returned by "cat /sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_max", divide by 1000 polling_interval = 1 # default is 1 seconds +intel_pstate_control = 0 # whether mbpfan should throttle down max turbo speed when exceeding max temp diff --git a/src/daemon.c b/src/daemon.c index 3f933e8..0ef2710 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -34,6 +34,7 @@ #include "global.h" #include "daemon.h" #include "util.h" +#include "intel_pstate.h" int daemonize = 1; int verbose = 0; @@ -108,6 +109,9 @@ static void cleanup_and_exit(int exit_code) sensors = next_sensor; } + intel_pstate_exit(intel_pstate); + free(intel_pstate); + exit(exit_code); } diff --git a/src/intel_pstate.c b/src/intel_pstate.c new file mode 100644 index 0000000..2bfb0b9 --- /dev/null +++ b/src/intel_pstate.c @@ -0,0 +1,148 @@ +/** + * intel_pstate.c - automatically control turbo frequency for MacBook Pro + * Copyright (C) 2021 Gokturk Yuksek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "intel_pstate.h" +#include "util.h" +#include + +static int read_int(FILE *fp, int *val) +{ + char buf[4]; /* Maximum we can read in this module is '100\0' */ + ssize_t ret; + + ret = pread(fileno(fp), buf, sizeof(buf), 0); + if (ret >= 0) { + buf[ret] = '\0'; + *val = atoi(buf); + return 0; + } else { + return (int)ret; + } +} + +static inline int write_str(FILE *fp, char *str, int len) +{ + ssize_t ret; + ret = (int)pwrite(fileno(fp), str, len, 0); + if (ret == len) + return 0; + else + return -1; +} + +static int write_int(FILE *fp, int val) +{ + char buf[4]; /* Maximum we can read in this module is '100\0' */ + int ret; + + ret = snprintf(buf, sizeof(buf), "%d", val); + if (ret < 0) + return ret; + + return write_str(fp, buf, ret); +} + +int intel_pstate_init(t_intel_pstate *intel_pstate) +{ + const char *path_max_perf_pct = INTEL_PT_PATH "/max_perf_pct"; + const char *path_min_perf_pct = INTEL_PT_PATH "/min_perf_pct"; + FILE *fp; + int err; + + fp = fopen(path_max_perf_pct, "w+"); + if (!fp) + return -1; + /* Save the initial value of max_perf_pct, to restore it on exit */ + err = read_int(fp, &intel_pstate->preserved_max_perf_pct); + if (err) + return err; + /* Start with the maximum performance percentage at 100% */ + err = write_str(fp, "100", 4); + if (err) + return err; + intel_pstate->f_max_perf_pct = fp; + + fp = fopen(path_min_perf_pct, "w+"); + if (!fp) + return -1; + /* Save the initial value of min_perf_pct, to restore it on exit */ + err = read_int(fp, &intel_pstate->preserved_min_perf_pct); + if (err) + return err; + /* Set the minimum performance percentage to 0 */ + err = write_str(fp, "0", 2); + if (err) + return err; + intel_pstate->f_min_perf_pct = fp; + + return 0; +} + +int intel_pstate_is_available(void) +{ + DIR* dir = opendir(INTEL_PT_PATH); + + if ((!dir) && ENOENT == errno) + return 0; + + closedir(dir); + return 1; +} + +int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step) +{ + int val; + int err; + + if (!intel_pstate) + return 1; + + err = read_int(intel_pstate->f_max_perf_pct, &val); + if (err) + return err; + + mbp_log(LOG_INFO, "Adjusting intel_pstate: val: %d, step: %d", val, step); + + val += step; + if (val < 0) + val = 0; + if (val > 100) + val = 100; + + return write_int(intel_pstate->f_max_perf_pct, val); +} + +void intel_pstate_exit(t_intel_pstate *intel_pstate) +{ + if (!intel_pstate) + return; + + (void)write_int(intel_pstate->f_max_perf_pct, intel_pstate->preserved_max_perf_pct); + (void)write_int(intel_pstate->f_min_perf_pct, intel_pstate->preserved_min_perf_pct); + + fclose(intel_pstate->f_max_perf_pct); + fclose(intel_pstate->f_min_perf_pct); + + memset(intel_pstate, 0, sizeof(*intel_pstate)); +} diff --git a/src/intel_pstate.h b/src/intel_pstate.h new file mode 100644 index 0000000..61c2278 --- /dev/null +++ b/src/intel_pstate.h @@ -0,0 +1,38 @@ +/** + * intel_pstate.c - automatically control turbo frequency for MacBook Pro + * Copyright (C) 2021 Gokturk Yuksek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _INTEL_PT_H_ +#define _INTEL_PT_H_ + +#define INTEL_PT_PATH "/sys/devices/system/cpu/intel_pstate/" + +struct s_intel_pstate { + FILE *f_max_perf_pct; + FILE *f_min_perf_pct; + int preserved_max_perf_pct; + int preserved_min_perf_pct; +}; + +typedef struct s_intel_pstate t_intel_pstate; + +extern t_intel_pstate *intel_pstate; + +extern int intel_pstate_init(t_intel_pstate *intel_pstate); +extern int intel_pstate_is_available(void); +extern int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step); +extern void intel_pstate_exit(t_intel_pstate *intel_pstate); + +#endif /*_INTEL_PT_H_*/ diff --git a/src/mbpfan.c b/src/mbpfan.c index 1916d64..63dbf14 100644 --- a/src/mbpfan.c +++ b/src/mbpfan.c @@ -45,6 +45,7 @@ #include "global.h" #include "settings.h" #include "util.h" +#include "intel_pstate.h" /* lazy min/max... */ #define min(a,b) ((a) < (b) ? (a) : (b)) @@ -61,6 +62,9 @@ int low_temp = 63; // try ranges 55-63 int high_temp = 66; // try ranges 58-66 int max_temp = 86; // do not set it > 90 +/* Whether mbpfan should control the turbo frequency or not */ +int intel_pstate_control = 0; // disabled by default + // maximum number of processors etc supported #define NUM_PROCESSORS 6 #define NUM_HWMONS 12 @@ -74,6 +78,7 @@ int polling_interval = 1; t_sensors* sensors = NULL; t_fans* fans = NULL; +t_intel_pstate* intel_pstate = NULL; char *smprintf(const char *fmt, ...) { @@ -524,6 +529,12 @@ void retrieve_settings(const char* settings_path, t_fans* fans) polling_interval = result; } + result = settings_get_int(settings, "general", "intel_pstate_control"); + + if (result != 0) { + intel_pstate_control = (result == 0) ? 0 : 1; + } + /* Destroy the settings object */ settings_delete(settings); } @@ -573,12 +584,31 @@ void mbpfan() { int old_temp, new_temp, fan_speed, steps; int temp_change; + int err; sensors = retrieve_sensors(); fans = retrieve_fans(); retrieve_settings(NULL, fans); + if (intel_pstate_control) { + if (!intel_pstate_is_available()) { + mbp_log(LOG_ERR, "Intel pstate control is requested but not available"); + exit(EXIT_FAILURE); + } else { + intel_pstate = (t_intel_pstate*)malloc(sizeof(t_intel_pstate)); + if (!intel_pstate) { + mbp_log(LOG_ERR, "Failed to allocate memory for intel_pstate control"); + exit(EXIT_FAILURE); + } + err = intel_pstate_init(intel_pstate); + if (err) { + mbp_log(LOG_ERR, "Failed to initialize intel_pstate control"); + exit(EXIT_FAILURE); + } + } + } + t_fans* fan = fans; while(fan != NULL) { @@ -620,6 +650,13 @@ void mbpfan() old_temp = new_temp; new_temp = get_temp(sensors); + if (new_temp <= high_temp) /* maximize turbo below high_temp */ + intel_pstate_adjust(intel_pstate, +100); + else if (new_temp >= max_temp) /* throttle down to keep the temp in control */ + intel_pstate_adjust(intel_pstate, -4); + else if ((new_temp - old_temp) <= -3) /* core is cooling down, increase turbo */ + intel_pstate_adjust(intel_pstate, +1); + fan = fans; while(fan != NULL) { From 5454d83577cae7ffc7ec5ab19b70fdffc4eb7f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Y=C3=BCksek?= Date: Sun, 14 Mar 2021 16:28:37 -0400 Subject: [PATCH 2/5] intel_pstate: check for NULL before calling closedir() --- src/intel_pstate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/intel_pstate.c b/src/intel_pstate.c index 2bfb0b9..1fdcb08 100644 --- a/src/intel_pstate.c +++ b/src/intel_pstate.c @@ -106,7 +106,8 @@ int intel_pstate_is_available(void) if ((!dir) && ENOENT == errno) return 0; - closedir(dir); + if (dir) + closedir(dir); return 1; } From 059a9025b163d1cd47f2bca00eadba92d67793ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Y=C3=BCksek?= Date: Sun, 14 Mar 2021 16:29:16 -0400 Subject: [PATCH 3/5] mbpfan: do not check the return of malloc for intel_pstate --- src/mbpfan.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mbpfan.c b/src/mbpfan.c index 63dbf14..99a9ee0 100644 --- a/src/mbpfan.c +++ b/src/mbpfan.c @@ -597,10 +597,6 @@ void mbpfan() exit(EXIT_FAILURE); } else { intel_pstate = (t_intel_pstate*)malloc(sizeof(t_intel_pstate)); - if (!intel_pstate) { - mbp_log(LOG_ERR, "Failed to allocate memory for intel_pstate control"); - exit(EXIT_FAILURE); - } err = intel_pstate_init(intel_pstate); if (err) { mbp_log(LOG_ERR, "Failed to initialize intel_pstate control"); From d4c2fb2e5ce9e5d9fff9081c344f12e88a6161c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Y=C3=BCksek?= Date: Sun, 14 Mar 2021 17:02:40 -0400 Subject: [PATCH 4/5] intel_pstate: use fscanf/fprintf instead of the custom read/write functions --- src/intel_pstate.c | 79 +++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 57 deletions(-) diff --git a/src/intel_pstate.c b/src/intel_pstate.c index 1fdcb08..8dbca19 100644 --- a/src/intel_pstate.c +++ b/src/intel_pstate.c @@ -26,74 +26,37 @@ #include "util.h" #include -static int read_int(FILE *fp, int *val) -{ - char buf[4]; /* Maximum we can read in this module is '100\0' */ - ssize_t ret; - - ret = pread(fileno(fp), buf, sizeof(buf), 0); - if (ret >= 0) { - buf[ret] = '\0'; - *val = atoi(buf); - return 0; - } else { - return (int)ret; - } -} - -static inline int write_str(FILE *fp, char *str, int len) -{ - ssize_t ret; - ret = (int)pwrite(fileno(fp), str, len, 0); - if (ret == len) - return 0; - else - return -1; -} - -static int write_int(FILE *fp, int val) -{ - char buf[4]; /* Maximum we can read in this module is '100\0' */ - int ret; - - ret = snprintf(buf, sizeof(buf), "%d", val); - if (ret < 0) - return ret; - - return write_str(fp, buf, ret); -} - int intel_pstate_init(t_intel_pstate *intel_pstate) { const char *path_max_perf_pct = INTEL_PT_PATH "/max_perf_pct"; const char *path_min_perf_pct = INTEL_PT_PATH "/min_perf_pct"; FILE *fp; - int err; + int ret; fp = fopen(path_max_perf_pct, "w+"); if (!fp) return -1; /* Save the initial value of max_perf_pct, to restore it on exit */ - err = read_int(fp, &intel_pstate->preserved_max_perf_pct); - if (err) - return err; + ret = fscanf(fp, "%d", &intel_pstate->preserved_max_perf_pct); + if (ret != 1) + return -1; /* Start with the maximum performance percentage at 100% */ - err = write_str(fp, "100", 4); - if (err) - return err; + ret = fprintf(fp, "100"); + if (ret != 3) /* "100" -> 3 characters */ + return -1; intel_pstate->f_max_perf_pct = fp; fp = fopen(path_min_perf_pct, "w+"); if (!fp) return -1; /* Save the initial value of min_perf_pct, to restore it on exit */ - err = read_int(fp, &intel_pstate->preserved_min_perf_pct); - if (err) - return err; + ret = fscanf(fp, "%d", &intel_pstate->preserved_min_perf_pct); + if (ret != 1) + return -1; /* Set the minimum performance percentage to 0 */ - err = write_str(fp, "0", 2); - if (err) - return err; + ret = fprintf(fp, "0"); + if (ret != 1) /* "0" -> 1 character */ + return -1; intel_pstate->f_min_perf_pct = fp; return 0; @@ -114,14 +77,15 @@ int intel_pstate_is_available(void) int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step) { int val; - int err; + int ret; if (!intel_pstate) return 1; - err = read_int(intel_pstate->f_max_perf_pct, &val); - if (err) - return err; + rewind(intel_pstate->f_max_perf_pct); + ret = fscanf(intel_pstate->f_max_perf_pct, "%d", &val); + if (ret != 1) + return -1; mbp_log(LOG_INFO, "Adjusting intel_pstate: val: %d, step: %d", val, step); @@ -131,7 +95,8 @@ int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step) if (val > 100) val = 100; - return write_int(intel_pstate->f_max_perf_pct, val); + ret = fprintf(intel_pstate->f_max_perf_pct, "%d", val); + return (ret > 0) ? 0 : -1; } void intel_pstate_exit(t_intel_pstate *intel_pstate) @@ -139,8 +104,8 @@ void intel_pstate_exit(t_intel_pstate *intel_pstate) if (!intel_pstate) return; - (void)write_int(intel_pstate->f_max_perf_pct, intel_pstate->preserved_max_perf_pct); - (void)write_int(intel_pstate->f_min_perf_pct, intel_pstate->preserved_min_perf_pct); + (void)fprintf(intel_pstate->f_max_perf_pct, "%d", intel_pstate->preserved_max_perf_pct); + (void)fprintf(intel_pstate->f_min_perf_pct, "%d", intel_pstate->preserved_min_perf_pct); fclose(intel_pstate->f_max_perf_pct); fclose(intel_pstate->f_min_perf_pct); From 4065354edc149e2a8d58c581258e422f814889cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6kt=C3=BCrk=20Y=C3=BCksek?= Date: Sun, 14 Mar 2021 18:03:47 -0400 Subject: [PATCH 5/5] intel_pstate: prevent rewriting of the same maximum value repeatedly --- src/intel_pstate.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/intel_pstate.c b/src/intel_pstate.c index 8dbca19..6903c0e 100644 --- a/src/intel_pstate.c +++ b/src/intel_pstate.c @@ -76,26 +76,29 @@ int intel_pstate_is_available(void) int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step) { - int val; + int cur_val, new_val; int ret; if (!intel_pstate) return 1; rewind(intel_pstate->f_max_perf_pct); - ret = fscanf(intel_pstate->f_max_perf_pct, "%d", &val); + ret = fscanf(intel_pstate->f_max_perf_pct, "%d", &cur_val); if (ret != 1) return -1; - mbp_log(LOG_INFO, "Adjusting intel_pstate: val: %d, step: %d", val, step); + new_val = cur_val + step; + if (new_val < 0) + new_val = 0; + if (new_val > 100) + new_val = 100; - val += step; - if (val < 0) - val = 0; - if (val > 100) - val = 100; + if (new_val == cur_val) + return 0; - ret = fprintf(intel_pstate->f_max_perf_pct, "%d", val); + mbp_log(LOG_INFO, "Adjusting intel_pstate: cur_val: %d, step: %d", cur_val, step); + + ret = fprintf(intel_pstate->f_max_perf_pct, "%d", new_val); return (ret > 0) ? 0 : -1; }