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 (NELEM(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 1
350 	error = 0; /* XXX not implemented yet in cpuctl(4) */
351 #else
352 	error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
353 	if (error < 0)
354 		WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
355 #endif
356 	close(fd);
357 	return (error);
358 }
359 
360 static int
361 do_update(const char *dev)
362 {
363 	int fd;
364 	unsigned int i;
365 	int error;
366 	struct ucode_handler *handler;
367 	struct datadir *dir;
368 	DIR *dirp;
369 	struct dirent *direntry;
370 	char buf[MAXPATHLEN];
371 
372 	fd = open(dev, O_RDONLY);
373 	if (fd < 0) {
374 		WARN(0, "error opening %s for reading", dev);
375 		return (1);
376 	}
377 
378 	/*
379 	 * Find the appropriate handler for device.
380 	 */
381 	for (i = 0; i < NHANDLERS; i++)
382 		if (handlers[i].probe(fd) == 0)
383 			break;
384 	if (i < NHANDLERS)
385 		handler = &handlers[i];
386 	else {
387 		WARNX(0, "cannot find the appropriate handler for device");
388 		close(fd);
389 		return (1);
390 	}
391 	close(fd);
392 
393 	/*
394 	 * Process every image in specified data directories.
395 	 */
396 	SLIST_FOREACH(dir, &datadirs, next) {
397 		dirp = opendir(dir->path);
398 		if (dirp == NULL) {
399 			WARNX(1, "skipping directory %s: not accessible", dir->path);
400 			continue;
401 		}
402 		while ((direntry = readdir(dirp)) != NULL) {
403 			if (direntry->d_namlen == 0)
404 				continue;
405 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
406 			    direntry->d_name);
407 			if ((unsigned)error >= sizeof(buf))
408 				WARNX(0, "skipping %s, buffer too short",
409 				    direntry->d_name);
410 			if (isdir(buf) != 0) {
411 				WARNX(2, "skipping %s: is a directory", buf);
412 				continue;
413 			}
414 			handler->update(dev, buf);
415 		}
416 		error = closedir(dirp);
417 		if (error != 0)
418 			WARN(0, "closedir(%s)", dir->path);
419 	}
420 	return (0);
421 }
422 
423 /*
424  * Add new data directory to the search list.
425  */
426 static void
427 datadir_add(const char *path)
428 {
429 	struct datadir *newdir;
430 
431 	newdir = (struct datadir *)malloc(sizeof(*newdir));
432 	if (newdir == NULL)
433 		err(EX_OSERR, "cannot allocate memory");
434 	newdir->path = path;
435 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
436 }
437 
438 int
439 main(int argc, char *argv[])
440 {
441 	int c, flags;
442 	const char *cmdarg;
443 	const char *dev;
444 	int error;
445 
446 	flags = 0;
447 	error = 0;
448 	cmdarg = "";	/* To keep gcc3 happy. */
449 
450 	while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) {
451 		switch (c) {
452 		case 'd':
453 			datadir_add(optarg);
454 			break;
455 		case 'e':
456 			flags |= FLAG_E;
457 			break;
458 		case 'i':
459 			flags |= FLAG_I;
460 			cmdarg = optarg;
461 			break;
462 		case 'm':
463 			flags |= FLAG_M;
464 			cmdarg = optarg;
465 			break;
466 		case 'n':
467 			flags |= FLAG_N;
468 			break;
469 		case 'u':
470 			flags |= FLAG_U;
471 			break;
472 		case 'v':
473 			verbosity_level++;
474 			break;
475 		case 'h':
476 			/* FALLTHROUGH */
477 		default:
478 			usage();
479 			/* NOTREACHED */
480 		}
481 	}
482 	argc -= optind;
483 	argv += optind;
484 	if (argc < 1) {
485 		usage();
486 		/* NOTREACHED */
487 	}
488 	if ((flags & FLAG_N) == 0)
489 		datadir_add(DEFAULT_DATADIR);
490 	dev = argv[0];
491 	c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U);
492 	switch (c) {
493 	case FLAG_I:
494 		if (strstr(cmdarg, ",") != NULL)
495 			error = do_cpuid_count(cmdarg, dev);
496 		else
497 			error = do_cpuid(cmdarg, dev);
498 		break;
499 	case FLAG_M:
500 		error = do_msr(cmdarg, dev);
501 		break;
502 	case FLAG_U:
503 		error = do_update(dev);
504 		break;
505 	case FLAG_E:
506 		error = do_eval_cpu_features(dev);
507 		break;
508 	default:
509 		usage();	/* Only one command can be selected. */
510 	}
511 	return (error == 0 ? 0 : 1);
512 }
513