1 #include "config.h"
2
3 #include <errno.h>
4 #include <math.h>
5 #include <setjmp.h>
6 #include <signal.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #if IS_FREEBSD
13 #include <sys/cpuctl.h>
14 #include <sys/ioccom.h>
15 #endif
16
17 #define absf(x) ((x) < 0 ? -(x) : (x))
18
19 #if IS_FREEBSD
20
cpuctl_rd(int fd,int a,uint64_t * t)21 static inline bool cpuctl_rd(int fd, int a, uint64_t * t) {
22 cpuctl_msr_args_t args;
23 args.msr = a;
24 if (ioctl(fd, CPUCTL_RDMSR, &args) == -1) {
25 return false;
26 }
27 *t = args.data;
28 return true;
29 }
30
cpuctl_wr(int fd,int a,uint64_t * t)31 static inline bool cpuctl_wr(int fd, int a, uint64_t * t) {
32 cpuctl_msr_args_t args;
33 args.msr = a;
34 args.data = *t;
35 return ioctl(fd, CPUCTL_WRMSR, &args) != -1;
36 }
37
38 #define rd(c, a, t) (cpuctl_rd(c->fd_msr, (a), &(t)))
39 #define wr(c, a, t) (cpuctl_wr(c->fd_msr, (a), &(t)))
40
41 #else
42
43 #define rd(c, a, t) (pread(c->fd_msr, &(t), 8, (a)) == 8)
44 #define wr(c, a, t) (pwrite(c->fd_msr, &(t), 8, (a)) == 8)
45
46 #endif
47
48 static jmp_buf sigsegv_handler_jmp_buf;
49
sigsegv_handler(int sig)50 static void sigsegv_handler(int sig) {
51 siglongjmp(sigsegv_handler_jmp_buf, 1);
52 }
53
safe_read_write(uint64_t * addr,uint64_t * data,bool write)54 static bool safe_read_write(uint64_t * addr, uint64_t * data, bool write) {
55 struct sigaction oldact;
56 struct sigaction act;
57 memset(&act, 0, sizeof(struct sigaction));
58 act.sa_handler = sigsegv_handler;
59 sigaction(SIGSEGV, &act, &oldact);
60 bool success = false;
61 if (sigsetjmp(sigsegv_handler_jmp_buf, 1) == 0) {
62 if (write) {
63 *addr = *data;
64 } else {
65 *data = *addr;
66 }
67 success = true;
68 }
69 sigaction(SIGSEGV, &oldact, &act);
70 return success;
71 }
72
73 #define newline(nl) { \
74 if (nl) { \
75 if (*nl) { \
76 printf("\n"); \
77 } \
78 *nl = true; \
79 } \
80 }
81
82 typedef struct {
83 config_t * config;
84 bool write;
85 bool success;
86 } undervolt_ctx;
87
undervolt_it(uv_list_t * uv,void * data)88 static void undervolt_it(uv_list_t * uv, void * data) {
89 undervolt_ctx * ctx = data;
90
91 static int mask = 0x800;
92 uint64_t uvint = ((uint64_t) (mask - absf(uv->value) * 1.024f + 0.5f)
93 << 21) & 0xffffffff;
94 uint64_t rdval = 0x8000001000000000 | ((uint64_t) uv->index << 40);
95 uint64_t wrval = rdval | 0x100000000 | uvint;
96
97 bool write_success = !ctx->write ||
98 wr(ctx->config, MSR_ADDR_VOLTAGE, wrval);
99 bool read_success = write_success &&
100 wr(ctx->config, MSR_ADDR_VOLTAGE, rdval) &&
101 rd(ctx->config, MSR_ADDR_VOLTAGE, rdval);
102
103 const char * errstr = NULL;
104 if (!write_success || !read_success) {
105 errstr = strerror(errno);
106 } else if (ctx->write && (rdval & 0xffffffff) != (wrval & 0xffffffff)) {
107 errstr = "Values do not equal";
108 }
109
110 if (errstr) {
111 ctx->success = false;
112 printf("%s (%d): %s\n", uv->title, uv->index, errstr);
113 } else {
114 float val = ((mask - (rdval >> 21)) & (mask - 1)) / 1.024f;
115 printf("%s (%d): -%.02f mV\n", uv->title, uv->index, val);
116 }
117 }
118
undervolt(config_t * config,bool * nl,bool write)119 static bool undervolt(config_t * config, bool * nl, bool write) {
120 if (config->uv) {
121 newline(nl);
122 undervolt_ctx ctx;
123 ctx.config = config;
124 ctx.write = write;
125 ctx.success = true;
126 uv_list_foreach(config->uv, undervolt_it, &ctx);
127 return ctx.success;
128 } else {
129 return true;
130 }
131 }
132
tdp_to_seconds(int value,int time_unit)133 static float tdp_to_seconds(int value, int time_unit) {
134 float multiplier = 1 + ((value >> 6) & 0x3) / 4.f;
135 int exponent = (value >> 1) & 0x1f;
136 return exp2f(exponent) * multiplier / time_unit;
137 }
138
tdp_from_seconds(float seconds,int time_unit)139 static int tdp_from_seconds(float seconds, int time_unit) {
140 if (log2f(seconds * time_unit / 1.75f) >= 0x1f) {
141 return 0xfe;
142 } else {
143 int i;
144 float last_diff = 1.f;
145 int last_result = 0;
146 for (i = 0; i < 4; i++) {
147 float multiplier = 1 + (i / 4.f);
148 float value = seconds * time_unit / multiplier;
149 float exponent = log2f(value);
150 int exponent_int = (int) exponent;
151 float diff = exponent - exponent_int;
152 if (exponent_int < 0x19 && diff > 0.5f) {
153 exponent_int++;
154 diff = 1.f - diff;
155 }
156 if (exponent_int < 0x20) {
157 if (diff < last_diff) {
158 last_diff = diff;
159 last_result = (i << 6) | (exponent_int << 1);
160 }
161 }
162 }
163 return last_result;
164 }
165 }
166
tdp(config_t * config,bool * nl,bool write)167 static bool tdp(config_t * config, bool * nl, bool write) {
168 if (config->tdp_apply) {
169 newline(nl);
170 void * mem = config->tdp_mem + (MEM_ADDR_TDP & MAP_MASK);
171 const char * errstr = NULL;
172
173 uint64_t msr_limit;
174 uint64_t mchbar_limit;
175 uint64_t units;
176 if (rd(config, MSR_ADDR_TDP, msr_limit)) {
177 if (!safe_read_write(mem, &mchbar_limit, false)) {
178 errstr = "Segmentation fault";
179 } else {
180 if (!rd(config, MSR_ADDR_UNITS, units)) {
181 errstr = strerror(errno);
182 }
183 }
184 } else {
185 errstr = strerror(errno);
186 }
187
188 if (errstr) {
189 printf("Failed to read TDP values: %s\n", errstr);
190 } else {
191 int power_unit = (int) (exp2f(units & 0xf) + 0.5f);
192 int time_unit = (int) (exp2f((units >> 16) & 0xf) + 0.5f);
193
194 if (write) {
195 int max_tdp = 0x7fff / power_unit;
196 uint64_t masked = msr_limit & 0xffff8000ffff8000;
197 uint64_t short_term = config->tdp_short_term < 0 ? 0 :
198 config->tdp_short_term > max_tdp ? max_tdp :
199 config->tdp_short_term * power_unit;
200 uint64_t long_term = config->tdp_long_term < 0 ? 0 :
201 config->tdp_long_term > max_tdp ? max_tdp :
202 config->tdp_long_term * power_unit;
203 uint64_t value = masked | (short_term << 32) | long_term;
204 uint64_t time;
205 if (config->tdp_short_time_window > 0) {
206 masked = value & 0xff01ffffffffffff;
207 time = tdp_from_seconds(config->tdp_short_time_window,
208 time_unit);
209 value = masked | (time << 48);
210 }
211 if (config->tdp_long_time_window > 0) {
212 masked = value & 0xffffffffff01ffff;
213 time = tdp_from_seconds(config->tdp_long_time_window,
214 time_unit);
215 value = masked | (time << 16);
216 }
217 if (wr(config, MSR_ADDR_TDP, value)) {
218 msr_limit = value;
219 if (!safe_read_write(mem, &value, true)) {
220 errstr = "Segmentation fault";
221 }
222 } else {
223 errstr = strerror(errno);
224 }
225 } else if (msr_limit != mchbar_limit) {
226 printf("Warning: MSR and MCHBAR values are not equal\n");
227 }
228
229 if (errstr) {
230 printf("Failed to write TDP values: %s\n", errstr);
231 } else if (nl) {
232 if ((msr_limit >> 63) & 0x1) {
233 printf("Warning: power limit is locked\n");
234 }
235 int short_term = ((msr_limit >> 32) & 0x7fff) / power_unit;
236 int long_term = (msr_limit & 0x7fff) / power_unit;
237 bool short_term_enabled = !!((msr_limit >> 47) & 1);
238 bool long_term_enabled = !!((msr_limit >> 15) & 1);
239 float short_term_window = tdp_to_seconds(msr_limit >> 48,
240 time_unit);
241 float long_term_window = tdp_to_seconds(msr_limit >> 16,
242 time_unit);
243 printf("Short term TDP: %d W, %.03f s, %s\n",
244 short_term, short_term_window,
245 (short_term_enabled ? "enabled" : "disabled"));
246 printf("Long term TDP: %d W, %.03f s, %s\n",
247 long_term, long_term_window,
248 (long_term_enabled ? "enabled" : "disabled"));
249 }
250 }
251
252 return errstr == NULL;
253 } else {
254 return true;
255 }
256 }
257
tjoffset(config_t * config,bool * nl,bool write)258 static bool tjoffset(config_t * config, bool * nl, bool write) {
259 if (config->tjoffset_apply) {
260 newline(nl);
261 const char * errstr = NULL;
262
263 if (write) {
264 uint64_t limit;
265 if (rd(config, MSR_ADDR_TEMPERATURE, limit)) {
266 uint64_t offset = abs(config->tjoffset);
267 offset = offset > 0x3f ? 0x3f : offset;
268 limit = (limit & 0xffffffffc0ffffff) | (offset << 24);
269 if (!wr(config, MSR_ADDR_TEMPERATURE, limit)) {
270 errstr = strerror(errno);
271 }
272 } else {
273 errstr = strerror(errno);
274 }
275 }
276
277 if (errstr) {
278 printf("Failed to write temperature offset: %s\n", errstr);
279 } else if (nl) {
280 uint64_t limit;
281 if (rd(config, MSR_ADDR_TEMPERATURE, limit)) {
282 int offset = (limit & 0x3f000000) >> 24;
283 printf("Critical offset: -%d°C\n", offset);
284 } else {
285 printf("Failed to read temperature offset: %s\n", errstr);
286 }
287 }
288
289 return errstr == NULL;
290 } else {
291 return true;
292 }
293 }
294
read_apply(bool write)295 static bool read_apply(bool write) {
296 config_t * config = load_config(NULL);
297 if (config) {
298 bool nl = false;
299 bool success = true;
300 success &= undervolt(config, &nl, write);
301 success &= tdp(config, &nl, write);
302 success &= tjoffset(config, &nl, write);
303 free_config(config);
304 return success;
305 } else {
306 fprintf(stderr, "Failed to setup the program\n");
307 return false;
308 }
309 }
310
311 static bool reload_config;
312
sigusr1_handler(int sig)313 static void sigusr1_handler(int sig) {
314 reload_config = true;
315 }
316
daemon_mode()317 static int daemon_mode() {
318 config_t * config = load_config(NULL);
319 if (config && config->interval <= 0) {
320 fprintf(stderr, "Interval is not specified\n");
321 free_config(config);
322 config = NULL;
323 }
324 if (config) {
325 struct sigaction act;
326 memset(&act, 0, sizeof(struct sigaction));
327 act.sa_handler = sigusr1_handler;
328 sigaction(SIGUSR1, &act, NULL);
329
330 reload_config = false;
331 while (true) {
332 if (reload_config) {
333 reload_config = false;
334 printf("Reloading configuration\n");
335 config = load_config(config);
336 if (config == NULL) {
337 break;
338 }
339 }
340
341 tdp(config, NULL, true);
342 tjoffset(config, NULL, true);
343
344 usleep(config->interval * 1000);
345 }
346 }
347
348 if (config == NULL) {
349 fprintf(stderr, "Failed to setup the program\n");
350 return false;
351 } else {
352 free_config(config);
353 return true;
354 }
355 }
356
main(int argc,char ** argv)357 int main(int argc, char ** argv) {
358 bool write;
359 if (argc == 2 && !strcmp(argv[1], "read")) {
360 return read_apply(false) ? 0 : 1;
361 } else if (argc == 2 && !strcmp(argv[1], "apply")) {
362 return read_apply(true) ? 0 : 1;
363 } else if (argc == 2 && !strcmp(argv[1], "daemon")) {
364 return daemon_mode() ? 0 : 1;
365 } else {
366 fprintf(stderr,
367 "Usage: intel-undervolt COMMAND\n"
368 " read Read and display current values\n"
369 " apply Apply values from config file\n"
370 " daemon Run in daemon mode\n");
371 return argc == 1 ? 0 : 1;
372 }
373 }
374