xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 273c26a3c3bea87a241d6879abd4f991db180bf0)
1 /*-
2  * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * This utility provides userland access to the cpuctl(4) pseudo-device
28  * features.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <assert.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <err.h>
41 #include <sysexits.h>
42 #include <dirent.h>
43 
44 #include <sys/queue.h>
45 #include <sys/param.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/ioctl.h>
49 #include <sys/cpuctl.h>
50 
51 #include "cpucontrol.h"
52 #include "amd.h"
53 #include "intel.h"
54 #include "via.h"
55 
56 int	verbosity_level = 0;
57 
58 #define	DEFAULT_DATADIR	"/usr/local/share/cpucontrol"
59 
60 #define	FLAG_I	0x01
61 #define	FLAG_M	0x02
62 #define	FLAG_U	0x04
63 
64 #define	OP_INVAL	0x00
65 #define	OP_READ		0x01
66 #define	OP_WRITE	0x02
67 #define	OP_OR		0x04
68 #define	OP_AND		0x08
69 
70 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
71 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
72 
73 /*
74  * Macros for freeing SLISTs, probably must be in /sys/queue.h
75  */
76 #define	SLIST_FREE(head, field, freef) do {				\
77 		typeof(SLIST_FIRST(head)) __elm0;			\
78 		typeof(SLIST_FIRST(head)) __elm;			\
79 		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
80 			(void)(freef)(__elm);				\
81 } while(0);
82 
83 struct datadir {
84 	const char		*path;
85 	SLIST_ENTRY(datadir)	next;
86 };
87 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
88 
89 static struct ucode_handler {
90 	ucode_probe_t *probe;
91 	ucode_update_t *update;
92 } handlers[] = {
93 	{ intel_probe, intel_update },
94 	{ amd_probe, amd_update },
95 	{ via_probe, via_update },
96 };
97 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
98 
99 static void	usage(void);
100 static int	isdir(const char *path);
101 static int	do_cpuid(const char *cmdarg, const char *dev);
102 static int	do_cpuid_count(const char *cmdarg, const char *dev);
103 static int	do_msr(const char *cmdarg, const char *dev);
104 static int	do_update(const char *dev);
105 static void	datadir_add(const char *path);
106 
107 static void __dead2
108 usage(void)
109 {
110 	const char *name;
111 
112 	name = getprogname();
113 	if (name == NULL)
114 		name = "cpuctl";
115 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
116 	    "-i level | -i level,level_type | -u] device\n", name);
117 	exit(EX_USAGE);
118 }
119 
120 static int
121 isdir(const char *path)
122 {
123 	int error;
124 	struct stat st;
125 
126 	error = stat(path, &st);
127 	if (error < 0) {
128 		WARN(0, "stat(%s)", path);
129 		return (error);
130 	}
131 	return (st.st_mode & S_IFDIR);
132 }
133 
134 static int
135 do_cpuid(const char *cmdarg, const char *dev)
136 {
137 	unsigned int level;
138 	cpuctl_cpuid_args_t args;
139 	int fd, error;
140 	char *endptr;
141 
142 	assert(cmdarg != NULL);
143 	assert(dev != NULL);
144 
145 	level = strtoul(cmdarg, &endptr, 16);
146 	if (*cmdarg == '\0' || *endptr != '\0') {
147 		WARNX(0, "incorrect operand: %s", cmdarg);
148 		usage();
149 		/* NOTREACHED */
150 	}
151 
152 	/*
153 	 * Fill ioctl argument structure.
154 	 */
155 	args.level = level;
156 	fd = open(dev, O_RDONLY);
157 	if (fd < 0) {
158 		WARN(0, "error opening %s for reading", dev);
159 		return (1);
160 	}
161 	error = ioctl(fd, CPUCTL_CPUID, &args);
162 	if (error < 0) {
163 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
164 		close(fd);
165 		return (error);
166 	}
167 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
168 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
169 	close(fd);
170 	return (0);
171 }
172 
173 static int
174 do_cpuid_count(const char *cmdarg, const char *dev)
175 {
176 	char *cmdarg1, *endptr, *endptr1;
177 	unsigned int level, level_type;
178 	cpuctl_cpuid_count_args_t args;
179 	int fd, error;
180 
181 	assert(cmdarg != NULL);
182 	assert(dev != NULL);
183 
184 	level = strtoul(cmdarg, &endptr, 16);
185 	if (*cmdarg == '\0' || *endptr == '\0') {
186 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
187 		usage();
188 		/* NOTREACHED */
189 	}
190 	/* Locate the comma... */
191 	cmdarg1 = strstr(endptr, ",");
192 	/* ... and skip past it */
193 	cmdarg1 += 1;
194 	level_type = strtoul(cmdarg1, &endptr1, 16);
195 	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
196 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
197 		usage();
198 		/* NOTREACHED */
199 	}
200 
201 	/*
202 	 * Fill ioctl argument structure.
203 	 */
204 	args.level = level;
205 	args.level_type = level_type;
206 	fd = open(dev, O_RDONLY);
207 	if (fd < 0) {
208 		WARN(0, "error opening %s for reading", dev);
209 		return (1);
210 	}
211 	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
212 	if (error < 0) {
213 		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
214 		close(fd);
215 		return (error);
216 	}
217 	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
218 	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
219 	    args.data[2], args.data[3]);
220 	close(fd);
221 	return (0);
222 }
223 
224 static int
225 do_msr(const char *cmdarg, const char *dev)
226 {
227 	unsigned int msr;
228 	cpuctl_msr_args_t args;
229 	size_t len;
230 	uint64_t data = 0;
231 	unsigned long command;
232 	int do_invert = 0, op;
233 	int fd, error;
234 	const char *command_name;
235 	char *endptr;
236 	char *p;
237 
238 	assert(cmdarg != NULL);
239 	assert(dev != NULL);
240 	len = strlen(cmdarg);
241 	if (len == 0) {
242 		WARNX(0, "MSR register expected");
243 		usage();
244 		/* NOTREACHED */
245 	}
246 
247 	/*
248 	 * Parse command string.
249 	 */
250 	msr = strtoul(cmdarg, &endptr, 16);
251 	switch (*endptr) {
252 	case '\0':
253 		op = OP_READ;
254 		break;
255 	case '=':
256 		op = OP_WRITE;
257 		break;
258 	case '&':
259 		op = OP_AND;
260 		endptr++;
261 		break;
262 	case '|':
263 		op = OP_OR;
264 		endptr++;
265 		break;
266 	default:
267 		op = OP_INVAL;
268 	}
269 	if (op != OP_READ) {	/* Complex operation. */
270 		if (*endptr != '=')
271 			op = OP_INVAL;
272 		else {
273 			p = ++endptr;
274 			if (*p == '~') {
275 				do_invert = 1;
276 				p++;
277 			}
278 			data = strtoull(p, &endptr, 16);
279 			if (*p == '\0' || *endptr != '\0') {
280 				WARNX(0, "argument required: %s", cmdarg);
281 				usage();
282 				/* NOTREACHED */
283 			}
284 		}
285 	}
286 	if (op == OP_INVAL) {
287 		WARNX(0, "invalid operator: %s", cmdarg);
288 		usage();
289 		/* NOTREACHED */
290 	}
291 
292 	/*
293 	 * Fill ioctl argument structure.
294 	 */
295 	args.msr = msr;
296 	if ((do_invert != 0) ^ (op == OP_AND))
297 		args.data = ~data;
298 	else
299 		args.data = data;
300 	switch (op) {
301 	case OP_READ:
302 		command = CPUCTL_RDMSR;
303 		command_name = "RDMSR";
304 		break;
305 	case OP_WRITE:
306 		command = CPUCTL_WRMSR;
307 		command_name = "WRMSR";
308 		break;
309 	case OP_OR:
310 		command = CPUCTL_MSRSBIT;
311 		command_name = "MSRSBIT";
312 		break;
313 	case OP_AND:
314 		command = CPUCTL_MSRCBIT;
315 		command_name = "MSRCBIT";
316 		break;
317 	default:
318 		abort();
319 	}
320 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
321 	if (fd < 0) {
322 		WARN(0, "error opening %s for %s", dev,
323 		    op == OP_READ ? "reading" : "writing");
324 		return (1);
325 	}
326 	error = ioctl(fd, command, &args);
327 	if (error < 0) {
328 		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
329 		close(fd);
330 		return (1);
331 	}
332 	if (op == OP_READ)
333 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
334 		    HIGH(args.data), LOW(args.data));
335 	close(fd);
336 	return (0);
337 }
338 
339 static int
340 do_update(const char *dev)
341 {
342 	int fd;
343 	unsigned int i;
344 	int error;
345 	struct ucode_handler *handler;
346 	struct datadir *dir;
347 	DIR *dirp;
348 	struct dirent *direntry;
349 	char buf[MAXPATHLEN];
350 
351 	fd = open(dev, O_RDONLY);
352 	if (fd < 0) {
353 		WARN(0, "error opening %s for reading", dev);
354 		return (1);
355 	}
356 
357 	/*
358 	 * Find the appropriate handler for device.
359 	 */
360 	for (i = 0; i < NHANDLERS; i++)
361 		if (handlers[i].probe(fd) == 0)
362 			break;
363 	if (i < NHANDLERS)
364 		handler = &handlers[i];
365 	else {
366 		WARNX(0, "cannot find the appropriate handler for device");
367 		close(fd);
368 		return (1);
369 	}
370 	close(fd);
371 
372 	/*
373 	 * Process every image in specified data directories.
374 	 */
375 	SLIST_FOREACH(dir, &datadirs, next) {
376 		dirp = opendir(dir->path);
377 		if (dirp == NULL) {
378 			WARNX(1, "skipping directory %s: not accessible", dir->path);
379 			continue;
380 		}
381 		while ((direntry = readdir(dirp)) != NULL) {
382 			if (direntry->d_namlen == 0)
383 				continue;
384 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
385 			    direntry->d_name);
386 			if ((unsigned)error >= sizeof(buf))
387 				WARNX(0, "skipping %s, buffer too short",
388 				    direntry->d_name);
389 			if (isdir(buf) != 0) {
390 				WARNX(2, "skipping %s: is a directory", buf);
391 				continue;
392 			}
393 			handler->update(dev, buf);
394 		}
395 		error = closedir(dirp);
396 		if (error != 0)
397 			WARN(0, "closedir(%s)", dir->path);
398 	}
399 	return (0);
400 }
401 
402 /*
403  * Add new data directory to the search list.
404  */
405 static void
406 datadir_add(const char *path)
407 {
408 	struct datadir *newdir;
409 
410 	newdir = (struct datadir *)malloc(sizeof(*newdir));
411 	if (newdir == NULL)
412 		err(EX_OSERR, "cannot allocate memory");
413 	newdir->path = path;
414 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
415 }
416 
417 int
418 main(int argc, char *argv[])
419 {
420 	int c, flags;
421 	const char *cmdarg;
422 	const char *dev;
423 	int error;
424 
425 	flags = 0;
426 	error = 0;
427 	cmdarg = "";	/* To keep gcc3 happy. */
428 
429 	/*
430 	 * Add all default data dirs to the list first.
431 	 */
432 	datadir_add(DEFAULT_DATADIR);
433 	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
434 		switch (c) {
435 		case 'd':
436 			datadir_add(optarg);
437 			break;
438 		case 'i':
439 			flags |= FLAG_I;
440 			cmdarg = optarg;
441 			break;
442 		case 'm':
443 			flags |= FLAG_M;
444 			cmdarg = optarg;
445 			break;
446 		case 'u':
447 			flags |= FLAG_U;
448 			break;
449 		case 'v':
450 			verbosity_level++;
451 			break;
452 		case 'h':
453 			/* FALLTHROUGH */
454 		default:
455 			usage();
456 			/* NOTREACHED */
457 		}
458 	}
459 	argc -= optind;
460 	argv += optind;
461 	if (argc < 1) {
462 		usage();
463 		/* NOTREACHED */
464 	}
465 	dev = argv[0];
466 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
467 	switch (c) {
468 		case FLAG_I:
469 			if (strstr(cmdarg, ",") != NULL)
470 				error = do_cpuid_count(cmdarg, dev);
471 			else
472 				error = do_cpuid(cmdarg, dev);
473 			break;
474 		case FLAG_M:
475 			error = do_msr(cmdarg, dev);
476 			break;
477 		case FLAG_U:
478 			error = do_update(dev);
479 			break;
480 		default:
481 			usage();	/* Only one command can be selected. */
482 	}
483 	SLIST_FREE(&datadirs, next, free);
484 	return (error == 0 ? 0 : 1);
485 }
486