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