1181430Sstas/*-
2217046Sstas * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>.
3181430Sstas * All rights reserved.
4181430Sstas *
5181430Sstas * Redistribution and use in source and binary forms, with or without
6181430Sstas * modification, are permitted provided that the following conditions
7181430Sstas * are met:
8181430Sstas * 1. Redistributions of source code must retain the above copyright
9181430Sstas *    notice, this list of conditions and the following disclaimer.
10181430Sstas * 2. Redistributions in binary form must reproduce the above copyright
11181430Sstas *    notice, this list of conditions and the following disclaimer in the
12181430Sstas *    documentation and/or other materials provided with the distribution.
13181430Sstas *
14181430Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15181430Sstas * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16181430Sstas * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17181430Sstas * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18181430Sstas * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19181430Sstas * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20181430Sstas * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21181430Sstas * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22181430Sstas * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23181430Sstas * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24181430Sstas */
25181430Sstas
26181430Sstas/*
27181430Sstas * This utility provides userland access to the cpuctl(4) pseudo-device
28181430Sstas * features.
29181430Sstas */
30181430Sstas
31181430Sstas#include <sys/cdefs.h>
32181430Sstas__FBSDID("$FreeBSD$");
33181430Sstas
34181430Sstas#include <assert.h>
35181430Sstas#include <stdio.h>
36181430Sstas#include <stdlib.h>
37181430Sstas#include <string.h>
38181430Sstas#include <unistd.h>
39181430Sstas#include <fcntl.h>
40181430Sstas#include <err.h>
41181430Sstas#include <sysexits.h>
42181430Sstas#include <dirent.h>
43181430Sstas
44181430Sstas#include <sys/queue.h>
45181430Sstas#include <sys/param.h>
46181430Sstas#include <sys/types.h>
47181430Sstas#include <sys/stat.h>
48181430Sstas#include <sys/ioctl.h>
49181430Sstas#include <sys/cpuctl.h>
50181430Sstas
51181430Sstas#include "cpucontrol.h"
52181430Sstas#include "amd.h"
53181430Sstas#include "intel.h"
54229471Sfabient#include "via.h"
55181430Sstas
56181430Sstasint	verbosity_level = 0;
57181430Sstas
58181430Sstas#define	DEFAULT_DATADIR	"/usr/local/share/cpucontrol"
59181430Sstas
60181430Sstas#define	FLAG_I	0x01
61181430Sstas#define	FLAG_M	0x02
62181430Sstas#define	FLAG_U	0x04
63181430Sstas
64195189Sstas#define	OP_INVAL	0x00
65195189Sstas#define	OP_READ		0x01
66195189Sstas#define	OP_WRITE	0x02
67195189Sstas#define	OP_OR		0x04
68195189Sstas#define	OP_AND		0x08
69195189Sstas
70181430Sstas#define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
71181430Sstas#define	LOW(val)	(uint32_t)((val) & 0xffffffff)
72181430Sstas
73181430Sstas/*
74181430Sstas * Macros for freeing SLISTs, probably must be in /sys/queue.h
75181430Sstas */
76181430Sstas#define	SLIST_FREE(head, field, freef) do {				\
77181430Sstas		typeof(SLIST_FIRST(head)) __elm0;			\
78181430Sstas		typeof(SLIST_FIRST(head)) __elm;			\
79181430Sstas		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
80181430Sstas			(void)(freef)(__elm);				\
81181430Sstas} while(0);
82181430Sstas
83181430Sstasstruct datadir {
84181430Sstas	const char		*path;
85181430Sstas	SLIST_ENTRY(datadir)	next;
86181430Sstas};
87201145Santoinestatic SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
88181430Sstas
89181430Sstasstruct ucode_handler {
90181430Sstas	ucode_probe_t *probe;
91181430Sstas	ucode_update_t *update;
92181430Sstas} handlers[] = {
93181430Sstas	{ intel_probe, intel_update },
94181430Sstas	{ amd_probe, amd_update },
95229471Sfabient	{ via_probe, via_update },
96181430Sstas};
97181430Sstas#define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
98181430Sstas
99181430Sstasstatic void	usage(void);
100181430Sstasstatic int	isdir(const char *path);
101181430Sstasstatic int	do_cpuid(const char *cmdarg, const char *dev);
102181430Sstasstatic int	do_msr(const char *cmdarg, const char *dev);
103181430Sstasstatic int	do_update(const char *dev);
104181430Sstasstatic void	datadir_add(const char *path);
105181430Sstas
106181430Sstasstatic void __dead2
107201227Sedusage(void)
108181430Sstas{
109181430Sstas	const char *name;
110181430Sstas
111181430Sstas	name = getprogname();
112181430Sstas	if (name == NULL)
113181430Sstas		name = "cpuctl";
114181430Sstas	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
115181430Sstas	    "-i level | -u] device\n", name);
116181430Sstas	exit(EX_USAGE);
117181430Sstas}
118181430Sstas
119181430Sstasstatic int
120181430Sstasisdir(const char *path)
121181430Sstas{
122181430Sstas	int error;
123181430Sstas	struct stat st;
124181430Sstas
125181430Sstas	error = stat(path, &st);
126181430Sstas	if (error < 0) {
127181430Sstas		WARN(0, "stat(%s)", path);
128181430Sstas		return (error);
129181430Sstas	}
130181430Sstas	return (st.st_mode & S_IFDIR);
131181430Sstas}
132181430Sstas
133181430Sstasstatic int
134181430Sstasdo_cpuid(const char *cmdarg, const char *dev)
135181430Sstas{
136181430Sstas	unsigned int level;
137181430Sstas	cpuctl_cpuid_args_t args;
138181430Sstas	int fd, error;
139181430Sstas	char *endptr;
140181430Sstas
141181430Sstas	assert(cmdarg != NULL);
142181430Sstas	assert(dev != NULL);
143181430Sstas
144181430Sstas	level = strtoul(cmdarg, &endptr, 16);
145181430Sstas	if (*cmdarg == '\0' || *endptr != '\0') {
146181430Sstas		WARNX(0, "incorrect operand: %s", cmdarg);
147181430Sstas		usage();
148181430Sstas		/* NOTREACHED */
149181430Sstas	}
150181430Sstas
151181430Sstas	/*
152181430Sstas	 * Fill ioctl argument structure.
153181430Sstas	 */
154181430Sstas	args.level = level;
155181430Sstas	fd = open(dev, O_RDONLY);
156181430Sstas	if (fd < 0) {
157181628Sstas		WARN(0, "error opening %s for reading", dev);
158181430Sstas		return (1);
159181430Sstas	}
160181430Sstas	error = ioctl(fd, CPUCTL_CPUID, &args);
161181430Sstas	if (error < 0) {
162181628Sstas		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
163181430Sstas		close(fd);
164181430Sstas		return (error);
165181430Sstas	}
166181430Sstas	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
167181430Sstas	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
168181430Sstas	close(fd);
169181430Sstas	return (0);
170181430Sstas}
171181430Sstas
172181430Sstasstatic int
173181430Sstasdo_msr(const char *cmdarg, const char *dev)
174181430Sstas{
175181430Sstas	unsigned int msr;
176181430Sstas	cpuctl_msr_args_t args;
177195189Sstas	size_t len;
178195189Sstas	uint64_t data = 0;
179195189Sstas	unsigned long command;
180195189Sstas	int do_invert = 0, op;
181181430Sstas	int fd, error;
182217046Sstas	const char *command_name;
183195189Sstas	char *endptr;
184181430Sstas	char *p;
185181430Sstas
186181430Sstas	assert(cmdarg != NULL);
187181430Sstas	assert(dev != NULL);
188195189Sstas	len = strlen(cmdarg);
189195189Sstas	if (len == 0) {
190195189Sstas		WARNX(0, "MSR register expected");
191195189Sstas		usage();
192195189Sstas		/* NOTREACHED */
193195189Sstas	}
194181430Sstas
195195189Sstas	/*
196195189Sstas	 * Parse command string.
197195189Sstas	 */
198195189Sstas	msr = strtoul(cmdarg, &endptr, 16);
199195189Sstas	switch (*endptr) {
200195189Sstas	case '\0':
201195189Sstas		op = OP_READ;
202195189Sstas		break;
203195189Sstas	case '=':
204195189Sstas		op = OP_WRITE;
205195189Sstas		break;
206195189Sstas	case '&':
207195189Sstas		op = OP_AND;
208195189Sstas		endptr++;
209195189Sstas		break;
210195189Sstas	case '|':
211195189Sstas		op = OP_OR;
212195189Sstas		endptr++;
213195189Sstas		break;
214195189Sstas	default:
215195189Sstas		op = OP_INVAL;
216195189Sstas	}
217195189Sstas	if (op != OP_READ) {	/* Complex operation. */
218195189Sstas		if (*endptr != '=')
219195189Sstas			op = OP_INVAL;
220195189Sstas		else {
221195189Sstas			p = ++endptr;
222195189Sstas			if (*p == '~') {
223195189Sstas				do_invert = 1;
224195189Sstas				p++;
225195189Sstas			}
226195189Sstas			data = strtoull(p, &endptr, 16);
227195189Sstas			if (*p == '\0' || *endptr != '\0') {
228195189Sstas				WARNX(0, "argument required: %s", cmdarg);
229195189Sstas				usage();
230195189Sstas				/* NOTREACHED */
231195189Sstas			}
232181430Sstas		}
233181430Sstas	}
234195189Sstas	if (op == OP_INVAL) {
235195189Sstas		WARNX(0, "invalid operator: %s", cmdarg);
236181430Sstas		usage();
237181430Sstas		/* NOTREACHED */
238181430Sstas	}
239181430Sstas
240181430Sstas	/*
241181430Sstas	 * Fill ioctl argument structure.
242181430Sstas	 */
243181430Sstas	args.msr = msr;
244195189Sstas	if ((do_invert != 0) ^ (op == OP_AND))
245195189Sstas		args.data = ~data;
246195189Sstas	else
247195189Sstas		args.data = data;
248195189Sstas	switch (op) {
249195189Sstas	case OP_READ:
250195189Sstas		command = CPUCTL_RDMSR;
251217119Sstas		command_name = "RDMSR";
252195189Sstas		break;
253195189Sstas	case OP_WRITE:
254195189Sstas		command = CPUCTL_WRMSR;
255217119Sstas		command_name = "WRMSR";
256195189Sstas		break;
257195189Sstas	case OP_OR:
258195189Sstas		command = CPUCTL_MSRSBIT;
259217119Sstas		command_name = "MSRSBIT";
260195189Sstas		break;
261195189Sstas	case OP_AND:
262195189Sstas		command = CPUCTL_MSRCBIT;
263217119Sstas		command_name = "MSRCBIT";
264195189Sstas		break;
265195189Sstas	default:
266195189Sstas		abort();
267195189Sstas	}
268195189Sstas	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
269181430Sstas	if (fd < 0) {
270181628Sstas		WARN(0, "error opening %s for %s", dev,
271195189Sstas		    op == OP_READ ? "reading" : "writing");
272181430Sstas		return (1);
273181430Sstas	}
274195189Sstas	error = ioctl(fd, command, &args);
275181430Sstas	if (error < 0) {
276217119Sstas		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
277181430Sstas		close(fd);
278181430Sstas		return (1);
279181430Sstas	}
280195189Sstas	if (op == OP_READ)
281181430Sstas		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
282181430Sstas		    HIGH(args.data), LOW(args.data));
283181430Sstas	close(fd);
284181430Sstas	return (0);
285181430Sstas}
286181430Sstas
287181430Sstasstatic int
288181430Sstasdo_update(const char *dev)
289181430Sstas{
290181430Sstas	int fd;
291181430Sstas	unsigned int i;
292181430Sstas	int error;
293181430Sstas	struct ucode_handler *handler;
294181430Sstas	struct datadir *dir;
295181430Sstas	DIR *dirfd;
296181430Sstas	struct dirent *direntry;
297181430Sstas	char buf[MAXPATHLEN];
298181430Sstas
299181430Sstas	fd = open(dev, O_RDONLY);
300181430Sstas	if (fd < 0) {
301181628Sstas		WARN(0, "error opening %s for reading", dev);
302181430Sstas		return (1);
303181430Sstas	}
304181430Sstas
305181430Sstas	/*
306181430Sstas	 * Find the appropriate handler for device.
307181430Sstas	 */
308181430Sstas	for (i = 0; i < NHANDLERS; i++)
309181430Sstas		if (handlers[i].probe(fd) == 0)
310181430Sstas			break;
311181430Sstas	if (i < NHANDLERS)
312181430Sstas		handler = &handlers[i];
313181430Sstas	else {
314181430Sstas		WARNX(0, "cannot find the appropriate handler for device");
315181430Sstas		close(fd);
316181430Sstas		return (1);
317181430Sstas	}
318181430Sstas	close(fd);
319181430Sstas
320181430Sstas	/*
321181430Sstas	 * Process every image in specified data directories.
322181430Sstas	 */
323181430Sstas	SLIST_FOREACH(dir, &datadirs, next) {
324181430Sstas		dirfd  = opendir(dir->path);
325181430Sstas		if (dirfd == NULL) {
326181430Sstas			WARNX(1, "skipping directory %s: not accessible", dir->path);
327181430Sstas			continue;
328181430Sstas		}
329181430Sstas		while ((direntry = readdir(dirfd)) != NULL) {
330181430Sstas			if (direntry->d_namlen == 0)
331181430Sstas				continue;
332181430Sstas			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
333181430Sstas			    direntry->d_name);
334181430Sstas			if ((unsigned)error >= sizeof(buf))
335181430Sstas				WARNX(0, "skipping %s, buffer too short",
336181430Sstas				    direntry->d_name);
337181430Sstas			if (isdir(buf) != 0) {
338181430Sstas				WARNX(2, "skipping %s: is a directory", buf);
339181430Sstas				continue;
340181430Sstas			}
341181430Sstas			handler->update(dev, buf);
342181430Sstas		}
343181430Sstas		error = closedir(dirfd);
344181430Sstas		if (error != 0)
345181430Sstas			WARN(0, "closedir(%s)", dir->path);
346181430Sstas	}
347181430Sstas	return (0);
348181430Sstas}
349181430Sstas
350181430Sstas/*
351181430Sstas * Add new data directory to the search list.
352181430Sstas */
353181430Sstasstatic void
354181430Sstasdatadir_add(const char *path)
355181430Sstas{
356181430Sstas	struct datadir *newdir;
357181430Sstas
358181430Sstas	newdir = (struct datadir *)malloc(sizeof(*newdir));
359181430Sstas	if (newdir == NULL)
360181430Sstas		err(EX_OSERR, "cannot allocate memory");
361181430Sstas	newdir->path = path;
362181430Sstas	SLIST_INSERT_HEAD(&datadirs, newdir, next);
363181430Sstas}
364181430Sstas
365181430Sstasint
366181430Sstasmain(int argc, char *argv[])
367181430Sstas{
368181430Sstas	int c, flags;
369181430Sstas	const char *cmdarg;
370181430Sstas	const char *dev;
371181430Sstas	int error;
372181430Sstas
373181430Sstas	flags = 0;
374181430Sstas	error = 0;
375181430Sstas	cmdarg = "";	/* To keep gcc3 happy. */
376181430Sstas
377181430Sstas	/*
378181430Sstas	 * Add all default data dirs to the list first.
379181430Sstas	 */
380181430Sstas	datadir_add(DEFAULT_DATADIR);
381181430Sstas	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
382181430Sstas		switch (c) {
383181430Sstas		case 'd':
384181430Sstas			datadir_add(optarg);
385181430Sstas			break;
386181430Sstas		case 'i':
387181430Sstas			flags |= FLAG_I;
388181430Sstas			cmdarg = optarg;
389181430Sstas			break;
390181430Sstas		case 'm':
391181430Sstas			flags |= FLAG_M;
392181430Sstas			cmdarg = optarg;
393181430Sstas			break;
394181430Sstas		case 'u':
395181430Sstas			flags |= FLAG_U;
396181430Sstas			break;
397181430Sstas		case 'v':
398181430Sstas			verbosity_level++;
399181430Sstas			break;
400181430Sstas		case 'h':
401181430Sstas			/* FALLTHROUGH */
402181430Sstas		default:
403181430Sstas			usage();
404181430Sstas			/* NOTREACHED */
405181430Sstas		}
406181430Sstas	}
407181430Sstas	argc -= optind;
408181430Sstas	argv += optind;
409181430Sstas	if (argc < 1) {
410181430Sstas		usage();
411181430Sstas		/* NOTREACHED */
412181430Sstas	}
413181430Sstas	dev = argv[0];
414181430Sstas	c = flags & (FLAG_I | FLAG_M | FLAG_U);
415181430Sstas	switch (c) {
416181430Sstas		case FLAG_I:
417181430Sstas			error = do_cpuid(cmdarg, dev);
418181430Sstas			break;
419181430Sstas		case FLAG_M:
420181430Sstas			error = do_msr(cmdarg, dev);
421181430Sstas			break;
422181430Sstas		case FLAG_U:
423181430Sstas			error = do_update(dev);
424181430Sstas			break;
425181430Sstas		default:
426181430Sstas			usage();	/* Only one command can be selected. */
427181430Sstas	}
428181430Sstas	SLIST_FREE(&datadirs, next, free);
429181430Sstas	return (error);
430181430Sstas}
431