xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 7672c254ebf9af19b53423b47a4ba8d6cf5644ba)
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 <err.h>
38 #include <errno.h>
39 #include <dirent.h>
40 #include <fcntl.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <sysexits.h>
46 
47 #include <sys/queue.h>
48 #include <sys/param.h>
49 #include <sys/types.h>
50 #include <sys/mman.h>
51 #include <sys/stat.h>
52 #include <sys/ioctl.h>
53 #include <sys/cpuctl.h>
54 
55 #include "cpucontrol.h"
56 #include "amd.h"
57 #include "intel.h"
58 #include "via.h"
59 
60 int	verbosity_level = 0;
61 
62 #define	DEFAULT_DATADIR	"/usr/local/share/cpucontrol"
63 
64 #define	FLAG_I	0x01
65 #define	FLAG_M	0x02
66 #define	FLAG_U	0x04
67 #define	FLAG_N	0x08
68 #define	FLAG_E	0x10
69 
70 #define	OP_INVAL	0x00
71 #define	OP_READ		0x01
72 #define	OP_WRITE	0x02
73 #define	OP_OR		0x04
74 #define	OP_AND		0x08
75 
76 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
77 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
78 
79 struct datadir {
80 	const char		*path;
81 	SLIST_ENTRY(datadir)	next;
82 };
83 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
84 
85 static struct ucode_handler {
86 	ucode_probe_t *probe;
87 	ucode_update_t *update;
88 } handlers[] = {
89 	{ intel_probe, intel_update },
90 	{ amd10h_probe, amd10h_update },
91 	{ amd_probe, amd_update },
92 	{ via_probe, via_update },
93 };
94 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
95 
96 static void	usage(void);
97 static int	do_cpuid(const char *cmdarg, const char *dev);
98 static int	do_cpuid_count(const char *cmdarg, const char *dev);
99 static int	do_msr(const char *cmdarg, const char *dev);
100 static int	do_update(const char *dev);
101 static void	datadir_add(const char *path);
102 
103 static void __dead2
104 usage(void)
105 {
106 	const char *name;
107 
108 	name = getprogname();
109 	if (name == NULL)
110 		name = "cpuctl";
111 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
112 	    "-i level | -i level,level_type | -e | -u] device\n", name);
113 	exit(EX_USAGE);
114 }
115 
116 static int
117 do_cpuid(const char *cmdarg, const char *dev)
118 {
119 	unsigned int level;
120 	cpuctl_cpuid_args_t args;
121 	int fd, error;
122 	char *endptr;
123 
124 	assert(cmdarg != NULL);
125 	assert(dev != NULL);
126 
127 	level = strtoul(cmdarg, &endptr, 16);
128 	if (*cmdarg == '\0' || *endptr != '\0') {
129 		WARNX(0, "incorrect operand: %s", cmdarg);
130 		usage();
131 		/* NOTREACHED */
132 	}
133 
134 	/*
135 	 * Fill ioctl argument structure.
136 	 */
137 	args.level = level;
138 	fd = open(dev, O_RDONLY);
139 	if (fd < 0) {
140 		WARN(0, "error opening %s for reading", dev);
141 		return (1);
142 	}
143 	error = ioctl(fd, CPUCTL_CPUID, &args);
144 	if (error < 0) {
145 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
146 		close(fd);
147 		return (error);
148 	}
149 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
150 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
151 	close(fd);
152 	return (0);
153 }
154 
155 static int
156 do_cpuid_count(const char *cmdarg, const char *dev)
157 {
158 	char *cmdarg1, *endptr, *endptr1;
159 	unsigned int level, level_type;
160 	cpuctl_cpuid_count_args_t args;
161 	int fd, error;
162 
163 	assert(cmdarg != NULL);
164 	assert(dev != NULL);
165 
166 	level = strtoul(cmdarg, &endptr, 16);
167 	if (*cmdarg == '\0' || *endptr == '\0') {
168 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
169 		usage();
170 		/* NOTREACHED */
171 	}
172 	/* Locate the comma... */
173 	cmdarg1 = strstr(endptr, ",");
174 	/* ... and skip past it */
175 	cmdarg1 += 1;
176 	level_type = strtoul(cmdarg1, &endptr1, 16);
177 	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
178 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
179 		usage();
180 		/* NOTREACHED */
181 	}
182 
183 	/*
184 	 * Fill ioctl argument structure.
185 	 */
186 	args.level = level;
187 	args.level_type = level_type;
188 	fd = open(dev, O_RDONLY);
189 	if (fd < 0) {
190 		WARN(0, "error opening %s for reading", dev);
191 		return (1);
192 	}
193 	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
194 	if (error < 0) {
195 		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
196 		close(fd);
197 		return (error);
198 	}
199 	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
200 	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
201 	    args.data[2], args.data[3]);
202 	close(fd);
203 	return (0);
204 }
205 
206 static int
207 do_msr(const char *cmdarg, const char *dev)
208 {
209 	unsigned int msr;
210 	cpuctl_msr_args_t args;
211 	size_t len;
212 	uint64_t data = 0;
213 	unsigned long command;
214 	int do_invert = 0, op;
215 	int fd, error;
216 	const char *command_name;
217 	char *endptr;
218 	char *p;
219 
220 	assert(cmdarg != NULL);
221 	assert(dev != NULL);
222 	len = strlen(cmdarg);
223 	if (len == 0) {
224 		WARNX(0, "MSR register expected");
225 		usage();
226 		/* NOTREACHED */
227 	}
228 
229 	/*
230 	 * Parse command string.
231 	 */
232 	msr = strtoul(cmdarg, &endptr, 16);
233 	switch (*endptr) {
234 	case '\0':
235 		op = OP_READ;
236 		break;
237 	case '=':
238 		op = OP_WRITE;
239 		break;
240 	case '&':
241 		op = OP_AND;
242 		endptr++;
243 		break;
244 	case '|':
245 		op = OP_OR;
246 		endptr++;
247 		break;
248 	default:
249 		op = OP_INVAL;
250 	}
251 	if (op != OP_READ) {	/* Complex operation. */
252 		if (*endptr != '=')
253 			op = OP_INVAL;
254 		else {
255 			p = ++endptr;
256 			if (*p == '~') {
257 				do_invert = 1;
258 				p++;
259 			}
260 			data = strtoull(p, &endptr, 16);
261 			if (*p == '\0' || *endptr != '\0') {
262 				WARNX(0, "argument required: %s", cmdarg);
263 				usage();
264 				/* NOTREACHED */
265 			}
266 		}
267 	}
268 	if (op == OP_INVAL) {
269 		WARNX(0, "invalid operator: %s", cmdarg);
270 		usage();
271 		/* NOTREACHED */
272 	}
273 
274 	/*
275 	 * Fill ioctl argument structure.
276 	 */
277 	args.msr = msr;
278 	if ((do_invert != 0) ^ (op == OP_AND))
279 		args.data = ~data;
280 	else
281 		args.data = data;
282 	switch (op) {
283 	case OP_READ:
284 		command = CPUCTL_RDMSR;
285 		command_name = "RDMSR";
286 		break;
287 	case OP_WRITE:
288 		command = CPUCTL_WRMSR;
289 		command_name = "WRMSR";
290 		break;
291 	case OP_OR:
292 		command = CPUCTL_MSRSBIT;
293 		command_name = "MSRSBIT";
294 		break;
295 	case OP_AND:
296 		command = CPUCTL_MSRCBIT;
297 		command_name = "MSRCBIT";
298 		break;
299 	default:
300 		abort();
301 	}
302 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
303 	if (fd < 0) {
304 		WARN(0, "error opening %s for %s", dev,
305 		    op == OP_READ ? "reading" : "writing");
306 		return (1);
307 	}
308 	error = ioctl(fd, command, &args);
309 	if (error < 0) {
310 		WARN(0, "ioctl(%s, CPUCTL_%s (%#x))", dev, command_name, msr);
311 		close(fd);
312 		return (1);
313 	}
314 	if (op == OP_READ)
315 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
316 		    HIGH(args.data), LOW(args.data));
317 	close(fd);
318 	return (0);
319 }
320 
321 static int
322 do_eval_cpu_features(const char *dev)
323 {
324 	int fd, error;
325 
326 	assert(dev != NULL);
327 
328 	fd = open(dev, O_RDWR);
329 	if (fd < 0) {
330 		WARN(0, "error opening %s for writing", dev);
331 		return (1);
332 	}
333 	error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
334 	if (error < 0)
335 		WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
336 	close(fd);
337 	return (error);
338 }
339 
340 static int
341 try_a_fw_image(const char *dev_path, int devfd, int fwdfd, const char *dpath,
342     const char *fname, struct ucode_handler *handler)
343 {
344 	struct ucode_update_params parm;
345 	struct stat st;
346 	char *fw_path;
347 	void *fw_map;
348 	int fwfd, rc;
349 
350 	rc = 0;
351 	fw_path = NULL;
352 	fw_map = MAP_FAILED;
353 	fwfd = openat(fwdfd, fname, O_RDONLY);
354 	if (fwfd < 0) {
355 		WARN(0, "openat(%s, %s)", dpath, fname);
356 		goto out;
357 	}
358 
359 	rc = asprintf(&fw_path, "%s/%s", dpath, fname);
360 	if (rc == -1) {
361 		WARNX(0, "out of memory");
362 		rc = ENOMEM;
363 		goto out;
364 	}
365 
366 	rc = fstat(fwfd, &st);
367 	if (rc != 0) {
368 		WARN(0, "fstat(%s)", fw_path);
369 		rc = 0;
370 		goto out;
371 	}
372 	if (!S_ISREG(st.st_mode))
373 		goto out;
374 	if (st.st_size <= 0) {
375 		WARN(0, "%s: empty", fw_path);
376 		goto out;
377 	}
378 
379 	fw_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fwfd, 0);
380 	if (fw_map == MAP_FAILED) {
381 		WARN(0, "mmap(%s)", fw_path);
382 		goto out;
383 	}
384 
385 
386 	memset(&parm, 0, sizeof(parm));
387 	parm.devfd = devfd;
388 	parm.fwimage = fw_map;
389 	parm.fwsize = st.st_size;
390 	parm.dev_path = dev_path;
391 	parm.fw_path = fw_path;
392 
393 	handler->update(&parm);
394 
395 out:
396 	if (fw_map != MAP_FAILED)
397 		munmap(fw_map, st.st_size);
398 	free(fw_path);
399 	if (fwfd >= 0)
400 		close(fwfd);
401 	return (rc);
402 }
403 
404 static int
405 do_update(const char *dev)
406 {
407 	int fd, fwdfd;
408 	unsigned int i;
409 	int error;
410 	struct ucode_handler *handler;
411 	struct datadir *dir;
412 	DIR *dirp;
413 	struct dirent *direntry;
414 
415 	fd = open(dev, O_RDONLY);
416 	if (fd < 0) {
417 		WARN(0, "error opening %s for reading", dev);
418 		return (1);
419 	}
420 
421 	/*
422 	 * Find the appropriate handler for CPU.
423 	 */
424 	for (i = 0; i < NHANDLERS; i++)
425 		if (handlers[i].probe(fd) == 0)
426 			break;
427 	if (i < NHANDLERS)
428 		handler = &handlers[i];
429 	else {
430 		WARNX(0, "cannot find the appropriate handler for %s", dev);
431 		close(fd);
432 		return (1);
433 	}
434 	close(fd);
435 
436 	fd = open(dev, O_RDWR);
437 	if (fd < 0) {
438 		WARN(0, "error opening %s for writing", dev);
439 		return (1);
440 	}
441 
442 	/*
443 	 * Process every image in specified data directories.
444 	 */
445 	SLIST_FOREACH(dir, &datadirs, next) {
446 		fwdfd = open(dir->path, O_RDONLY);
447 		if (fwdfd < 0) {
448 			WARN(1, "skipping directory %s: not accessible", dir->path);
449 			continue;
450 		}
451 		dirp = fdopendir(fwdfd);
452 		if (dirp == NULL) {
453 			WARNX(0, "out of memory");
454 			close(fwdfd);
455 			close(fd);
456 			return (1);
457 		}
458 
459 		while ((direntry = readdir(dirp)) != NULL) {
460 			if (direntry->d_namlen == 0)
461 				continue;
462 			if (direntry->d_type == DT_DIR)
463 				continue;
464 
465 			error = try_a_fw_image(dev, fd, fwdfd, dir->path,
466 			    direntry->d_name, handler);
467 			if (error != 0) {
468 				closedir(dirp);
469 				close(fd);
470 				return (1);
471 			}
472 		}
473 		error = closedir(dirp);
474 		if (error != 0)
475 			WARN(0, "closedir(%s)", dir->path);
476 	}
477 	close(fd);
478 	return (0);
479 }
480 
481 /*
482  * Add new data directory to the search list.
483  */
484 static void
485 datadir_add(const char *path)
486 {
487 	struct datadir *newdir;
488 
489 	newdir = (struct datadir *)malloc(sizeof(*newdir));
490 	if (newdir == NULL)
491 		err(EX_OSERR, "cannot allocate memory");
492 	newdir->path = path;
493 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
494 }
495 
496 int
497 main(int argc, char *argv[])
498 {
499 	struct datadir *elm;
500 	int c, flags;
501 	const char *cmdarg;
502 	const char *dev;
503 	int error;
504 
505 	flags = 0;
506 	error = 0;
507 	cmdarg = "";	/* To keep gcc3 happy. */
508 
509 	while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) {
510 		switch (c) {
511 		case 'd':
512 			datadir_add(optarg);
513 			break;
514 		case 'e':
515 			flags |= FLAG_E;
516 			break;
517 		case 'i':
518 			flags |= FLAG_I;
519 			cmdarg = optarg;
520 			break;
521 		case 'm':
522 			flags |= FLAG_M;
523 			cmdarg = optarg;
524 			break;
525 		case 'n':
526 			flags |= FLAG_N;
527 			break;
528 		case 'u':
529 			flags |= FLAG_U;
530 			break;
531 		case 'v':
532 			verbosity_level++;
533 			break;
534 		case 'h':
535 			/* FALLTHROUGH */
536 		default:
537 			usage();
538 			/* NOTREACHED */
539 		}
540 	}
541 	argc -= optind;
542 	argv += optind;
543 	if (argc < 1) {
544 		usage();
545 		/* NOTREACHED */
546 	}
547 	if ((flags & FLAG_N) == 0)
548 		datadir_add(DEFAULT_DATADIR);
549 	dev = argv[0];
550 	c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U);
551 	switch (c) {
552 	case FLAG_I:
553 		if (strstr(cmdarg, ",") != NULL)
554 			error = do_cpuid_count(cmdarg, dev);
555 		else
556 			error = do_cpuid(cmdarg, dev);
557 		break;
558 	case FLAG_M:
559 		error = do_msr(cmdarg, dev);
560 		break;
561 	case FLAG_U:
562 		error = do_update(dev);
563 		break;
564 	case FLAG_E:
565 		error = do_eval_cpu_features(dev);
566 		break;
567 	default:
568 		usage();	/* Only one command can be selected. */
569 	}
570 	while ((elm = SLIST_FIRST(&datadirs)) != NULL) {
571 		SLIST_REMOVE_HEAD(&datadirs, next);
572 		free(elm);
573 	}
574 	return (error == 0 ? 0 : 1);
575 }
576