1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2008-2011 Stanislav Sedov <[email protected]>.
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
61 int 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
80 struct datadir {
81 const char *path;
82 SLIST_ENTRY(datadir) next;
83 };
84 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
85
86 static 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
97 static void usage(void);
98 static int do_cpuid(const char *cmdarg, const char *dev);
99 static int do_cpuid_count(const char *cmdarg, const char *dev);
100 static int do_msr(const char *cmdarg, const char *dev);
101 static int do_update(const char *dev);
102 static void datadir_add(const char *path);
103
104 static void __dead2
usage(void)105 usage(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
117 static int
do_cpuid(const char * cmdarg,const char * dev)118 do_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
156 static int
do_cpuid_count(const char * cmdarg,const char * dev)157 do_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
207 static int
do_msr(const char * cmdarg,const char * dev)208 do_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
322 static int
do_eval_cpu_features(const char * dev)323 do_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
341 static int
try_a_fw_image(const char * dev_path,int devfd,int fwdfd,const char * dpath,const char * fname,struct ucode_handler * handler)342 try_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
396 out:
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
405 static int
do_update(const char * dev)406 do_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 */
485 static void
datadir_add(const char * path)486 datadir_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
497 int
main(int argc,char * argv[])498 main(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