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