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