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