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