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$"); 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 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 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 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 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 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 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 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 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 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