xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 10b3b54548f2290bbe8d8f88c59c28d12b7a635d)
1 /*-
2  * Copyright (c) 2008 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()
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 	char *endptr;
181 	char *p;
182 
183 	assert(cmdarg != NULL);
184 	assert(dev != NULL);
185 	len = strlen(cmdarg);
186 	if (len == 0) {
187 		WARNX(0, "MSR register expected");
188 		usage();
189 		/* NOTREACHED */
190 	}
191 
192 	/*
193 	 * Parse command string.
194 	 */
195 	msr = strtoul(cmdarg, &endptr, 16);
196 	switch (*endptr) {
197 	case '\0':
198 		op = OP_READ;
199 		break;
200 	case '=':
201 		op = OP_WRITE;
202 		break;
203 	case '&':
204 		op = OP_AND;
205 		endptr++;
206 		break;
207 	case '|':
208 		op = OP_OR;
209 		endptr++;
210 		break;
211 	default:
212 		op = OP_INVAL;
213 	}
214 	if (op != OP_READ) {	/* Complex operation. */
215 		if (*endptr != '=')
216 			op = OP_INVAL;
217 		else {
218 			p = ++endptr;
219 			if (*p == '~') {
220 				do_invert = 1;
221 				p++;
222 			}
223 			data = strtoull(p, &endptr, 16);
224 			if (*p == '\0' || *endptr != '\0') {
225 				WARNX(0, "argument required: %s", cmdarg);
226 				usage();
227 				/* NOTREACHED */
228 			}
229 		}
230 	}
231 	if (op == OP_INVAL) {
232 		WARNX(0, "invalid operator: %s", cmdarg);
233 		usage();
234 		/* NOTREACHED */
235 	}
236 
237 	/*
238 	 * Fill ioctl argument structure.
239 	 */
240 	args.msr = msr;
241 	if ((do_invert != 0) ^ (op == OP_AND))
242 		args.data = ~data;
243 	else
244 		args.data = data;
245 	switch (op) {
246 	case OP_READ:
247 		command = CPUCTL_RDMSR;
248 		break;
249 	case OP_WRITE:
250 		command = CPUCTL_WRMSR;
251 		break;
252 	case OP_OR:
253 		command = CPUCTL_MSRSBIT;
254 		break;
255 	case OP_AND:
256 		command = CPUCTL_MSRCBIT;
257 		break;
258 	default:
259 		abort();
260 	}
261 	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
262 	if (fd < 0) {
263 		WARN(0, "error opening %s for %s", dev,
264 		    op == OP_READ ? "reading" : "writing");
265 		return (1);
266 	}
267 	error = ioctl(fd, command, &args);
268 	if (error < 0) {
269 		WARN(0, "ioctl(%s, %lu)", dev, command);
270 		close(fd);
271 		return (1);
272 	}
273 	if (op == OP_READ)
274 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
275 		    HIGH(args.data), LOW(args.data));
276 	close(fd);
277 	return (0);
278 }
279 
280 static int
281 do_update(const char *dev)
282 {
283 	int fd;
284 	unsigned int i;
285 	int error;
286 	struct ucode_handler *handler;
287 	struct datadir *dir;
288 	DIR *dirfd;
289 	struct dirent *direntry;
290 	char buf[MAXPATHLEN];
291 
292 	fd = open(dev, O_RDONLY);
293 	if (fd < 0) {
294 		WARN(0, "error opening %s for reading", dev);
295 		return (1);
296 	}
297 
298 	/*
299 	 * Find the appropriate handler for device.
300 	 */
301 	for (i = 0; i < NHANDLERS; i++)
302 		if (handlers[i].probe(fd) == 0)
303 			break;
304 	if (i < NHANDLERS)
305 		handler = &handlers[i];
306 	else {
307 		WARNX(0, "cannot find the appropriate handler for device");
308 		close(fd);
309 		return (1);
310 	}
311 	close(fd);
312 
313 	/*
314 	 * Process every image in specified data directories.
315 	 */
316 	SLIST_FOREACH(dir, &datadirs, next) {
317 		dirfd  = opendir(dir->path);
318 		if (dirfd == NULL) {
319 			WARNX(1, "skipping directory %s: not accessible", dir->path);
320 			continue;
321 		}
322 		while ((direntry = readdir(dirfd)) != NULL) {
323 			if (direntry->d_namlen == 0)
324 				continue;
325 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
326 			    direntry->d_name);
327 			if ((unsigned)error >= sizeof(buf))
328 				WARNX(0, "skipping %s, buffer too short",
329 				    direntry->d_name);
330 			if (isdir(buf) != 0) {
331 				WARNX(2, "skipping %s: is a directory", buf);
332 				continue;
333 			}
334 			handler->update(dev, buf);
335 		}
336 		error = closedir(dirfd);
337 		if (error != 0)
338 			WARN(0, "closedir(%s)", dir->path);
339 	}
340 	return (0);
341 }
342 
343 /*
344  * Add new data directory to the search list.
345  */
346 static void
347 datadir_add(const char *path)
348 {
349 	struct datadir *newdir;
350 
351 	newdir = (struct datadir *)malloc(sizeof(*newdir));
352 	if (newdir == NULL)
353 		err(EX_OSERR, "cannot allocate memory");
354 	newdir->path = path;
355 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
356 }
357 
358 int
359 main(int argc, char *argv[])
360 {
361 	int c, flags;
362 	const char *cmdarg;
363 	const char *dev;
364 	int error;
365 
366 	flags = 0;
367 	error = 0;
368 	cmdarg = "";	/* To keep gcc3 happy. */
369 
370 	/*
371 	 * Add all default data dirs to the list first.
372 	 */
373 	datadir_add(DEFAULT_DATADIR);
374 	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
375 		switch (c) {
376 		case 'd':
377 			datadir_add(optarg);
378 			break;
379 		case 'i':
380 			flags |= FLAG_I;
381 			cmdarg = optarg;
382 			break;
383 		case 'm':
384 			flags |= FLAG_M;
385 			cmdarg = optarg;
386 			break;
387 		case 'u':
388 			flags |= FLAG_U;
389 			break;
390 		case 'v':
391 			verbosity_level++;
392 			break;
393 		case 'h':
394 			/* FALLTHROUGH */
395 		default:
396 			usage();
397 			/* NOTREACHED */
398 		}
399 	}
400 	argc -= optind;
401 	argv += optind;
402 	if (argc < 1) {
403 		usage();
404 		/* NOTREACHED */
405 	}
406 	dev = argv[0];
407 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
408 	switch (c) {
409 		case FLAG_I:
410 			error = do_cpuid(cmdarg, dev);
411 			break;
412 		case FLAG_M:
413 			error = do_msr(cmdarg, dev);
414 			break;
415 		case FLAG_U:
416 			error = do_update(dev);
417 			break;
418 		default:
419 			usage();	/* Only one command can be selected. */
420 	}
421 	SLIST_FREE(&datadirs, next, free);
422 	return (error);
423 }
424