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 <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <fcntl.h> 42 #include <err.h> 43 #include <sysexits.h> 44 #include <dirent.h> 45 46 #include <sys/queue.h> 47 #include <sys/param.h> 48 #include <sys/types.h> 49 #include <sys/stat.h> 50 #include <sys/ioctl.h> 51 #include <sys/cpuctl.h> 52 53 #include "cpucontrol.h" 54 #include "amd.h" 55 #include "intel.h" 56 #include "via.h" 57 58 int verbosity_level = 0; 59 60 #define DEFAULT_DATADIR "/usr/local/share/cpucontrol" 61 62 #define FLAG_I 0x01 63 #define FLAG_M 0x02 64 #define FLAG_U 0x04 65 #define FLAG_N 0x08 66 #define FLAG_E 0x10 67 68 #define OP_INVAL 0x00 69 #define OP_READ 0x01 70 #define OP_WRITE 0x02 71 #define OP_OR 0x04 72 #define OP_AND 0x08 73 74 #define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff) 75 #define LOW(val) (uint32_t)((val) & 0xffffffff) 76 77 /* 78 * Macros for freeing SLISTs, probably must be in /sys/queue.h 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 (NELEM(handlers)) 96 97 static void usage(void); 98 static int isdir(const char *path); 99 static int do_cpuid(const char *cmdarg, const char *dev); 100 static int do_cpuid_count(const char *cmdarg, const char *dev); 101 static int do_msr(const char *cmdarg, const char *dev); 102 static int do_update(const char *dev); 103 static void datadir_add(const char *path); 104 105 static void __dead2 106 usage(void) 107 { 108 const char *name; 109 110 name = getprogname(); 111 if (name == NULL) 112 name = "cpuctl"; 113 fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | " 114 "-i level | -i level,level_type | -e | -u] device\n", name); 115 exit(EX_USAGE); 116 } 117 118 static int 119 isdir(const char *path) 120 { 121 int error; 122 struct stat st; 123 124 error = stat(path, &st); 125 if (error < 0) { 126 WARN(0, "stat(%s)", path); 127 return (error); 128 } 129 return (st.st_mode & S_IFDIR); 130 } 131 132 static int 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 = strtoul(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 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 = strtoul(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 = strtoul(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 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 = strtoul(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 (%lu))", dev, command_name, command); 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 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 #if 1 350 error = 0; /* XXX not implemented yet in cpuctl(4) */ 351 #else 352 error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL); 353 if (error < 0) 354 WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev); 355 #endif 356 close(fd); 357 return (error); 358 } 359 360 static int 361 do_update(const char *dev) 362 { 363 int fd; 364 unsigned int i; 365 int error; 366 struct ucode_handler *handler; 367 struct datadir *dir; 368 DIR *dirp; 369 struct dirent *direntry; 370 char buf[MAXPATHLEN]; 371 372 fd = open(dev, O_RDONLY); 373 if (fd < 0) { 374 WARN(0, "error opening %s for reading", dev); 375 return (1); 376 } 377 378 /* 379 * Find the appropriate handler for device. 380 */ 381 for (i = 0; i < NHANDLERS; i++) 382 if (handlers[i].probe(fd) == 0) 383 break; 384 if (i < NHANDLERS) 385 handler = &handlers[i]; 386 else { 387 WARNX(0, "cannot find the appropriate handler for device"); 388 close(fd); 389 return (1); 390 } 391 close(fd); 392 393 /* 394 * Process every image in specified data directories. 395 */ 396 SLIST_FOREACH(dir, &datadirs, next) { 397 dirp = opendir(dir->path); 398 if (dirp == NULL) { 399 WARNX(1, "skipping directory %s: not accessible", dir->path); 400 continue; 401 } 402 while ((direntry = readdir(dirp)) != NULL) { 403 if (direntry->d_namlen == 0) 404 continue; 405 error = snprintf(buf, sizeof(buf), "%s/%s", dir->path, 406 direntry->d_name); 407 if ((unsigned)error >= sizeof(buf)) 408 WARNX(0, "skipping %s, buffer too short", 409 direntry->d_name); 410 if (isdir(buf) != 0) { 411 WARNX(2, "skipping %s: is a directory", buf); 412 continue; 413 } 414 handler->update(dev, buf); 415 } 416 error = closedir(dirp); 417 if (error != 0) 418 WARN(0, "closedir(%s)", dir->path); 419 } 420 return (0); 421 } 422 423 /* 424 * Add new data directory to the search list. 425 */ 426 static void 427 datadir_add(const char *path) 428 { 429 struct datadir *newdir; 430 431 newdir = (struct datadir *)malloc(sizeof(*newdir)); 432 if (newdir == NULL) 433 err(EX_OSERR, "cannot allocate memory"); 434 newdir->path = path; 435 SLIST_INSERT_HEAD(&datadirs, newdir, next); 436 } 437 438 int 439 main(int argc, char *argv[]) 440 { 441 int c, flags; 442 const char *cmdarg; 443 const char *dev; 444 int error; 445 446 flags = 0; 447 error = 0; 448 cmdarg = ""; /* To keep gcc3 happy. */ 449 450 while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) { 451 switch (c) { 452 case 'd': 453 datadir_add(optarg); 454 break; 455 case 'e': 456 flags |= FLAG_E; 457 break; 458 case 'i': 459 flags |= FLAG_I; 460 cmdarg = optarg; 461 break; 462 case 'm': 463 flags |= FLAG_M; 464 cmdarg = optarg; 465 break; 466 case 'n': 467 flags |= FLAG_N; 468 break; 469 case 'u': 470 flags |= FLAG_U; 471 break; 472 case 'v': 473 verbosity_level++; 474 break; 475 case 'h': 476 /* FALLTHROUGH */ 477 default: 478 usage(); 479 /* NOTREACHED */ 480 } 481 } 482 argc -= optind; 483 argv += optind; 484 if (argc < 1) { 485 usage(); 486 /* NOTREACHED */ 487 } 488 if ((flags & FLAG_N) == 0) 489 datadir_add(DEFAULT_DATADIR); 490 dev = argv[0]; 491 c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U); 492 switch (c) { 493 case FLAG_I: 494 if (strstr(cmdarg, ",") != NULL) 495 error = do_cpuid_count(cmdarg, dev); 496 else 497 error = do_cpuid(cmdarg, dev); 498 break; 499 case FLAG_M: 500 error = do_msr(cmdarg, dev); 501 break; 502 case FLAG_U: 503 error = do_update(dev); 504 break; 505 case FLAG_E: 506 error = do_eval_cpu_features(dev); 507 break; 508 default: 509 usage(); /* Only one command can be selected. */ 510 } 511 return (error == 0 ? 0 : 1); 512 } 513