Skip to content

Commit 4bf6bd5

Browse files
committed
Add support for Intel turbo frequency throttling through intel_pstate
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 <gokturk@gentoo.org>
1 parent 52d8973 commit 4bf6bd5

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

mbpfan.conf

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ low_temp = 63 # try ranges 55-63, default is 63
1616
high_temp = 66 # try ranges 58-66, default is 66
1717
max_temp = 86 # take highest number returned by "cat /sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_max", divide by 1000
1818
polling_interval = 1 # default is 1 seconds
19+
intel_pstate_control = 0 # whether mbpfan should throttle down max turbo speed when exceeding max temp

src/daemon.c

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "global.h"
3535
#include "daemon.h"
3636
#include "util.h"
37+
#include "intel_pstate.h"
3738

3839
int daemonize = 1;
3940
int verbose = 0;
@@ -108,6 +109,9 @@ static void cleanup_and_exit(int exit_code)
108109
sensors = next_sensor;
109110
}
110111

112+
intel_pstate_exit(intel_pstate);
113+
free(intel_pstate);
114+
111115
exit(exit_code);
112116
}
113117

src/intel_pstate.c

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* intel_pstate.c - automatically control turbo frequency for MacBook Pro
3+
* Copyright (C) 2021 Gokturk Yuksek <gokturk@gentoo.org>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
*/
16+
17+
#include <stdio.h>
18+
#include <stdlib.h>
19+
#include <string.h>
20+
#include <unistd.h>
21+
#include <sys/types.h>
22+
#include <dirent.h>
23+
#include <errno.h>
24+
25+
#include "intel_pstate.h"
26+
#include "util.h"
27+
#include <syslog.h>
28+
29+
static int read_int(FILE *fp, int *val)
30+
{
31+
char buf[4]; /* Maximum we can read in this module is '100\0' */
32+
ssize_t ret;
33+
34+
ret = pread(fileno(fp), buf, sizeof(buf), 0);
35+
if (ret >= 0) {
36+
buf[ret] = '\0';
37+
*val = atoi(buf);
38+
return 0;
39+
} else {
40+
return (int)ret;
41+
}
42+
}
43+
44+
static inline int write_str(FILE *fp, char *str, int len)
45+
{
46+
ssize_t ret;
47+
ret = (int)pwrite(fileno(fp), str, len, 0);
48+
if (ret == len)
49+
return 0;
50+
else
51+
return -1;
52+
}
53+
54+
static int write_int(FILE *fp, int val)
55+
{
56+
char buf[4]; /* Maximum we can read in this module is '100\0' */
57+
int ret;
58+
59+
ret = snprintf(buf, sizeof(buf), "%d", val);
60+
if (ret < 0)
61+
return ret;
62+
63+
return write_str(fp, buf, ret);
64+
}
65+
66+
int intel_pstate_init(t_intel_pstate *intel_pstate)
67+
{
68+
const char *path_max_perf_pct = INTEL_PT_PATH "/max_perf_pct";
69+
const char *path_min_perf_pct = INTEL_PT_PATH "/min_perf_pct";
70+
FILE *fp;
71+
int err;
72+
73+
fp = fopen(path_max_perf_pct, "w+");
74+
if (!fp)
75+
return -1;
76+
/* Save the initial value of max_perf_pct, to restore it on exit */
77+
err = read_int(fp, &intel_pstate->preserved_max_perf_pct);
78+
if (err)
79+
return err;
80+
/* Start with the maximum performance percentage at 100% */
81+
err = write_str(fp, "100", 4);
82+
if (err)
83+
return err;
84+
intel_pstate->f_max_perf_pct = fp;
85+
86+
fp = fopen(path_min_perf_pct, "w+");
87+
if (!fp)
88+
return -1;
89+
/* Save the initial value of min_perf_pct, to restore it on exit */
90+
err = read_int(fp, &intel_pstate->preserved_min_perf_pct);
91+
if (err)
92+
return err;
93+
/* Set the minimum performance percentage to 0 */
94+
err = write_str(fp, "0", 2);
95+
if (err)
96+
return err;
97+
intel_pstate->f_min_perf_pct = fp;
98+
99+
return 0;
100+
}
101+
102+
int intel_pstate_is_available(void)
103+
{
104+
DIR* dir = opendir(INTEL_PT_PATH);
105+
106+
if ((!dir) && ENOENT == errno)
107+
return 0;
108+
109+
closedir(dir);
110+
return 1;
111+
}
112+
113+
int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step)
114+
{
115+
int val;
116+
int err;
117+
118+
if (!intel_pstate)
119+
return 1;
120+
121+
err = read_int(intel_pstate->f_max_perf_pct, &val);
122+
if (err)
123+
return err;
124+
125+
mbp_log(LOG_INFO, "Adjusting intel_pstate: val: %d, step: %d", val, step);
126+
127+
val += step;
128+
if (val < 0)
129+
val = 0;
130+
if (val > 100)
131+
val = 100;
132+
133+
return write_int(intel_pstate->f_max_perf_pct, val);
134+
}
135+
136+
void intel_pstate_exit(t_intel_pstate *intel_pstate)
137+
{
138+
if (!intel_pstate)
139+
return;
140+
141+
(void)write_int(intel_pstate->f_max_perf_pct, intel_pstate->preserved_max_perf_pct);
142+
(void)write_int(intel_pstate->f_min_perf_pct, intel_pstate->preserved_min_perf_pct);
143+
144+
fclose(intel_pstate->f_max_perf_pct);
145+
fclose(intel_pstate->f_min_perf_pct);
146+
147+
memset(intel_pstate, 0, sizeof(*intel_pstate));
148+
}

