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: stable/12/usr.sbin/cpucontrol/cpucontrol.c 354768 2019-11-16 10:50:10Z kib $");
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
60 int 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
79 struct datadir {
80 const char *path;
81 SLIST_ENTRY(datadir) next;
82 };
83 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
84
85 static 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
96 static void usage(void);
97 static int do_cpuid(const char *cmdarg, const char *dev);
98 static int do_cpuid_count(const char *cmdarg, const char *dev);
99 static int do_msr(const char *cmdarg, const char *dev);
100 static int do_update(const char *dev);
101 static void datadir_add(const char *path);
102
103 static void __dead2
usage(void)104 usage(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
116 static int
do_cpuid(const char * cmdarg,const char * dev)117 do_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
155 static int
do_cpuid_count(const char * cmdarg,const char * dev)156 do_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
206 static int
do_msr(const char * cmdarg,const char * dev)207 do_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
321 static int
do_eval_cpu_features(const char * dev)322 do_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
340 static int
try_a_fw_image(const char * dev_path,int devfd,int fwdfd,const char * dpath,const char * fname,struct ucode_handler * handler)341 try_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
395 out:
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
404 static int
do_update(const char * dev)405 do_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 */
484 static void
datadir_add(const char * path)485 datadir_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
496 int
main(int argc,char * argv[])497 main(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