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