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