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