src/intel_pstate.h

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* intel_pstate.c - automatically control turbo frequency for MacBook Pro
3+
* Copyright (C) 2021 Gokturk Yuksek <gokturk@gentoo.org>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
*/
16+
17+
#ifndef _INTEL_PT_H_
18+
#define _INTEL_PT_H_
19+
20+
#define INTEL_PT_PATH "/sys/devices/system/cpu/intel_pstate/"
21+
22+
struct s_intel_pstate {
23+
FILE *f_max_perf_pct;
24+
FILE *f_min_perf_pct;
25+
int preserved_max_perf_pct;
26+
int preserved_min_perf_pct;
27+
};
28+
29+
typedef struct s_intel_pstate t_intel_pstate;
30+
31+
extern t_intel_pstate *intel_pstate;
32+
33+
extern int intel_pstate_init(t_intel_pstate *intel_pstate);
34+
extern int intel_pstate_is_available(void);
35+
extern int intel_pstate_adjust(t_intel_pstate *intel_pstate, int step);
36+
extern void intel_pstate_exit(t_intel_pstate *intel_pstate);
37+
38+
#endif /*_INTEL_PT_H_*/

src/mbpfan.c

+37
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "global.h"
4646
#include "settings.h"
4747
#include "util.h"
48+
#include "intel_pstate.h"
4849

4950
/* lazy min/max... */
5051
#define min(a,b) ((a) < (b) ? (a) : (b))
@@ -61,6 +62,9 @@ int low_temp = 63; // try ranges 55-63
6162
int high_temp = 66; // try ranges 58-66
6263
int max_temp = 86; // do not set it > 90
6364

65+
/* Whether mbpfan should control the turbo frequency or not */
66+
int intel_pstate_control = 0; // disabled by default
67+
6468
// maximum number of processors etc supported
6569
#define NUM_PROCESSORS 6
6670
#define NUM_HWMONS 12
@@ -74,6 +78,7 @@ int polling_interval = 1;
7478

7579
t_sensors* sensors = NULL;
7680
t_fans* fans = NULL;
81+
t_intel_pstate* intel_pstate = NULL;
7782

7883
char *smprintf(const char *fmt, ...)
7984
{
@@ -524,6 +529,12 @@ void retrieve_settings(const char* settings_path, t_fans* fans)
524529
polling_interval = result;
525530
}
526531

532+
result = settings_get_int(settings, "general", "intel_pstate_control");
533+
534+
if (result != 0) {
535+
intel_pstate_control = (result == 0) ? 0 : 1;
536+
}
537+
527538
/* Destroy the settings object */
528539
settings_delete(settings);
529540
}
@@ -573,12 +584,31 @@ void mbpfan()
573584
{
574585
int old_temp, new_temp, fan_speed, steps;
575586
int temp_change;
587+
int err;
576588

577589
sensors = retrieve_sensors();
578590
fans = retrieve_fans();
579591

580592
retrieve_settings(NULL, fans);
581593

594+
if (intel_pstate_control) {
595+
if (!intel_pstate_is_available()) {
596+
mbp_log(LOG_ERR, "Intel pstate control is requested but not available");
597+
exit(EXIT_FAILURE);
598+
} else {
599+
intel_pstate = (t_intel_pstate*)malloc(sizeof(t_intel_pstate));
600+
if (!intel_pstate) {
601+
mbp_log(LOG_ERR, "Failed to allocate memory for intel_pstate control");
602+
exit(EXIT_FAILURE);
603+
}
604+
err = intel_pstate_init(intel_pstate);
605+
if (err) {
606+
mbp_log(LOG_ERR, "Failed to initialize intel_pstate control");
607+
exit(EXIT_FAILURE);
608+
}
609+
}
610+
}
611+
582612
t_fans* fan = fans;
583613
while(fan != NULL) {
584614

@@ -620,6 +650,13 @@ void mbpfan()
620650
old_temp = new_temp;
621651
new_temp = get_temp(sensors);
622652

653+
if (new_temp <= high_temp) /* maximize turbo below high_temp */
654+
intel_pstate_adjust(intel_pstate, +100);
655+
else if (new_temp >= max_temp) /* throttle down to keep the temp in control */
656+
intel_pstate_adjust(intel_pstate, -4);
657+
else if ((new_temp - old_temp) <= -3) /* core is cooling down, increase turbo */
658+
intel_pstate_adjust(intel_pstate, +1);
659+
623660
fan = fans;
624661

625662
while(fan != NULL) {

0 commit comments

Comments
 (0)