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: releng/10.3/usr.sbin/cpucontrol/cpucontrol.c 268157 2014-07-02 13:09:26Z kib $");
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"
54228436Sfabient#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
89241737Sedstatic struct ucode_handler {
90181430Sstas	ucode_probe_t *probe;
91181430Sstas	ucode_update_t *update;
92181430Sstas} handlers[] = {
93181430Sstas	{ intel_probe, intel_update },
94181430Sstas	{ amd_probe, amd_update },
95228436Sfabient	{ 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);
102268157Skibstatic int	do_cpuid_count(const char *cmdarg, const char *dev);
103181430Sstasstatic int	do_msr(const char *cmdarg, const char *dev);
104181430Sstasstatic int	do_update(const char *dev);
105181430Sstasstatic void	datadir_add(const char *path);
106181430Sstas
107181430Sstasstatic void __dead2
108201227Sedusage(void)
109181430Sstas{
110181430Sstas	const char *name;
111181430Sstas
112181430Sstas	name = getprogname();
113181430Sstas	if (name == NULL)
114181430Sstas		name = "cpuctl";
115181430Sstas	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
116268157Skib	    "-i level | -i level,level_type | -u] device\n", name);
117181430Sstas	exit(EX_USAGE);
118181430Sstas}
119181430Sstas
120181430Sstasstatic int
121181430Sstasisdir(const char *path)
122181430Sstas{
123181430Sstas	int error;
124181430Sstas	struct stat st;
125181430Sstas
126181430Sstas	error = stat(path, &st);
127181430Sstas	if (error < 0) {
128181430Sstas		WARN(0, "stat(%s)", path);
129181430Sstas		return (error);
130181430Sstas	}
131181430Sstas	return (st.st_mode & S_IFDIR);
132181430Sstas}
133181430Sstas
134181430Sstasstatic int
135181430Sstasdo_cpuid(const char *cmdarg, const char *dev)
136181430Sstas{
137181430Sstas	unsigned int level;
138181430Sstas	cpuctl_cpuid_args_t args;
139181430Sstas	int fd, error;
140181430Sstas	char *endptr;
141181430Sstas
142181430Sstas	assert(cmdarg != NULL);
143181430Sstas	assert(dev != NULL);
144181430Sstas
145181430Sstas	level = strtoul(cmdarg, &endptr, 16);
146181430Sstas	if (*cmdarg == '\0' || *endptr != '\0') {
147181430Sstas		WARNX(0, "incorrect operand: %s", cmdarg);
148181430Sstas		usage();
149181430Sstas		/* NOTREACHED */
150181430Sstas	}
151181430Sstas
152181430Sstas	/*
153181430Sstas	 * Fill ioctl argument structure.
154181430Sstas	 */
155181430Sstas	args.level = level;
156181430Sstas	fd = open(dev, O_RDONLY);
157181430Sstas	if (fd < 0) {
158181628Sstas		WARN(0, "error opening %s for reading", dev);
159181430Sstas		return (1);
160181430Sstas	}
161181430Sstas	error = ioctl(fd, CPUCTL_CPUID, &args);
162181430Sstas	if (error < 0) {
163181628Sstas		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
164181430Sstas		close(fd);
165181430Sstas		return (error);
166181430Sstas	}
167181430Sstas	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
168181430Sstas	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
169181430Sstas	close(fd);
170181430Sstas	return (0);
171181430Sstas}
172181430Sstas
173181430Sstasstatic int
174268157Skibdo_cpuid_count(const char *cmdarg, const char *dev)
175268157Skib{
176268157Skib	char *cmdarg1, *endptr, *endptr1;
177268157Skib	unsigned int level, level_type;
178268157Skib	cpuctl_cpuid_count_args_t args;
179268157Skib	int fd, error;
180268157Skib
181268157Skib	assert(cmdarg != NULL);
182268157Skib	assert(dev != NULL);
183268157Skib
184268157Skib	level = strtoul(cmdarg, &endptr, 16);
185268157Skib	if (*cmdarg == '\0' || *endptr == '\0') {
186268157Skib		WARNX(0, "incorrect or missing operand: %s", cmdarg);
187268157Skib		usage();
188268157Skib		/* NOTREACHED */
189268157Skib	}
190268157Skib	/* Locate the comma... */
191268157Skib	cmdarg1 = strstr(endptr, ",");
192268157Skib	/* ... and skip past it */
193268157Skib	cmdarg1 += 1;
194268157Skib	level_type = strtoul(cmdarg1, &endptr1, 16);
195268157Skib	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
196268157Skib		WARNX(0, "incorrect or missing operand: %s", cmdarg);
197268157Skib		usage();
198268157Skib		/* NOTREACHED */
199268157Skib	}
200268157Skib
201268157Skib	/*
202268157Skib	 * Fill ioctl argument structure.
203268157Skib	 */
204268157Skib	args.level = level;
205268157Skib	args.level_type = level_type;
206268157Skib	fd = open(dev, O_RDONLY);
207268157Skib	if (fd < 0) {
208268157Skib		WARN(0, "error opening %s for reading", dev);
209268157Skib		return (1);
210268157Skib	}
211268157Skib	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
212268157Skib	if (error < 0) {
213268157Skib		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
214268157Skib		close(fd);
215268157Skib		return (error);
216268157Skib	}
217268157Skib	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
218268157Skib	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
219268157Skib	    args.data[2], args.data[3]);
220268157Skib	close(fd);
221268157Skib	return (0);
222268157Skib}
223268157Skib
224268157Skibstatic int
225181430Sstasdo_msr(const char *cmdarg, const char *dev)
226181430Sstas{
227181430Sstas	unsigned int msr;
228181430Sstas	cpuctl_msr_args_t args;
229195189Sstas	size_t len;
230195189Sstas	uint64_t data = 0;
231195189Sstas	unsigned long command;
232195189Sstas	int do_invert = 0, op;
233181430Sstas	int fd, error;
234217046Sstas	const char *command_name;
235195189Sstas	char *endptr;
236181430Sstas	char *p;
237181430Sstas
238181430Sstas	assert(cmdarg != NULL);
239181430Sstas	assert(dev != NULL);
240195189Sstas	len = strlen(cmdarg);
241195189Sstas	if (len == 0) {
242195189Sstas		WARNX(0, "MSR register expected");
243195189Sstas		usage();
244195189Sstas		/* NOTREACHED */
245195189Sstas	}
246181430Sstas
247195189Sstas	/*
248195189Sstas	 * Parse command string.
249195189Sstas	 */
250195189Sstas	msr = strtoul(cmdarg, &endptr, 16);
251195189Sstas	switch (*endptr) {
252195189Sstas	case '\0':
253195189Sstas		op = OP_READ;
254195189Sstas		break;
255195189Sstas	case '=':
256195189Sstas		op = OP_WRITE;
257195189Sstas		break;
258195189Sstas	case '&':
259195189Sstas		op = OP_AND;
260195189Sstas		endptr++;
261195189Sstas		break;
262195189Sstas	case '|':
263195189Sstas		op = OP_OR;
264195189Sstas		endptr++;
265195189Sstas		break;
266195189Sstas	default:
267195189Sstas		op = OP_INVAL;
268195189Sstas	}
269195189Sstas	if (op != OP_READ) {	/* Complex operation. */
270195189Sstas		if (*endptr != '=')
271195189Sstas			op = OP_INVAL;
272195189Sstas		else {
273195189Sstas			p = ++endptr;
274195189Sstas			if (*p == '~') {
275195189Sstas				do_invert = 1;
276195189Sstas				p++;
277195189Sstas			}
278195189Sstas			data = strtoull(p, &endptr, 16);
279195189Sstas			if (*p == '\0' || *endptr != '\0') {
280195189Sstas				WARNX(0, "argument required: %s", cmdarg);
281195189Sstas				usage();
282195189Sstas				/* NOTREACHED */
283195189Sstas			}
284181430Sstas		}
285181430Sstas	}
286195189Sstas	if (op == OP_INVAL) {
287195189Sstas		WARNX(0, "invalid operator: %s", cmdarg);
288181430Sstas		usage();
289181430Sstas		/* NOTREACHED */
290181430Sstas	}
291181430Sstas
292181430Sstas	/*
293181430Sstas	 * Fill ioctl argument structure.
294181430Sstas	 */
295181430Sstas	args.msr = msr;
296195189Sstas	if ((do_invert != 0) ^ (op == OP_AND))
297195189Sstas		args.data = ~data;
298195189Sstas	else
299195189Sstas		args.data = data;
300195189Sstas	switch (op) {
301195189Sstas	case OP_READ:
302195189Sstas		command = CPUCTL_RDMSR;
303217119Sstas		command_name = "RDMSR";
304195189Sstas		break;
305195189Sstas	case OP_WRITE:
306195189Sstas		command = CPUCTL_WRMSR;
307217119Sstas		command_name = "WRMSR";
308195189Sstas		break;
309195189Sstas	case OP_OR:
310195189Sstas		command = CPUCTL_MSRSBIT;
311217119Sstas		command_name = "MSRSBIT";
312195189Sstas		break;
313195189Sstas	case OP_AND:
314195189Sstas		command = CPUCTL_MSRCBIT;
315217119Sstas		command_name = "MSRCBIT";
316195189Sstas		break;
317195189Sstas	default:
318195189Sstas		abort();
319195189Sstas	}
320195189Sstas	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
321181430Sstas	if (fd < 0) {
322181628Sstas		WARN(0, "error opening %s for %s", dev,
323195189Sstas		    op == OP_READ ? "reading" : "writing");
324181430Sstas		return (1);
325181430Sstas	}
326195189Sstas	error = ioctl(fd, command, &args);
327181430Sstas	if (error < 0) {
328217119Sstas		WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
329181430Sstas		close(fd);
330181430Sstas		return (1);
331181430Sstas	}
332195189Sstas	if (op == OP_READ)
333181430Sstas		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
334181430Sstas		    HIGH(args.data), LOW(args.data));
335181430Sstas	close(fd);
336181430Sstas	return (0);
337181430Sstas}
338181430Sstas
339181430Sstasstatic int
340181430Sstasdo_update(const char *dev)
341181430Sstas{
342181430Sstas	int fd;
343181430Sstas	unsigned int i;
344181430Sstas	int error;
345181430Sstas	struct ucode_handler *handler;
346181430Sstas	struct datadir *dir;
347235647Sgleb	DIR *dirp;
348181430Sstas	struct dirent *direntry;
349181430Sstas	char buf[MAXPATHLEN];
350181430Sstas
351181430Sstas	fd = open(dev, O_RDONLY);
352181430Sstas	if (fd < 0) {
353181628Sstas		WARN(0, "error opening %s for reading", dev);
354181430Sstas		return (1);
355181430Sstas	}
356181430Sstas
357181430Sstas	/*
358181430Sstas	 * Find the appropriate handler for device.
359181430Sstas	 */
360181430Sstas	for (i = 0; i < NHANDLERS; i++)
361181430Sstas		if (handlers[i].probe(fd) == 0)
362181430Sstas			break;
363181430Sstas	if (i < NHANDLERS)
364181430Sstas		handler = &handlers[i];
365181430Sstas	else {
366181430Sstas		WARNX(0, "cannot find the appropriate handler for device");
367181430Sstas		close(fd);
368181430Sstas		return (1);
369181430Sstas	}
370181430Sstas	close(fd);
371181430Sstas
372181430Sstas	/*
373181430Sstas	 * Process every image in specified data directories.
374181430Sstas	 */
375181430Sstas	SLIST_FOREACH(dir, &datadirs, next) {
376235647Sgleb		dirp = opendir(dir->path);
377235647Sgleb		if (dirp == NULL) {
378181430Sstas			WARNX(1, "skipping directory %s: not accessible", dir->path);
379181430Sstas			continue;
380181430Sstas		}
381235647Sgleb		while ((direntry = readdir(dirp)) != NULL) {
382181430Sstas			if (direntry->d_namlen == 0)
383181430Sstas				continue;
384181430Sstas			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
385181430Sstas			    direntry->d_name);
386181430Sstas			if ((unsigned)error >= sizeof(buf))
387181430Sstas				WARNX(0, "skipping %s, buffer too short",
388181430Sstas				    direntry->d_name);
389181430Sstas			if (isdir(buf) != 0) {
390181430Sstas				WARNX(2, "skipping %s: is a directory", buf);
391181430Sstas				continue;
392181430Sstas			}
393181430Sstas			handler->update(dev, buf);
394181430Sstas		}
395235647Sgleb		error = closedir(dirp);
396181430Sstas		if (error != 0)
397181430Sstas			WARN(0, "closedir(%s)", dir->path);
398181430Sstas	}
399181430Sstas	return (0);
400181430Sstas}
401181430Sstas
402181430Sstas/*
403181430Sstas * Add new data directory to the search list.
404181430Sstas */
405181430Sstasstatic void
406181430Sstasdatadir_add(const char *path)
407181430Sstas{
408181430Sstas	struct datadir *newdir;
409181430Sstas
410181430Sstas	newdir = (struct datadir *)malloc(sizeof(*newdir));
411181430Sstas	if (newdir == NULL)
412181430Sstas		err(EX_OSERR, "cannot allocate memory");
413181430Sstas	newdir->path = path;
414181430Sstas	SLIST_INSERT_HEAD(&datadirs, newdir, next);
415181430Sstas}
416181430Sstas
417181430Sstasint
418181430Sstasmain(int argc, char *argv[])
419181430Sstas{
420181430Sstas	int c, flags;
421181430Sstas	const char *cmdarg;
422181430Sstas	const char *dev;
423181430Sstas	int error;
424181430Sstas
425181430Sstas	flags = 0;
426181430Sstas	error = 0;
427181430Sstas	cmdarg = "";	/* To keep gcc3 happy. */
428181430Sstas
429181430Sstas	/*
430181430Sstas	 * Add all default data dirs to the list first.
431181430Sstas	 */
432181430Sstas	datadir_add(DEFAULT_DATADIR);
433181430Sstas	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
434181430Sstas		switch (c) {
435181430Sstas		case 'd':
436181430Sstas			datadir_add(optarg);
437181430Sstas			break;
438181430Sstas		case 'i':
439181430Sstas			flags |= FLAG_I;
440181430Sstas			cmdarg = optarg;
441181430Sstas			break;
442181430Sstas		case 'm':
443181430Sstas			flags |= FLAG_M;
444181430Sstas			cmdarg = optarg;
445181430Sstas			break;
446181430Sstas		case 'u':
447181430Sstas			flags |= FLAG_U;
448181430Sstas			break;
449181430Sstas		case 'v':
450181430Sstas			verbosity_level++;
451181430Sstas			break;
452181430Sstas		case 'h':
453181430Sstas			/* FALLTHROUGH */
454181430Sstas		default:
455181430Sstas			usage();
456181430Sstas			/* NOTREACHED */
457181430Sstas		}
458181430Sstas	}
459181430Sstas	argc -= optind;
460181430Sstas	argv += optind;
461181430Sstas	if (argc < 1) {
462181430Sstas		usage();
463181430Sstas		/* NOTREACHED */
464181430Sstas	}
465181430Sstas	dev = argv[0];
466181430Sstas	c = flags & (FLAG_I | FLAG_M | FLAG_U);
467181430Sstas	switch (c) {
468181430Sstas		case FLAG_I:
469268157Skib			if (strstr(cmdarg, ",") != NULL)
470268157Skib				error = do_cpuid_count(cmdarg, dev);
471268157Skib			else
472268157Skib				error = do_cpuid(cmdarg, dev);
473181430Sstas			break;
474181430Sstas		case FLAG_M:
475181430Sstas			error = do_msr(cmdarg, dev);
476181430Sstas			break;
477181430Sstas		case FLAG_U:
478181430Sstas			error = do_update(dev);
479181430Sstas			break;
480181430Sstas		default:
481181430Sstas			usage();	/* Only one command can be selected. */
482181430Sstas	}
483181430Sstas	SLIST_FREE(&datadirs, next, free);
484181430Sstas	return (error);
485181430Sstas}
486