1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28/*
29 * This utility provides userland access to the cpuctl(4) pseudo-device
30 * features.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <assert.h>
37#include <err.h>
38#include <errno.h>
39#include <dirent.h>
40#include <fcntl.h>
41#include <paths.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <sysexits.h>
47
48#include <sys/queue.h>
49#include <sys/param.h>
50#include <sys/types.h>
51#include <sys/mman.h>
52#include <sys/stat.h>
53#include <sys/ioctl.h>
54#include <sys/cpuctl.h>
55
56#include "cpucontrol.h"
57#include "amd.h"
58#include "intel.h"
59#include "via.h"
60
61int	verbosity_level = 0;
62
63#define	DEFAULT_DATADIR	_PATH_LOCALBASE "/share/cpucontrol"
64
65#define	FLAG_I	0x01
66#define	FLAG_M	0x02
67#define	FLAG_U	0x04
68#define	FLAG_N	0x08
69#define	FLAG_E	0x10
70
71#define	OP_INVAL	0x00
72#define	OP_READ		0x01
73#define	OP_WRITE	0x02
74#define	OP_OR		0x04
75#define	OP_AND		0x08
76
77#define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
78#define	LOW(val)	(uint32_t)((val) & 0xffffffff)
79
80struct datadir {
81	const char		*path;
82	SLIST_ENTRY(datadir)	next;
83};
84static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
85
86static struct ucode_handler {
87	ucode_probe_t *probe;
88	ucode_update_t *update;
89} handlers[] = {
90	{ intel_probe, intel_update },
91	{ amd10h_probe, amd10h_update },
92	{ amd_probe, amd_update },
93	{ via_probe, via_update },
94};
95#define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
96
97static void	usage(void);
98static int	do_cpuid(const char *cmdarg, const char *dev);
99static int	do_cpuid_count(const char *cmdarg, const char *dev);
100static int	do_msr(const char *cmdarg, const char *dev);
101static int	do_update(const char *dev);
102static void	datadir_add(const char *path);
103
104static void __dead2
105usage(void)
106{
107	const char *name;
108
109	name = getprogname();
110	if (name == NULL)
111		name = "cpuctl";
112	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
113	    "-i level | -i level,level_type | -e | -u] device\n", name);
114	exit(EX_USAGE);
115}
116
117static int
118do_cpuid(const char *cmdarg, const char *dev)
119{
120	unsigned int level;
121	cpuctl_cpuid_args_t args;
122	int fd, error;
123	char *endptr;
124
125	assert(cmdarg != NULL);
126	assert(dev != NULL);
127
128	level = strtoul(cmdarg, &endptr, 16);
129	if (*cmdarg == '\0' || *endptr != '\0') {
130		WARNX(0, "incorrect operand: %s", cmdarg);
131		usage();
132		/* NOTREACHED */
133	}
134
135	/*
136	 * Fill ioctl argument structure.
137	 */
138	args.level = level;
139	fd = open(dev, O_RDONLY);
140	if (fd < 0) {
141		WARN(0, "error opening %s for reading", dev);
142		return (1);
143	}
144	error = ioctl(fd, CPUCTL_CPUID, &args);
145	if (error < 0) {
146		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
147		close(fd);
148		return (error);
149	}
150	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
151	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
152	close(fd);
153	return (0);
154}
155
156static int
157do_cpuid_count(const char *cmdarg, const char *dev)
158{
159	char *cmdarg1, *endptr, *endptr1;
160	unsigned int level, level_type;
161	cpuctl_cpuid_count_args_t args;
162	int fd, error;
163
164	assert(cmdarg != NULL);
165	assert(dev != NULL);
166
167	level = strtoul(cmdarg, &endptr, 16);
168	if (*cmdarg == '\0' || *endptr == '\0') {
169		WARNX(0, "incorrect or missing operand: %s", cmdarg);
170		usage();
171		/* NOTREACHED */
172	}
173	/* Locate the comma... */
174	cmdarg1 = strstr(endptr, ",");
175	/* ... and skip past it */
176	cmdarg1 += 1;
177	level_type = strtoul(cmdarg1, &endptr1, 16);
178	if (*cmdarg1 == '\0' || *endptr1 != '\0') {
179		WARNX(0, "incorrect or missing operand: %s", cmdarg);
180		usage();
181		/* NOTREACHED */
182	}
183
184	/*
185	 * Fill ioctl argument structure.
186	 */
187	args.level = level;
188	args.level_type = level_type;
189	fd = open(dev, O_RDONLY);
190	if (fd < 0) {
191		WARN(0, "error opening %s for reading", dev);
192		return (1);
193	}
194	error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
195	if (error < 0) {
196		WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
197		close(fd);
198		return (error);
199	}
200	fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
201	    "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
202	    args.data[2], args.data[3]);
203	close(fd);
204	return (0);
205}
206
207static int
208do_msr(const char *cmdarg, const char *dev)
209{
210	unsigned int msr;
211	cpuctl_msr_args_t args;
212	size_t len;
213	uint64_t data = 0;
214	unsigned long command;
215	int do_invert = 0, op;
216	int fd, error;
217	const char *command_name;
218	char *endptr;
219	char *p;
220
221	assert(cmdarg != NULL);
222	assert(dev != NULL);
223	len = strlen(cmdarg);
224	if (len == 0) {
225		WARNX(0, "MSR register expected");
226		usage();
227		/* NOTREACHED */
228	}
229
230	/*
231	 * Parse command string.
232	 */
233	msr = strtoul(cmdarg, &endptr, 16);
234	switch (*endptr) {
235	case '\0':
236		op = OP_READ;
237		break;
238	case '=':
239		op = OP_WRITE;
240		break;
241	case '&':
242		op = OP_AND;
243		endptr++;
244		break;
245	case '|':
246		op = OP_OR;
247		endptr++;
248		break;
249	default:
250		op = OP_INVAL;
251	}
252	if (op != OP_READ) {	/* Complex operation. */
253		if (*endptr != '=')
254			op = OP_INVAL;
255		else {
256			p = ++endptr;
257			if (*p == '~') {
258				do_invert = 1;
259				p++;
260			}
261			data = strtoull(p, &endptr, 16);
262			if (*p == '\0' || *endptr != '\0') {
263				WARNX(0, "argument required: %s", cmdarg);
264				usage();
265				/* NOTREACHED */
266			}
267		}
268	}
269	if (op == OP_INVAL) {
270		WARNX(0, "invalid operator: %s", cmdarg);
271		usage();
272		/* NOTREACHED */
273	}
274
275	/*
276	 * Fill ioctl argument structure.
277	 */
278	args.msr = msr;
279	if ((do_invert != 0) ^ (op == OP_AND))
280		args.data = ~data;
281	else
282		args.data = data;
283	switch (op) {
284	case OP_READ:
285		command = CPUCTL_RDMSR;
286		command_name = "RDMSR";
287		break;
288	case OP_WRITE:
289		command = CPUCTL_WRMSR;
290		command_name = "WRMSR";
291		break;
292	case OP_OR:
293		command = CPUCTL_MSRSBIT;
294		command_name = "MSRSBIT";
295		break;
296	case OP_AND:
297		command = CPUCTL_MSRCBIT;
298		command_name = "MSRCBIT";
299		break;
300	default:
301		abort();
302	}
303	fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
304	if (fd < 0) {
305		WARN(0, "error opening %s for %s", dev,
306		    op == OP_READ ? "reading" : "writing");
307		return (1);
308	}
309	error = ioctl(fd, command, &args);
310	if (error < 0) {
311		WARN(0, "ioctl(%s, CPUCTL_%s (%#x))", dev, command_name, msr);
312		close(fd);
313		return (1);
314	}
315	if (op == OP_READ)
316		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
317		    HIGH(args.data), LOW(args.data));
318	close(fd);
319	return (0);
320}
321
322static int
323do_eval_cpu_features(const char *dev)
324{
325	int fd, error;
326
327	assert(dev != NULL);
328
329	fd = open(dev, O_RDWR);
330	if (fd < 0) {
331		WARN(0, "error opening %s for writing", dev);
332		return (1);
333	}
334	error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
335	if (error < 0)
336		WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
337	close(fd);
338	return (error);
339}
340
341static int
342try_a_fw_image(const char *dev_path, int devfd, int fwdfd, const char *dpath,
343    const char *fname, struct ucode_handler *handler)
344{
345	struct ucode_update_params parm;
346	struct stat st;
347	char *fw_path;
348	void *fw_map;
349	int fwfd, rc;
350
351	rc = 0;
352	fw_path = NULL;
353	fw_map = MAP_FAILED;
354	fwfd = openat(fwdfd, fname, O_RDONLY);
355	if (fwfd < 0) {
356		WARN(0, "openat(%s, %s)", dpath, fname);
357		goto out;
358	}
359
360	rc = asprintf(&fw_path, "%s/%s", dpath, fname);
361	if (rc == -1) {
362		WARNX(0, "out of memory");
363		rc = ENOMEM;
364		goto out;
365	}
366
367	rc = fstat(fwfd, &st);
368	if (rc != 0) {
369		WARN(0, "fstat(%s)", fw_path);
370		rc = 0;
371		goto out;
372	}
373	if (!S_ISREG(st.st_mode))
374		goto out;
375	if (st.st_size <= 0) {
376		WARN(0, "%s: empty", fw_path);
377		goto out;
378	}
379
380	fw_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fwfd, 0);
381	if (fw_map == MAP_FAILED) {
382		WARN(0, "mmap(%s)", fw_path);
383		goto out;
384	}
385
386
387	memset(&parm, 0, sizeof(parm));
388	parm.devfd = devfd;
389	parm.fwimage = fw_map;
390	parm.fwsize = st.st_size;
391	parm.dev_path = dev_path;
392	parm.fw_path = fw_path;
393
394	handler->update(&parm);
395
396out:
397	if (fw_map != MAP_FAILED)
398		munmap(fw_map, st.st_size);
399	free(fw_path);
400	if (fwfd >= 0)
401		close(fwfd);
402	return (rc);
403}
404
405static int
406do_update(const char *dev)
407{
408	int fd, fwdfd;
409	unsigned int i;
410	int error;
411	struct ucode_handler *handler;
412	struct datadir *dir;
413	DIR *dirp;
414	struct dirent *direntry;
415
416	fd = open(dev, O_RDONLY);
417	if (fd < 0) {
418		WARN(0, "error opening %s for reading", dev);
419		return (1);
420	}
421
422	/*
423	 * Find the appropriate handler for CPU.
424	 */
425	for (i = 0; i < NHANDLERS; i++)
426		if (handlers[i].probe(fd) == 0)
427			break;
428	if (i < NHANDLERS)
429		handler = &handlers[i];
430	else {
431		WARNX(0, "cannot find the appropriate handler for %s", dev);
432		close(fd);
433		return (1);
434	}
435	close(fd);
436
437	fd = open(dev, O_RDWR);
438	if (fd < 0) {
439		WARN(0, "error opening %s for writing", dev);
440		return (1);
441	}
442
443	/*
444	 * Process every image in specified data directories.
445	 */
446	SLIST_FOREACH(dir, &datadirs, next) {
447		fwdfd = open(dir->path, O_RDONLY);
448		if (fwdfd < 0) {
449			WARN(1, "skipping directory %s: not accessible", dir->path);
450			continue;
451		}
452		dirp = fdopendir(fwdfd);
453		if (dirp == NULL) {
454			WARNX(0, "out of memory");
455			close(fwdfd);
456			close(fd);
457			return (1);
458		}
459
460		while ((direntry = readdir(dirp)) != NULL) {
461			if (direntry->d_namlen == 0)
462				continue;
463			if (direntry->d_type == DT_DIR)
464				continue;
465
466			error = try_a_fw_image(dev, fd, fwdfd, dir->path,
467			    direntry->d_name, handler);
468			if (error != 0) {
469				closedir(dirp);
470				close(fd);
471				return (1);
472			}
473		}
474		error = closedir(dirp);
475		if (error != 0)
476			WARN(0, "closedir(%s)", dir->path);
477	}
478	close(fd);
479	return (0);
480}
481
482/*
483 * Add new data directory to the search list.
484 */
485static void
486datadir_add(const char *path)
487{
488	struct datadir *newdir;
489
490	newdir = (struct datadir *)malloc(sizeof(*newdir));
491	if (newdir == NULL)
492		err(EX_OSERR, "cannot allocate memory");
493	newdir->path = path;
494	SLIST_INSERT_HEAD(&datadirs, newdir, next);
495}
496
497int
498main(int argc, char *argv[])
499{
500	struct datadir *elm;
501	int c, flags;
502	const char *cmdarg;
503	const char *dev;
504	int error;
505
506	flags = 0;
507	error = 0;
508	cmdarg = "";	/* To keep gcc3 happy. */
509
510	while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) {
511		switch (c) {
512		case 'd':
513			datadir_add(optarg);
514			break;
515		case 'e':
516			flags |= FLAG_E;
517			break;
518		case 'i':
519			flags |= FLAG_I;
520			cmdarg = optarg;
521			break;
522		case 'm':
523			flags |= FLAG_M;
524			cmdarg = optarg;
525			break;
526		case 'n':
527			flags |= FLAG_N;
528			break;
529		case 'u':
530			flags |= FLAG_U;
531			break;
532		case 'v':
533			verbosity_level++;
534			break;
535		case 'h':
536			/* FALLTHROUGH */
537		default:
538			usage();
539			/* NOTREACHED */
540		}
541	}
542	argc -= optind;
543	argv += optind;
544	if (argc < 1) {
545		usage();
546		/* NOTREACHED */
547	}
548	if ((flags & FLAG_N) == 0)
549		datadir_add(DEFAULT_DATADIR);
550	dev = argv[0];
551	c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U);
552	switch (c) {
553	case FLAG_I:
554		if (strstr(cmdarg, ",") != NULL)
555			error = do_cpuid_count(cmdarg, dev);
556		else
557			error = do_cpuid(cmdarg, dev);
558		break;
559	case FLAG_M:
560		error = do_msr(cmdarg, dev);
561		break;
562	case FLAG_U:
563		error = do_update(dev);
564		break;
565	case FLAG_E:
566		error = do_eval_cpu_features(dev);
567		break;
568	default:
569		usage();	/* Only one command can be selected. */
570	}
571	while ((elm = SLIST_FIRST(&datadirs)) != NULL) {
572		SLIST_REMOVE_HEAD(&datadirs, next);
573		free(elm);
574	}
575	return (error == 0 ? 0 : 1);
576}
577