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