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