xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 38069501)
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 #define	FLAG_N	0x08
64 
65 #define	OP_INVAL	0x00
66 #define	OP_READ		0x01
67 #define	OP_WRITE	0x02
68 #define	OP_OR		0x04
69 #define	OP_AND		0x08
70 
71 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
72 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
73 
74 /*
75  * Macros for freeing SLISTs, probably must be in /sys/queue.h
76  */
77 #define	SLIST_FREE(head, field, freef) do {				\
78 		typeof(SLIST_FIRST(head)) __elm0;			\
79 		typeof(SLIST_FIRST(head)) __elm;			\
80 		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
81 			(void)(freef)(__elm);				\
82 } while(0);
83 
84 struct datadir {
85 	const char		*path;
86 	SLIST_ENTRY(datadir)	next;
87 };
88 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
89 
90 static struct ucode_handler {
91 	ucode_probe_t *probe;
92 	ucode_update_t *update;
93 } handlers[] = {
94 	{ intel_probe, intel_update },
95 	{ amd10h_probe, amd10h_update },
96 	{ amd_probe, amd_update },
97 	{ via_probe, via_update },
98 };
99 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
100 
101 static void	usage(void);
102 static int	isdir(const char *path);
103 static int	do_cpuid(const char *cmdarg, const char *dev);
104 static int	do_cpuid_count(const char *cmdarg, const char *dev);
105 static int	do_msr(const char *cmdarg, const char *dev);
106 static int	do_update(const char *dev);
107 static void	datadir_add(const char *path);
108 
109 static void __dead2
110 usage(void)
111 {
112 	const char *name;
113 
114 	name = getprogname();
115 	if (name == NULL)
116 		name = "cpuctl";
117 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
118 	    "-i level | -i level,level_type | -u] device\n", name);
119 	exit(EX_USAGE);
120 }
121 
122 static int
123 isdir(const char *path)
124 {
125 	int error;
126 	struct stat st;
127 
128 	error = stat(path, &st);
129 	if (error < 0) {
130 		WARN(0, "stat(%s)", path);
131 		return (error);
132 	}
133 	return (st.st_mode & S_IFDIR);
134 }
135 
136 static int
137 do_cpuid(const char *cmdarg, const char *dev)
138 {
139 	unsigned int level;
140 	cpuctl_cpuid_args_t args;
141 	int fd, error;
142 	char *endptr;
143 
144 	assert(cmdarg != NULL);
145 	assert(dev != NULL);
146 
147 	level = strtoul(cmdarg, &endptr, 16);
148 	if (*cmdarg == '\0' || *endptr != '\0') {
149 		WARNX(0, "incorrect operand: %s", cmdarg);
150 		usage();
151 		/* NOTREACHED */
152 	}
153 
154 	/*
155 	 * Fill ioctl argument structure.
156 	 */
157 	args.level = level;
158 	fd = open(dev, O_RDONLY);
159 	if (fd < 0) {
160 		WARN(0, "error opening %s for reading", dev);
161 		return (1);
162 	}
163 	error = ioctl(fd, CPUCTL_CPUID, &args);
164 	if (error < 0) {
165 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
166 		close(fd);
167 		return (error);
168 	}
169 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
170 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
171 	close(fd);
172 	return (0);
173 }
174 
175 static int
176 do_cpuid_count(const char *cmdarg, const char *dev)
177 {
178 	char *cmdarg1, *endptr, *endptr1;
179 	unsigned int level, level_type;
180 	cpuctl_cpuid_count_args_t args;
181 	int fd, error;
182 
183 	assert(cmdarg != NULL);
184 	assert(dev != NULL);
185 
186 	level = strtoul(cmdarg, &endptr, 16);
187 	if (*cmdarg == '\0' || *endptr == '\0') {
188 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
189 		usage();
190 		/* NOTREACHED */
191 	}
192 	/* Locate the comma... */
193 	cmdarg1 = strstr(endptr, ",");
194 	/* ... and skip past it */
195 	cmdarg1 += 1;
196 	level_type = strtoul(cmdarg1, &endptr1, 16);
197 	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
198 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
199 		usage();
200 		/* NOTREACHED */
201 	}
202 
203 	/*
204 	 * Fill ioctl argument structure.
205 	 */
206 	args.level = level;
207 	args.level_type = level_type;
208 	fd = open(dev, O_RDONLY);
209 	if (fd < 0) {
210 		WARN(0, "error opening %s for reading", dev);
211 		return (1);
212 	}
213 	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
214 	if (error < 0) {
215 		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
216 		close(fd);
217 		return (error);
218 	}
219 	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
220 	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
221 	    args.data[2], args.data[3]);
222 	close(fd);
223 	return (0);
224 }
225 
226 static int
227 do_msr(const char *cmdarg, const char *dev)
228 {
229 	unsigned int msr;
230 	cpuctl_msr_args_t args;
231 	size_t len;
232 	uint64_t data = 0;
233 	unsigned long command;
234 	int do_invert = 0, op;
235 	int fd, error;
236 	const char *command_name;
237 	char *endptr;
238 	char *p;
239 
240 	assert(cmdarg != NULL);
241 	assert(dev != NULL);
242 	len = strlen(cmdarg);
243 	if (len == 0) {
244 		WARNX(0, "MSR register expected");
245 		usage();
246 		/* NOTREACHED */
247 	}
248 
249 	/*
250 	 * Parse command string.
251 	 */
252 	msr = strtoul(cmdarg, &endptr, 16);
253 	switch (*endptr) {
254 	case '\0':
255 		op = OP_READ;
256 		break;
257 	case '=':
258 		op = OP_WRITE;
259 		break;
260 	case '&':
261 		op = OP_AND;
262 		endptr++;
263 		break;
264 	case '|':
265 		op = OP_OR;
266 		endptr++;
267 		break;
268 	default:
269 		op = OP_INVAL;
270 	}
271 	if (op != OP_READ) {	/* Complex operation. */
272 		if (*endptr != '=')
273 			op = OP_INVAL;
274 		else {
275 			p = ++endptr;
276 			if (*p == '~') {
277 				do_invert = 1;
278 				p++;
279 			}
280 			data = strtoull(p, &endptr, 16);
281 			if (*p == '\0' || *endptr != '\0') {
282 				WARNX(0, "argument required: %s", cmdarg);
283 				usage();
284 				/* NOTREACHED */
285 			}
286 		}
287 	}
288 	if (op == OP_INVAL) {
289 		WARNX(0, "invalid operator: %s", cmdarg);
290 		usage();
291 		/* NOTREACHED */
292 	}
293 
294 	/*
295 	 * Fill ioctl argument structure.
296 	 */
297 	args.msr = msr;
298 	if ((do_invert != 0) ^ (op == OP_AND))
299 		args.data = ~data;
300 	else
301 		args.data = data;
302 	switch (op) {
303 	case OP_READ:
304 		command = CPUCTL_RDMSR;
305 		command_name = "RDMSR";
306 		break;
307 	case OP_WRITE:
308 		command = CPUCTL_WRMSR;
309 		command_name = "WRMSR";
310 		break;
311 	case OP_OR:
312 		command = CPUCTL_MSRSBIT;
313 		command_name = "MSRSBIT";
314 		break;
315 	case OP_AND:
316 		command = CPUCTL_MSRCBIT;
317 		command_name = "MSRCBIT";
318 		break;
319 	default:
320 		abort();
321 	}
322 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
323 	if (fd < 0) {
324 		WARN(0, "error opening %s for %s", dev,
325 		    op == OP_READ ? "reading" : "writing");
326 		return (1);
327 	}
328 	error = ioctl(fd, command, &args);
329 	if (error < 0) {
330 		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
331 		close(fd);
332 		return (1);
333 	}
334 	if (op == OP_READ)
335 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
336 		    HIGH(args.data), LOW(args.data));
337 	close(fd);
338 	return (0);
339 }
340 
341 static int
342 do_update(const char *dev)
343 {
344 	int fd;
345 	unsigned int i;
346 	int error;
347 	struct ucode_handler *handler;
348 	struct datadir *dir;
349 	DIR *dirp;
350 	struct dirent *direntry;
351 	char buf[MAXPATHLEN];
352 
353 	fd = open(dev, O_RDONLY);
354 	if (fd < 0) {
355 		WARN(0, "error opening %s for reading", dev);
356 		return (1);
357 	}
358 
359 	/*
360 	 * Find the appropriate handler for device.
361 	 */
362 	for (i = 0; i < NHANDLERS; i++)
363 		if (handlers[i].probe(fd) == 0)
364 			break;
365 	if (i < NHANDLERS)
366 		handler = &handlers[i];
367 	else {
368 		WARNX(0, "cannot find the appropriate handler for device");
369 		close(fd);
370 		return (1);
371 	}
372 	close(fd);
373 
374 	/*
375 	 * Process every image in specified data directories.
376 	 */
377 	SLIST_FOREACH(dir, &datadirs, next) {
378 		dirp = opendir(dir->path);
379 		if (dirp == NULL) {
380 			WARNX(1, "skipping directory %s: not accessible", dir->path);
381 			continue;
382 		}
383 		while ((direntry = readdir(dirp)) != NULL) {
384 			if (direntry->d_namlen == 0)
385 				continue;
386 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
387 			    direntry->d_name);
388 			if ((unsigned)error >= sizeof(buf))
389 				WARNX(0, "skipping %s, buffer too short",
390 				    direntry->d_name);
391 			if (isdir(buf) != 0) {
392 				WARNX(2, "skipping %s: is a directory", buf);
393 				continue;
394 			}
395 			handler->update(dev, buf);
396 		}
397 		error = closedir(dirp);
398 		if (error != 0)
399 			WARN(0, "closedir(%s)", dir->path);
400 	}
401 	return (0);
402 }
403 
404 /*
405  * Add new data directory to the search list.
406  */
407 static void
408 datadir_add(const char *path)
409 {
410 	struct datadir *newdir;
411 
412 	newdir = (struct datadir *)malloc(sizeof(*newdir));
413 	if (newdir == NULL)
414 		err(EX_OSERR, "cannot allocate memory");
415 	newdir->path = path;
416 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
417 }
418 
419 int
420 main(int argc, char *argv[])
421 {
422 	int c, flags;
423 	const char *cmdarg;
424 	const char *dev;
425 	int error;
426 
427 	flags = 0;
428 	error = 0;
429 	cmdarg = "";	/* To keep gcc3 happy. */
430 
431 	while ((c = getopt(argc, argv, "d:hi:m:nuv")) != -1) {
432 		switch (c) {
433 		case 'd':
434 			datadir_add(optarg);
435 			break;
436 		case 'i':
437 			flags |= FLAG_I;
438 			cmdarg = optarg;
439 			break;
440 		case 'm':
441 			flags |= FLAG_M;
442 			cmdarg = optarg;
443 			break;
444 		case 'n':
445 			flags |= FLAG_N;
446 			break;
447 		case 'u':
448 			flags |= FLAG_U;
449 			break;
450 		case 'v':
451 			verbosity_level++;
452 			break;
453 		case 'h':
454 			/* FALLTHROUGH */
455 		default:
456 			usage();
457 			/* NOTREACHED */
458 		}
459 	}
460 	argc -= optind;
461 	argv += optind;
462 	if (argc < 1) {
463 		usage();
464 		/* NOTREACHED */
465 	}
466 	if ((flags & FLAG_N) == 0)
467 		datadir_add(DEFAULT_DATADIR);
468 	dev = argv[0];
469 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
470 	switch (c) {
471 		case FLAG_I:
472 			if (strstr(cmdarg, ",") != NULL)
473 				error = do_cpuid_count(cmdarg, dev);
474 			else
475 				error = do_cpuid(cmdarg, dev);
476 			break;
477 		case FLAG_M:
478 			error = do_msr(cmdarg, dev);
479 			break;
480 		case FLAG_U:
481 			error = do_update(dev);
482 			break;
483 		default:
484 			usage();	/* Only one command can be selected. */
485 	}
486 	SLIST_FREE(&datadirs, next, free);
487 	return (error == 0 ? 0 : 1);
488 }
489