xref: /freebsd/usr.sbin/cpucontrol/cpucontrol.c (revision 39beb93c)
1 /*-
2  * Copyright (c) 2008 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 
55 int	verbosity_level = 0;
56 
57 #define	DEFAULT_DATADIR	"/usr/local/share/cpucontrol"
58 
59 #define	FLAG_I	0x01
60 #define	FLAG_M	0x02
61 #define	FLAG_U	0x04
62 
63 #define	HIGH(val)	(uint32_t)(((val) >> 32) & 0xffffffff)
64 #define	LOW(val)	(uint32_t)((val) & 0xffffffff)
65 
66 /*
67  * Macros for freeing SLISTs, probably must be in /sys/queue.h
68  */
69 #define	SLIST_FREE(head, field, freef) do {				\
70 		typeof(SLIST_FIRST(head)) __elm0;			\
71 		typeof(SLIST_FIRST(head)) __elm;			\
72 		SLIST_FOREACH_SAFE(__elm, (head), field, __elm0)	\
73 			(void)(freef)(__elm);				\
74 } while(0);
75 
76 struct datadir {
77 	const char		*path;
78 	SLIST_ENTRY(datadir)	next;
79 };
80 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(&datadirs);
81 
82 struct ucode_handler {
83 	ucode_probe_t *probe;
84 	ucode_update_t *update;
85 } handlers[] = {
86 	{ intel_probe, intel_update },
87 	{ amd_probe, amd_update },
88 };
89 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
90 
91 static void	usage(void);
92 static int	isdir(const char *path);
93 static int	do_cpuid(const char *cmdarg, const char *dev);
94 static int	do_msr(const char *cmdarg, const char *dev);
95 static int	do_update(const char *dev);
96 static void	datadir_add(const char *path);
97 
98 static void __dead2
99 usage()
100 {
101 	const char *name;
102 
103 	name = getprogname();
104 	if (name == NULL)
105 		name = "cpuctl";
106 	fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
107 	    "-i level | -u] device\n", name);
108 	exit(EX_USAGE);
109 }
110 
111 static int
112 isdir(const char *path)
113 {
114 	int error;
115 	struct stat st;
116 
117 	error = stat(path, &st);
118 	if (error < 0) {
119 		WARN(0, "stat(%s)", path);
120 		return (error);
121 	}
122 	return (st.st_mode & S_IFDIR);
123 }
124 
125 static int
126 do_cpuid(const char *cmdarg, const char *dev)
127 {
128 	unsigned int level;
129 	cpuctl_cpuid_args_t args;
130 	int fd, error;
131 	char *endptr;
132 
133 	assert(cmdarg != NULL);
134 	assert(dev != NULL);
135 
136 	level = strtoul(cmdarg, &endptr, 16);
137 	if (*cmdarg == '\0' || *endptr != '\0') {
138 		WARNX(0, "incorrect operand: %s", cmdarg);
139 		usage();
140 		/* NOTREACHED */
141 	}
142 
143 	/*
144 	 * Fill ioctl argument structure.
145 	 */
146 	args.level = level;
147 	fd = open(dev, O_RDONLY);
148 	if (fd < 0) {
149 		WARN(0, "error opening %s for reading", dev);
150 		return (1);
151 	}
152 	error = ioctl(fd, CPUCTL_CPUID, &args);
153 	if (error < 0) {
154 		WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
155 		close(fd);
156 		return (error);
157 	}
158 	fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
159 	    level, args.data[0], args.data[1], args.data[2], args.data[3]);
160 	close(fd);
161 	return (0);
162 }
163 
164 static int
165 do_msr(const char *cmdarg, const char *dev)
166 {
167 	unsigned int msr;
168 	cpuctl_msr_args_t args;
169 	int fd, error;
170 	int wr = 0;
171 	char *p;
172 	char *endptr;
173 
174 	assert(cmdarg != NULL);
175 	assert(dev != NULL);
176 
177 	p = strchr(cmdarg, '=');
178 	if (p != NULL) {
179 		wr = 1;
180 		*p++ = '\0';
181 		args.data = strtoull(p, &endptr, 16);
182 		if (*p == '\0' || *endptr != '\0') {
183 			WARNX(0, "incorrect MSR value: %s", p);
184 			usage();
185 			/* NOTREACHED */
186 		}
187 	}
188 	msr = strtoul(cmdarg, &endptr, 16);
189 	if (*cmdarg == '\0' || *endptr != '\0') {
190 		WARNX(0, "incorrect MSR register: %s", cmdarg);
191 		usage();
192 		/* NOTREACHED */
193 	}
194 
195 	/*
196 	 * Fill ioctl argument structure.
197 	 */
198 	args.msr = msr;
199 	fd = open(dev, wr == 0 ? O_RDONLY : O_WRONLY);
200 	if (fd < 0) {
201 		WARN(0, "error opening %s for %s", dev,
202 		    wr == 0 ? "reading" : "writing");
203 		return (1);
204 	}
205 	error = ioctl(fd, wr == 0 ? CPUCTL_RDMSR : CPUCTL_WRMSR, &args);
206 	if (error < 0) {
207 		WARN(0, "ioctl(%s, %s)", dev,
208 		    wr == 0 ? "CPUCTL_RDMSR" : "CPUCTL_WRMSR");
209 		close(fd);
210 		return (1);
211 	}
212 	if (wr == 0)
213 		fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
214 		    HIGH(args.data), LOW(args.data));
215 	close(fd);
216 	return (0);
217 }
218 
219 static int
220 do_update(const char *dev)
221 {
222 	int fd;
223 	unsigned int i;
224 	int error;
225 	struct ucode_handler *handler;
226 	struct datadir *dir;
227 	DIR *dirfd;
228 	struct dirent *direntry;
229 	char buf[MAXPATHLEN];
230 
231 	fd = open(dev, O_RDONLY);
232 	if (fd < 0) {
233 		WARN(0, "error opening %s for reading", dev);
234 		return (1);
235 	}
236 
237 	/*
238 	 * Find the appropriate handler for device.
239 	 */
240 	for (i = 0; i < NHANDLERS; i++)
241 		if (handlers[i].probe(fd) == 0)
242 			break;
243 	if (i < NHANDLERS)
244 		handler = &handlers[i];
245 	else {
246 		WARNX(0, "cannot find the appropriate handler for device");
247 		close(fd);
248 		return (1);
249 	}
250 	close(fd);
251 
252 	/*
253 	 * Process every image in specified data directories.
254 	 */
255 	SLIST_FOREACH(dir, &datadirs, next) {
256 		dirfd  = opendir(dir->path);
257 		if (dirfd == NULL) {
258 			WARNX(1, "skipping directory %s: not accessible", dir->path);
259 			continue;
260 		}
261 		while ((direntry = readdir(dirfd)) != NULL) {
262 			if (direntry->d_namlen == 0)
263 				continue;
264 			error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
265 			    direntry->d_name);
266 			if ((unsigned)error >= sizeof(buf))
267 				WARNX(0, "skipping %s, buffer too short",
268 				    direntry->d_name);
269 			if (isdir(buf) != 0) {
270 				WARNX(2, "skipping %s: is a directory", buf);
271 				continue;
272 			}
273 			handler->update(dev, buf);
274 		}
275 		error = closedir(dirfd);
276 		if (error != 0)
277 			WARN(0, "closedir(%s)", dir->path);
278 	}
279 	return (0);
280 }
281 
282 /*
283  * Add new data directory to the search list.
284  */
285 static void
286 datadir_add(const char *path)
287 {
288 	struct datadir *newdir;
289 
290 	newdir = (struct datadir *)malloc(sizeof(*newdir));
291 	if (newdir == NULL)
292 		err(EX_OSERR, "cannot allocate memory");
293 	newdir->path = path;
294 	SLIST_INSERT_HEAD(&datadirs, newdir, next);
295 }
296 
297 int
298 main(int argc, char *argv[])
299 {
300 	int c, flags;
301 	const char *cmdarg;
302 	const char *dev;
303 	int error;
304 
305 	flags = 0;
306 	error = 0;
307 	cmdarg = "";	/* To keep gcc3 happy. */
308 
309 	/*
310 	 * Add all default data dirs to the list first.
311 	 */
312 	datadir_add(DEFAULT_DATADIR);
313 	while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
314 		switch (c) {
315 		case 'd':
316 			datadir_add(optarg);
317 			break;
318 		case 'i':
319 			flags |= FLAG_I;
320 			cmdarg = optarg;
321 			break;
322 		case 'm':
323 			flags |= FLAG_M;
324 			cmdarg = optarg;
325 			break;
326 		case 'u':
327 			flags |= FLAG_U;
328 			break;
329 		case 'v':
330 			verbosity_level++;
331 			break;
332 		case 'h':
333 			/* FALLTHROUGH */
334 		default:
335 			usage();
336 			/* NOTREACHED */
337 		}
338 	}
339 	argc -= optind;
340 	argv += optind;
341 	if (argc < 1) {
342 		usage();
343 		/* NOTREACHED */
344 	}
345 	dev = argv[0];
346 	c = flags & (FLAG_I | FLAG_M | FLAG_U);
347 	switch (c) {
348 		case FLAG_I:
349 			error = do_cpuid(cmdarg, dev);
350 			break;
351 		case FLAG_M:
352 			error = do_msr(cmdarg, dev);
353 			break;
354 		case FLAG_U:
355 			error = do_update(dev);
356 			break;
357 		default:
358 			usage();	/* Only one command can be selected. */
359 	}
360 	SLIST_FREE(&datadirs, next, free);
361 	return (error);
362 }
363