xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 23f6875a43f7ce365f2d52cf857da010c47fb03b)
1 /*-
2  * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * This utility provides userland access to the cpuctl(4) pseudo-device
28  * features.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <assert.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <err.h>
41 #include <sysexits.h>
42 #include <dirent.h>
43 
44 #include <sys/queue.h>
45 #include <sys/param.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/ioctl.h>
49 #include <sys/cpuctl.h>
50 
51 #include "cpucontrol.h"
52 #include "amd.h"
53 #include "intel.h"
54 #include "via.h"
55 
56 int	verbosity_level = 0;
57 
58 #define	DEFAULT_DATADIR	"/usr/local/share/cpucontrol"
59 
60 #define	FLAG_I	0x01
61 #define	FLAG_M	0x02
62 #define	FLAG_U	0x04
63 
64 #define	OP_INVAL	0x00
65 #define	OP_READ		0x01
66 #define	OP_WRITE	0x02
67 #define	OP_OR		0x04
68 #define	OP_AND		0x08
69 
70 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
71 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
72 
73 /*
74  * Macros for freeing SLISTs, probably must be in /sys/queue.h
75  */
76 #define	SLIST_FREE(head, field, freef) do {				\
77 		typeof(SLIST_FIRST(head)) __elm0;			\
78 		typeof(SLIST_FIRST(head)) __elm;			\
79 		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
80 			(void)(freef)(__elm);				\
81 } while(0);
82 
83 struct datadir {
84 	const char		*path;
85 	SLIST_ENTRY(datadir)	next;
86 };
87 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
88 
89 static struct ucode_handler {
90 	ucode_probe_t *probe;
91 	ucode_update_t *update;
92 } handlers[] = {
93 	{ intel_probe, intel_update },
94 	{ amd10h_probe, amd10h_update },
95 	{ amd_probe, amd_update },
96 	{ via_probe, via_update },
97 };
98 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
99 
100 static void	usage(void);
101 static int	isdir(const char *path);
102 static int	do_cpuid(const char *cmdarg, const char *dev);
103 static int	do_cpuid_count(const char *cmdarg, const char *dev);
104 static int	do_msr(const char *cmdarg, const char *dev);
105 static int	do_update(const char *dev);
106 static void	datadir_add(const char *path);
107 
108 static void __dead2
109 usage(void)
110 {
111 	const char *name;
112 
113 	name = getprogname();
114 	if (name == NULL)
115 		name = "cpuctl";
116 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
117 	    "-i level | -i level,level_type | -u] device\n", name);
118 	exit(EX_USAGE);
119 }
120 
121 static int
122 isdir(const char *path)
123 {
124 	int error;
125 	struct stat st;
126 
127 	error = stat(path, &st);
128 	if (error < 0) {
129 		WARN(0, "stat(%s)", path);
130 		return (error);
131 	}
132 	return (st.st_mode & S_IFDIR);
133 }
134 
135 static int
136 do_cpuid(const char *cmdarg, const char *dev)
137 {
138 	unsigned int level;
139 	cpuctl_cpuid_args_t args;
140 	int fd, error;
141 	char *endptr;
142 
143 	assert(cmdarg != NULL);
144 	assert(dev != NULL);
145 
146 	level = strtoul(cmdarg, &endptr, 16);
147 	if (*cmdarg == '\0' || *endptr != '\0') {
148 		WARNX(0, "incorrect operand: %s", cmdarg);
149 		usage();
150 		/* NOTREACHED */
151 	}
152 
153 	/*
154 	 * Fill ioctl argument structure.
155 	 */
156 	args.level = level;
157 	fd = open(dev, O_RDONLY);
158 	if (fd < 0) {
159 		WARN(0, "error opening %s for reading", dev);
160 		return (1);
161 	}
162 	error = ioctl(fd, CPUCTL_CPUID, &args);
163 	if (error < 0) {
164 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
165 		close(fd);
166 		return (error);
167 	}
168 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
169 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
170 	close(fd);
171 	return (0);
172 }
173 
174 static int
175 do_cpuid_count(const char *cmdarg, const char *dev)
176 {
177 	char *cmdarg1, *endptr, *endptr1;
178 	unsigned int level, level_type;
179 	cpuctl_cpuid_count_args_t args;
180 	int fd, error;
181 
182 	assert(cmdarg != NULL);
183 	assert(dev != NULL);
184 
185 	level = strtoul(cmdarg, &endptr, 16);
186 	if (*cmdarg == '\0' || *endptr == '\0') {
187 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
188 		usage();
189 		/* NOTREACHED */
190 	}
191 	/* Locate the comma... */
192 	cmdarg1 = strstr(endptr, ",");
193 	/* ... and skip past it */
194 	cmdarg1 += 1;
195 	level_type = strtoul(cmdarg1, &endptr1, 16);
196 	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
197 		WARNX(0, "incorrect or missing operand: %s", cmdarg);
198 		usage();
199 		/* NOTREACHED */
200 	}
201 
202 	/*
203 	 * Fill ioctl argument structure.
204 	 */
205 	args.level = level;
206 	args.level_type = level_type;
207 	fd = open(dev, O_RDONLY);
208 	if (fd < 0) {
209 		WARN(0, "error opening %s for reading", dev);
210 		return (1);
211 	}
212 	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
213 	if (error < 0) {
214 		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
215 		close(fd);
216 		return (error);
217 	}
218 	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
219 	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
220 	    args.data[2], args.data[3]);
221 	close(fd);
222 	return (0);
223 }
224 
225 static int
226 do_msr(const char *cmdarg, const char *dev)
227 {
228 	unsigned int msr;
229 	cpuctl_msr_args_t args;
230 	size_t len;
231 	uint64_t data = 0;
232 	unsigned long command;
233 	int do_invert = 0, op;
234 	int fd, error;
235 	const char *command_name;
236 	char *endptr;
237 	char *p;
238 
239 	assert(cmdarg != NULL);
240 	assert(dev != NULL);
241 	len = strlen(cmdarg);
242 	if (len == 0) {
243 		WARNX(0, "MSR register expected");
244 		usage();
245 		/* NOTREACHED */
246 	}
247 
248 	/*
249 	 * Parse command string.
250 	 */
251 	msr = strtoul(cmdarg, &endptr, 16);
252 	switch (*endptr) {
253 	case '\0':
254 		op = OP_READ;
255 		break;
256 	case '=':
257 		op = OP_WRITE;
258 		break;
259 	case '&':
260 		op = OP_AND;
261 		endptr++;
262 		break;
263 	case '|':
264 		op = OP_OR;
265 		endptr++;
266 		break;
267 	default:
268 		op = OP_INVAL;
269 	}
270 	if (op != OP_READ) {	/* Complex operation. */
271 		if (*endptr != '=')
272 			op = OP_INVAL;
273 		else {
274 			p = ++endptr;
275 			if (*p == '~') {
276 				do_invert = 1;
277 				p++;
278 			}
279 			data = strtoull(p, &endptr, 16);
280 			if (*p == '\0' || *endptr != '\0') {
281 				WARNX(0, "argument required: %s", cmdarg);
282 				usage();
283 				/* NOTREACHED */
284 			}
285 		}
286 	}
287 	if (op == OP_INVAL) {
288 		WARNX(0, "invalid operator: %s", cmdarg);
289 		usage();
290 		/* NOTREACHED */
291 	}
292 
293 	/*
294 	 * Fill ioctl argument structure.
295 	 */
296 	args.msr = msr;
297 	if ((do_invert != 0) ^ (op == OP_AND))
298 		args.data = ~data;
299 	else
300 		args.data = data;
301 	switch (op) {
302 	case OP_READ:
303 		command = CPUCTL_RDMSR;
304 		command_name = "RDMSR";
305 		break;
306 	case OP_WRITE:
307 		command = CPUCTL_WRMSR;
308 		command_name = "WRMSR";
309 		break;
310 	case OP_OR:
311 		command = CPUCTL_MSRSBIT;
312 		command_name = "MSRSBIT";
313 		break;
314 	case OP_AND:
315 		command = CPUCTL_MSRCBIT;
316 		command_name = "MSRCBIT";
317 		break;
318 	default:
319 		abort();
320 	}
321 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
322 	if (fd < 0) {
323 		WARN(0, "error opening %s for %s", dev,
324 		    op == OP_READ ? "reading" : "writing");
325 		return (1);
326 	}
327 	error = ioctl(fd, command, &args);
328 	if (error < 0) {
329 		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
330 		close(fd);
331 		return (1);
332 	}
333 	if (op == OP_READ)
334 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
335 		    HIGH(args.data), LOW(args.data));
336 	close(fd);
337 	return (0);
338 }
339 
340 static int
341 do_update(const char *dev)
342 {
343 	int fd;
344 	unsigned int i;
345 	int error;
346 	struct ucode_handler *handler;
347 	struct datadir *dir;
348 	DIR *dirp;
349 	struct dirent *direntry;
350 	char buf[MAXPATHLEN];
351 
352 	fd = open(dev, O_RDONLY);
353 	if (fd < 0) {
354 		WARN(0, "error opening %s for reading", dev);
355 		return (1);
356 	}
357 
358 	/*
359 	 * Find the appropriate handler for device.
360 	 */
361 	for (i = 0; i < NHANDLERS; i++)
362 		if (handlers[i].probe(fd) == 0)
363 			break;
364 	if (i < NHANDLERS)
365 		handler = &handlers[i];
366 	else {
367 		WARNX(0, "cannot find the appropriate handler for device");
368 		close(fd);
369 		return (1);
370 	}
371 	close(fd);
372 
373 	/*
374 	 * Process every image in specified data directories.
375 	 */
376 	SLIST_FOREACH(dir, &datadirs, next) {
377 		dirp = opendir(dir->path);
378 		if (dirp == NULL) {
379 			WARNX(1, "skipping directory %s: not accessible", dir->path);
380 			continue;
381 		}
382 		while ((direntry = readdir(dirp)) != NULL) {
383 			if (direntry->d_namlen == 0)
384 				continue;
385 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
386 			    direntry->d_name);
387 			if ((unsigned)error >= sizeof(buf))
388 				WARNX(0, "skipping %s, buffer too short",
389 				    direntry->d_name);
390 			if (isdir(buf) != 0) {
391 				WARNX(2, "skipping %s: is a directory", buf);
392 				continue;
393 			}
394 			handler->update(dev, buf);
395 		}
396 		error = closedir(dirp);
397 		if (error != 0)
398 			WARN(0, "closedir(%s)", dir->path);
399 	}
400 	return (0);
401 }
402 
403 /*
404  * Add new data directory to the search list.
405  */
406 static void
407 datadir_add(const char *path)
408 {
409 	struct datadir *newdir;
410 
411 	newdir = (struct datadir *)malloc(sizeof(*newdir));
412 	if (newdir == NULL)
413 		err(EX_OSERR, "cannot allocate memory");
414 	newdir->path = path;
415 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
416 }
417 
418 int
419 main(int argc, char *argv[])
420 {
421 	int c, flags;
422 	const char *cmdarg;
423 	const char *dev;
424 	int error;
425 
426 	flags = 0;
427 	error = 0;
428 	cmdarg = "";	/* To keep gcc3 happy. */
429 
430 	/*
431 	 * Add all default data dirs to the list first.
432 	 */
433 	datadir_add(DEFAULT_DATADIR);
434 	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
435 		switch (c) {
436 		case 'd':
437 			datadir_add(optarg);
438 			break;
439 		case 'i':
440 			flags |= FLAG_I;
441 			cmdarg = optarg;
442 			break;
443 		case 'm':
444 			flags |= FLAG_M;
445 			cmdarg = optarg;
446 			break;
447 		case 'u':
448 			flags |= FLAG_U;
449 			break;
450 		case 'v':
451 			verbosity_level++;
452 			break;
453 		case 'h':
454 			/* FALLTHROUGH */
455 		default:
456 			usage();
457 			/* NOTREACHED */
458 		}
459 	}
460 	argc -= optind;
461 	argv += optind;
462 	if (argc < 1) {
463 		usage();
464 		/* NOTREACHED */
465 	}
466 	dev = argv[0];
467 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
468 	switch (c) {
469 		case FLAG_I:
470 			if (strstr(cmdarg, ",") != NULL)
471 				error = do_cpuid_count(cmdarg, dev);
472 			else
473 				error = do_cpuid(cmdarg, dev);
474 			break;
475 		case FLAG_M:
476 			error = do_msr(cmdarg, dev);
477 			break;
478 		case FLAG_U:
479 			error = do_update(dev);
480 			break;
481 		default:
482 			usage();	/* Only one command can be selected. */
483 	}
484 	SLIST_FREE(&datadirs, next, free);
485 	return (error == 0 ? 0 : 1);
486 }
487