xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 97cb52fa9aefd90fad38790fded50905aeeb9b9e)
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 
67 #define	OP_INVAL	0x00
68 #define	OP_READ		0x01
69 #define	OP_WRITE	0x02
70 #define	OP_OR		0x04
71 #define	OP_AND		0x08
72 
73 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
74 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
75 
76 /*
77  * Macros for freeing SLISTs, probably must be in /sys/queue.h
78  */
79 #define	SLIST_FREE(head, field, freef) do {				\
80 		typeof(SLIST_FIRST(head)) __elm0;			\
81 		typeof(SLIST_FIRST(head)) __elm;			\
82 		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
83 			(void)(freef)(__elm);				\
84 } while(0);
85 
86 struct datadir {
87 	const char		*path;
88 	SLIST_ENTRY(datadir)	next;
89 };
90 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
91 
92 static struct ucode_handler {
93 	ucode_probe_t *probe;
94 	ucode_update_t *update;
95 } handlers[] = {
96 	{ intel_probe, intel_update },
97 	{ amd10h_probe, amd10h_update },
98 	{ amd_probe, amd_update },
99 	{ via_probe, via_update },
100 };
101 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
102 
103 static void	usage(void);
104 static int	isdir(const char *path);
105 static int	do_cpuid(const char *cmdarg, const char *dev);
106 static int	do_cpuid_count(const char *cmdarg, const char *dev);
107 static int	do_msr(const char *cmdarg, const char *dev);
108 static int	do_update(const char *dev);
109 static void	datadir_add(const char *path);
110 
111 static void __dead2
112 usage(void)
113 {
114 	const char *name;
115 
116 	name = getprogname();
117 	if (name == NULL)
118 		name = "cpuctl";
119 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
120 	    "-i level | -i level,level_type | -u] device\n", name);
121 	exit(EX_USAGE);
122 }
123 
124 static int
125 isdir(const char *path)
126 {
127 	int error;
128 	struct stat st;
129 
130 	error = stat(path, &st);
131 	if (error < 0) {
132 		WARN(0, "stat(%s)", path);
133 		return (error);
134 	}
135 	return (st.st_mode & S_IFDIR);
136 }
137 
138 static int
139 do_cpuid(const char *cmdarg, const char *dev)
140 {
141 	unsigned int level;
142 	cpuctl_cpuid_args_t args;
143 	int fd, error;
144 	char *endptr;
145 
146 	assert(cmdarg != NULL);
147 	assert(dev != NULL);
148 
149 	level = strtoul(cmdarg, &endptr, 16);
150 	if (*cmdarg == '\0' || *endptr != '\0') {
151 		WARNX(0, "incorrect operand: %s", cmdarg);
152 		usage();
153 		/* NOTREACHED */
154 	}
155 
156 	/*
157 	 * Fill ioctl argument structure.
158 	 */
159 	args.level = level;
160 	fd = open(dev, O_RDONLY);
161 	if (fd < 0) {
162 		WARN(0, "error opening %s for reading", dev);
163 		return (1);
164 	}
165 	error = ioctl(fd, CPUCTL_CPUID, &args);
166 	if (error < 0) {
167 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
168 		close(fd);
169 		return (error);
170 	}
171 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
172 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
173 	close(fd);
174 	return (0);
175 }
176 
177 static int
178 do_cpuid_count(const char *cmdarg, const char *dev)
179 {
180 	char *cmdarg1, *endptr, *endptr1;
181 	unsigned int level, level_type;
182 	cpuctl_cpuid_count_args_t args;
183 	int fd, error;
184 
185 	assert(cmdarg != NULL);
186 	assert(dev != NULL);
187 
188 	level = strtoul(cmdarg, &endptr, 16);
189 	if (*cmdarg == '\0' || *endptr == '\0') {
190 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
191 		usage();
192 		/* NOTREACHED */
193 	}
194 	/* Locate the comma... */
195 	cmdarg1 = strstr(endptr, ",");
196 	/* ... and skip past it */
197 	cmdarg1 += 1;
198 	level_type = strtoul(cmdarg1, &endptr1, 16);
199 	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
200 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
201 		usage();
202 		/* NOTREACHED */
203 	}
204 
205 	/*
206 	 * Fill ioctl argument structure.
207 	 */
208 	args.level = level;
209 	args.level_type = level_type;
210 	fd = open(dev, O_RDONLY);
211 	if (fd < 0) {
212 		WARN(0, "error opening %s for reading", dev);
213 		return (1);
214 	}
215 	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
216 	if (error < 0) {
217 		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
218 		close(fd);
219 		return (error);
220 	}
221 	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
222 	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
223 	    args.data[2], args.data[3]);
224 	close(fd);
225 	return (0);
226 }
227 
228 static int
229 do_msr(const char *cmdarg, const char *dev)
230 {
231 	unsigned int msr;
232 	cpuctl_msr_args_t args;
233 	size_t len;
234 	uint64_t data = 0;
235 	unsigned long command;
236 	int do_invert = 0, op;
237 	int fd, error;
238 	const char *command_name;
239 	char *endptr;
240 	char *p;
241 
242 	assert(cmdarg != NULL);
243 	assert(dev != NULL);
244 	len = strlen(cmdarg);
245 	if (len == 0) {
246 		WARNX(0, "MSR register expected");
247 		usage();
248 		/* NOTREACHED */
249 	}
250 
251 	/*
252 	 * Parse command string.
253 	 */
254 	msr = strtoul(cmdarg, &endptr, 16);
255 	switch (*endptr) {
256 	case '\0':
257 		op = OP_READ;
258 		break;
259 	case '=':
260 		op = OP_WRITE;
261 		break;
262 	case '&':
263 		op = OP_AND;
264 		endptr++;
265 		break;
266 	case '|':
267 		op = OP_OR;
268 		endptr++;
269 		break;
270 	default:
271 		op = OP_INVAL;
272 	}
273 	if (op != OP_READ) {	/* Complex operation. */
274 		if (*endptr != '=')
275 			op = OP_INVAL;
276 		else {
277 			p = ++endptr;
278 			if (*p == '~') {
279 				do_invert = 1;
280 				p++;
281 			}
282 			data = strtoull(p, &endptr, 16);
283 			if (*p == '\0' || *endptr != '\0') {
284 				WARNX(0, "argument required: %s", cmdarg);
285 				usage();
286 				/* NOTREACHED */
287 			}
288 		}
289 	}
290 	if (op == OP_INVAL) {
291 		WARNX(0, "invalid operator: %s", cmdarg);
292 		usage();
293 		/* NOTREACHED */
294 	}
295 
296 	/*
297 	 * Fill ioctl argument structure.
298 	 */
299 	args.msr = msr;
300 	if ((do_invert != 0) ^ (op == OP_AND))
301 		args.data = ~data;
302 	else
303 		args.data = data;
304 	switch (op) {
305 	case OP_READ:
306 		command = CPUCTL_RDMSR;
307 		command_name = "RDMSR";
308 		break;
309 	case OP_WRITE:
310 		command = CPUCTL_WRMSR;
311 		command_name = "WRMSR";
312 		break;
313 	case OP_OR:
314 		command = CPUCTL_MSRSBIT;
315 		command_name = "MSRSBIT";
316 		break;
317 	case OP_AND:
318 		command = CPUCTL_MSRCBIT;
319 		command_name = "MSRCBIT";
320 		break;
321 	default:
322 		abort();
323 	}
324 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
325 	if (fd < 0) {
326 		WARN(0, "error opening %s for %s", dev,
327 		    op == OP_READ ? "reading" : "writing");
328 		return (1);
329 	}
330 	error = ioctl(fd, command, &args);
331 	if (error < 0) {
332 		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
333 		close(fd);
334 		return (1);
335 	}
336 	if (op == OP_READ)
337 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
338 		    HIGH(args.data), LOW(args.data));
339 	close(fd);
340 	return (0);
341 }
342 
343 static int
344 do_update(const char *dev)
345 {
346 	int fd;
347 	unsigned int i;
348 	int error;
349 	struct ucode_handler *handler;
350 	struct datadir *dir;
351 	DIR *dirp;
352 	struct dirent *direntry;
353 	char buf[MAXPATHLEN];
354 
355 	fd = open(dev, O_RDONLY);
356 	if (fd < 0) {
357 		WARN(0, "error opening %s for reading", dev);
358 		return (1);
359 	}
360 
361 	/*
362 	 * Find the appropriate handler for device.
363 	 */
364 	for (i = 0; i < NHANDLERS; i++)
365 		if (handlers[i].probe(fd) == 0)
366 			break;
367 	if (i < NHANDLERS)
368 		handler = &handlers[i];
369 	else {
370 		WARNX(0, "cannot find the appropriate handler for device");
371 		close(fd);
372 		return (1);
373 	}
374 	close(fd);
375 
376 	/*
377 	 * Process every image in specified data directories.
378 	 */
379 	SLIST_FOREACH(dir, &datadirs, next) {
380 		dirp = opendir(dir->path);
381 		if (dirp == NULL) {
382 			WARNX(1, "skipping directory %s: not accessible", dir->path);
383 			continue;
384 		}
385 		while ((direntry = readdir(dirp)) != NULL) {
386 			if (direntry->d_namlen == 0)
387 				continue;
388 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
389 			    direntry->d_name);
390 			if ((unsigned)error >= sizeof(buf))
391 				WARNX(0, "skipping %s, buffer too short",
392 				    direntry->d_name);
393 			if (isdir(buf) != 0) {
394 				WARNX(2, "skipping %s: is a directory", buf);
395 				continue;
396 			}
397 			handler->update(dev, buf);
398 		}
399 		error = closedir(dirp);
400 		if (error != 0)
401 			WARN(0, "closedir(%s)", dir->path);
402 	}
403 	return (0);
404 }
405 
406 /*
407  * Add new data directory to the search list.
408  */
409 static void
410 datadir_add(const char *path)
411 {
412 	struct datadir *newdir;
413 
414 	newdir = (struct datadir *)malloc(sizeof(*newdir));
415 	if (newdir == NULL)
416 		err(EX_OSERR, "cannot allocate memory");
417 	newdir->path = path;
418 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
419 }
420 
421 int
422 main(int argc, char *argv[])
423 {
424 	int c, flags;
425 	const char *cmdarg;
426 	const char *dev;
427 	int error;
428 
429 	flags = 0;
430 	error = 0;
431 	cmdarg = "";	/* To keep gcc3 happy. */
432 
433 	while ((c = getopt(argc, argv, "d:hi:m:nuv")) != -1) {
434 		switch (c) {
435 		case 'd':
436 			datadir_add(optarg);
437 			break;
438 		case 'i':
439 			flags |= FLAG_I;
440 			cmdarg = optarg;
441 			break;
442 		case 'm':
443 			flags |= FLAG_M;
444 			cmdarg = optarg;
445 			break;
446 		case 'n':
447 			flags |= FLAG_N;
448 			break;
449 		case 'u':
450 			flags |= FLAG_U;
451 			break;
452 		case 'v':
453 			verbosity_level++;
454 			break;
455 		case 'h':
456 			/* FALLTHROUGH */
457 		default:
458 			usage();
459 			/* NOTREACHED */
460 		}
461 	}
462 	argc -= optind;
463 	argv += optind;
464 	if (argc < 1) {
465 		usage();
466 		/* NOTREACHED */
467 	}
468 	if ((flags & FLAG_N) == 0)
469 		datadir_add(DEFAULT_DATADIR);
470 	dev = argv[0];
471 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
472 	switch (c) {
473 		case FLAG_I:
474 			if (strstr(cmdarg, ",") != NULL)
475 				error = do_cpuid_count(cmdarg, dev);
476 			else
477 				error = do_cpuid(cmdarg, dev);
478 			break;
479 		case FLAG_M:
480 			error = do_msr(cmdarg, dev);
481 			break;
482 		case FLAG_U:
483 			error = do_update(dev);
484 			break;
485 		default:
486 			usage();	/* Only one command can be selected. */
487 	}
488 	SLIST_FREE(&datadirs, next, free);
489 	return (error == 0 ? 0 : 1);
490 }
491