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