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 67 #define OP_INVAL 0x00 68 #define OP_READ 0x01 69 #define OP_WRITE 0x02 70 #define OP_OR 0x04 71 #define OP_AND 0x08 72 73 #define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff) 74 #define LOW(val) (uint32_t)((val) & 0xffffffff) 75 76 /* 77 * Macros for freeing SLISTs, probably must be in /sys/queue.h 78 */ 79 #define SLIST_FREE(head, field, freef) do { \ 80 typeof(SLIST_FIRST(head)) __elm0; \ 81 typeof(SLIST_FIRST(head)) __elm; \ 82 SLIST_FOREACH_SAFE(__elm, (head), field, __elm0) \ 83 (void)(freef)(__elm); \ 84 } while(0); 85 86 struct datadir { 87 const char *path; 88 SLIST_ENTRY(datadir) next; 89 }; 90 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs); 91 92 static struct ucode_handler { 93 ucode_probe_t *probe; 94 ucode_update_t *update; 95 } handlers[] = { 96 { intel_probe, intel_update }, 97 { amd10h_probe, amd10h_update }, 98 { amd_probe, amd_update }, 99 { via_probe, via_update }, 100 }; 101 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers)) 102 103 static void usage(void); 104 static int isdir(const char *path); 105 static int do_cpuid(const char *cmdarg, const char *dev); 106 static int do_cpuid_count(const char *cmdarg, const char *dev); 107 static int do_msr(const char *cmdarg, const char *dev); 108 static int do_update(const char *dev); 109 static void datadir_add(const char *path); 110 111 static void __dead2 112 usage(void) 113 { 114 const char *name; 115 116 name = getprogname(); 117 if (name == NULL) 118 name = "cpuctl"; 119 fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | " 120 "-i level | -i level,level_type | -u] device\n", name); 121 exit(EX_USAGE); 122 } 123 124 static int 125 isdir(const char *path) 126 { 127 int error; 128 struct stat st; 129 130 error = stat(path, &st); 131 if (error < 0) { 132 WARN(0, "stat(%s)", path); 133 return (error); 134 } 135 return (st.st_mode & S_IFDIR); 136 } 137 138 static int 139 do_cpuid(const char *cmdarg, const char *dev) 140 { 141 unsigned int level; 142 cpuctl_cpuid_args_t args; 143 int fd, error; 144 char *endptr; 145 146 assert(cmdarg != NULL); 147 assert(dev != NULL); 148 149 level = strtoul(cmdarg, &endptr, 16); 150 if (*cmdarg == '\0' || *endptr != '\0') { 151 WARNX(0, "incorrect operand: %s", cmdarg); 152 usage(); 153 /* NOTREACHED */ 154 } 155 156 /* 157 * Fill ioctl argument structure. 158 */ 159 args.level = level; 160 fd = open(dev, O_RDONLY); 161 if (fd < 0) { 162 WARN(0, "error opening %s for reading", dev); 163 return (1); 164 } 165 error = ioctl(fd, CPUCTL_CPUID, &args); 166 if (error < 0) { 167 WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev); 168 close(fd); 169 return (error); 170 } 171 fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n", 172 level, args.data[0], args.data[1], args.data[2], args.data[3]); 173 close(fd); 174 return (0); 175 } 176 177 static int 178 do_cpuid_count(const char *cmdarg, const char *dev) 179 { 180 char *cmdarg1, *endptr, *endptr1; 181 unsigned int level, level_type; 182 cpuctl_cpuid_count_args_t args; 183 int fd, error; 184 185 assert(cmdarg != NULL); 186 assert(dev != NULL); 187 188 level = strtoul(cmdarg, &endptr, 16); 189 if (*cmdarg == '\0' || *endptr == '\0') { 190 WARNX(0, "incorrect or missing operand: %s", cmdarg); 191 usage(); 192 /* NOTREACHED */ 193 } 194 /* Locate the comma... */ 195 cmdarg1 = strstr(endptr, ","); 196 /* ... and skip past it */ 197 cmdarg1 += 1; 198 level_type = strtoul(cmdarg1, &endptr1, 16); 199 if (*cmdarg1 == '\0' || *endptr1 != '\0') { 200 WARNX(0, "incorrect or missing operand: %s", cmdarg); 201 usage(); 202 /* NOTREACHED */ 203 } 204 205 /* 206 * Fill ioctl argument structure. 207 */ 208 args.level = level; 209 args.level_type = level_type; 210 fd = open(dev, O_RDONLY); 211 if (fd < 0) { 212 WARN(0, "error opening %s for reading", dev); 213 return (1); 214 } 215 error = ioctl(fd, CPUCTL_CPUID_COUNT, &args); 216 if (error < 0) { 217 WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev); 218 close(fd); 219 return (error); 220 } 221 fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x " 222 "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1], 223 args.data[2], args.data[3]); 224 close(fd); 225 return (0); 226 } 227 228 static int 229 do_msr(const char *cmdarg, const char *dev) 230 { 231 unsigned int msr; 232 cpuctl_msr_args_t args; 233 size_t len; 234 uint64_t data = 0; 235 unsigned long command; 236 int do_invert = 0, op; 237 int fd, error; 238 const char *command_name; 239 char *endptr; 240 char *p; 241 242 assert(cmdarg != NULL); 243 assert(dev != NULL); 244 len = strlen(cmdarg); 245 if (len == 0) { 246 WARNX(0, "MSR register expected"); 247 usage(); 248 /* NOTREACHED */ 249 } 250 251 /* 252 * Parse command string. 253 */ 254 msr = strtoul(cmdarg, &endptr, 16); 255 switch (*endptr) { 256 case '\0': 257 op = OP_READ; 258 break; 259 case '=': 260 op = OP_WRITE; 261 break; 262 case '&': 263 op = OP_AND; 264 endptr++; 265 break; 266 case '|': 267 op = OP_OR; 268 endptr++; 269 break; 270 default: 271 op = OP_INVAL; 272 } 273 if (op != OP_READ) { /* Complex operation. */ 274 if (*endptr != '=') 275 op = OP_INVAL; 276 else { 277 p = ++endptr; 278 if (*p == '~') { 279 do_invert = 1; 280 p++; 281 } 282 data = strtoull(p, &endptr, 16); 283 if (*p == '\0' || *endptr != '\0') { 284 WARNX(0, "argument required: %s", cmdarg); 285 usage(); 286 /* NOTREACHED */ 287 } 288 } 289 } 290 if (op == OP_INVAL) { 291 WARNX(0, "invalid operator: %s", cmdarg); 292 usage(); 293 /* NOTREACHED */ 294 } 295 296 /* 297 * Fill ioctl argument structure. 298 */ 299 args.msr = msr; 300 if ((do_invert != 0) ^ (op == OP_AND)) 301 args.data = ~data; 302 else 303 args.data = data; 304 switch (op) { 305 case OP_READ: 306 command = CPUCTL_RDMSR; 307 command_name = "RDMSR"; 308 break; 309 case OP_WRITE: 310 command = CPUCTL_WRMSR; 311 command_name = "WRMSR"; 312 break; 313 case OP_OR: 314 command = CPUCTL_MSRSBIT; 315 command_name = "MSRSBIT"; 316 break; 317 case OP_AND: 318 command = CPUCTL_MSRCBIT; 319 command_name = "MSRCBIT"; 320 break; 321 default: 322 abort(); 323 } 324 fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY); 325 if (fd < 0) { 326 WARN(0, "error opening %s for %s", dev, 327 op == OP_READ ? "reading" : "writing"); 328 return (1); 329 } 330 error = ioctl(fd, command, &args); 331 if (error < 0) { 332 WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command); 333 close(fd); 334 return (1); 335 } 336 if (op == OP_READ) 337 fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr, 338 HIGH(args.data), LOW(args.data)); 339 close(fd); 340 return (0); 341 } 342 343 static int 344 do_update(const char *dev) 345 { 346 int fd; 347 unsigned int i; 348 int error; 349 struct ucode_handler *handler; 350 struct datadir *dir; 351 DIR *dirp; 352 struct dirent *direntry; 353 char buf[MAXPATHLEN]; 354 355 fd = open(dev, O_RDONLY); 356 if (fd < 0) { 357 WARN(0, "error opening %s for reading", dev); 358 return (1); 359 } 360 361 /* 362 * Find the appropriate handler for device. 363 */ 364 for (i = 0; i < NHANDLERS; i++) 365 if (handlers[i].probe(fd) == 0) 366 break; 367 if (i < NHANDLERS) 368 handler = &handlers[i]; 369 else { 370 WARNX(0, "cannot find the appropriate handler for device"); 371 close(fd); 372 return (1); 373 } 374 close(fd); 375 376 /* 377 * Process every image in specified data directories. 378 */ 379 SLIST_FOREACH(dir, &datadirs, next) { 380 dirp = opendir(dir->path); 381 if (dirp == NULL) { 382 WARNX(1, "skipping directory %s: not accessible", dir->path); 383 continue; 384 } 385 while ((direntry = readdir(dirp)) != NULL) { 386 if (direntry->d_namlen == 0) 387 continue; 388 error = snprintf(buf, sizeof(buf), "%s/%s", dir->path, 389 direntry->d_name); 390 if ((unsigned)error >= sizeof(buf)) 391 WARNX(0, "skipping %s, buffer too short", 392 direntry->d_name); 393 if (isdir(buf) != 0) { 394 WARNX(2, "skipping %s: is a directory", buf); 395 continue; 396 } 397 handler->update(dev, buf); 398 } 399 error = closedir(dirp); 400 if (error != 0) 401 WARN(0, "closedir(%s)", dir->path); 402 } 403 return (0); 404 } 405 406 /* 407 * Add new data directory to the search list. 408 */ 409 static void 410 datadir_add(const char *path) 411 { 412 struct datadir *newdir; 413 414 newdir = (struct datadir *)malloc(sizeof(*newdir)); 415 if (newdir == NULL) 416 err(EX_OSERR, "cannot allocate memory"); 417 newdir->path = path; 418 SLIST_INSERT_HEAD(&datadirs, newdir, next); 419 } 420 421 int 422 main(int argc, char *argv[]) 423 { 424 int c, flags; 425 const char *cmdarg; 426 const char *dev; 427 int error; 428 429 flags = 0; 430 error = 0; 431 cmdarg = ""; /* To keep gcc3 happy. */ 432 433 while ((c = getopt(argc, argv, "d:hi:m:nuv")) != -1) { 434 switch (c) { 435 case 'd': 436 datadir_add(optarg); 437 break; 438 case 'i': 439 flags |= FLAG_I; 440 cmdarg = optarg; 441 break; 442 case 'm': 443 flags |= FLAG_M; 444 cmdarg = optarg; 445 break; 446 case 'n': 447 flags |= FLAG_N; 448 break; 449 case 'u': 450 flags |= FLAG_U; 451 break; 452 case 'v': 453 verbosity_level++; 454 break; 455 case 'h': 456 /* FALLTHROUGH */ 457 default: 458 usage(); 459 /* NOTREACHED */ 460 } 461 } 462 argc -= optind; 463 argv += optind; 464 if (argc < 1) { 465 usage(); 466 /* NOTREACHED */ 467 } 468 if ((flags & FLAG_N) == 0) 469 datadir_add(DEFAULT_DATADIR); 470 dev = argv[0]; 471 c = flags & (FLAG_I | FLAG_M | FLAG_U); 472 switch (c) { 473 case FLAG_I: 474 if (strstr(cmdarg, ",") != NULL) 475 error = do_cpuid_count(cmdarg, dev); 476 else 477 error = do_cpuid(cmdarg, dev); 478 break; 479 case FLAG_M: 480 error = do_msr(cmdarg, dev); 481 break; 482 case FLAG_U: 483 error = do_update(dev); 484 break; 485 default: 486 usage(); /* Only one command can be selected. */ 487 } 488 SLIST_FREE(&datadirs, next, free); 489 return (error == 0 ? 0 : 1); 490 } 491