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