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