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