1 /*- 2 * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /* 27 * This utility provides userland access to the cpuctl(4) pseudo-device 28 * features. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <assert.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 #include <fcntl.h> 40 #include <err.h> 41 #include <sysexits.h> 42 #include <dirent.h> 43 44 #include <sys/queue.h> 45 #include <sys/param.h> 46 #include <sys/types.h> 47 #include <sys/stat.h> 48 #include <sys/ioctl.h> 49 #include <sys/cpuctl.h> 50 51 #include "cpucontrol.h" 52 #include "amd.h" 53 #include "intel.h" 54 #include "via.h" 55 56 int verbosity_level = 0; 57 58 #define DEFAULT_DATADIR "/usr/local/share/cpucontrol" 59 60 #define FLAG_I 0x01 61 #define FLAG_M 0x02 62 #define FLAG_U 0x04 63 #define FLAG_N 0x08 64 65 #define OP_INVAL 0x00 66 #define OP_READ 0x01 67 #define OP_WRITE 0x02 68 #define OP_OR 0x04 69 #define OP_AND 0x08 70 71 #define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff) 72 #define LOW(val) (uint32_t)((val) & 0xffffffff) 73 74 /* 75 * Macros for freeing SLISTs, probably must be in /sys/queue.h 76 */ 77 #define SLIST_FREE(head, field, freef) do { \ 78 typeof(SLIST_FIRST(head)) __elm0; \ 79 typeof(SLIST_FIRST(head)) __elm; \ 80 SLIST_FOREACH_SAFE(__elm, (head), field, __elm0) \ 81 (void)(freef)(__elm); \ 82 } while(0); 83 84 struct datadir { 85 const char *path; 86 SLIST_ENTRY(datadir) next; 87 }; 88 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs); 89 90 static struct ucode_handler { 91 ucode_probe_t *probe; 92 ucode_update_t *update; 93 } handlers[] = { 94 { intel_probe, intel_update }, 95 { amd10h_probe, amd10h_update }, 96 { amd_probe, amd_update }, 97 { via_probe, via_update }, 98 }; 99 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers)) 100 101 static void usage(void); 102 static int isdir(const char *path); 103 static int do_cpuid(const char *cmdarg, const char *dev); 104 static int do_cpuid_count(const char *cmdarg, const char *dev); 105 static int do_msr(const char *cmdarg, const char *dev); 106 static int do_update(const char *dev); 107 static void datadir_add(const char *path); 108 109 static void __dead2 110 usage(void) 111 { 112 const char *name; 113 114 name = getprogname(); 115 if (name == NULL) 116 name = "cpuctl"; 117 fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | " 118 "-i level | -i level,level_type | -u] device\n", name); 119 exit(EX_USAGE); 120 } 121 122 static int 123 isdir(const char *path) 124 { 125 int error; 126 struct stat st; 127 128 error = stat(path, &st); 129 if (error < 0) { 130 WARN(0, "stat(%s)", path); 131 return (error); 132 } 133 return (st.st_mode & S_IFDIR); 134 } 135 136 static int 137 do_cpuid(const char *cmdarg, const char *dev) 138 { 139 unsigned int level; 140 cpuctl_cpuid_args_t args; 141 int fd, error; 142 char *endptr; 143 144 assert(cmdarg != NULL); 145 assert(dev != NULL); 146 147 level = strtoul(cmdarg, &endptr, 16); 148 if (*cmdarg == '\0' || *endptr != '\0') { 149 WARNX(0, "incorrect operand: %s", cmdarg); 150 usage(); 151 /* NOTREACHED */ 152 } 153 154 /* 155 * Fill ioctl argument structure. 156 */ 157 args.level = level; 158 fd = open(dev, O_RDONLY); 159 if (fd < 0) { 160 WARN(0, "error opening %s for reading", dev); 161 return (1); 162 } 163 error = ioctl(fd, CPUCTL_CPUID, &args); 164 if (error < 0) { 165 WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev); 166 close(fd); 167 return (error); 168 } 169 fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n", 170 level, args.data[0], args.data[1], args.data[2], args.data[3]); 171 close(fd); 172 return (0); 173 } 174 175 static int 176 do_cpuid_count(const char *cmdarg, const char *dev) 177 { 178 char *cmdarg1, *endptr, *endptr1; 179 unsigned int level, level_type; 180 cpuctl_cpuid_count_args_t args; 181 int fd, error; 182 183 assert(cmdarg != NULL); 184 assert(dev != NULL); 185 186 level = strtoul(cmdarg, &endptr, 16); 187 if (*cmdarg == '\0' || *endptr == '\0') { 188 WARNX(0, "incorrect or missing operand: %s", cmdarg); 189 usage(); 190 /* NOTREACHED */ 191 } 192 /* Locate the comma... */ 193 cmdarg1 = strstr(endptr, ","); 194 /* ... and skip past it */ 195 cmdarg1 += 1; 196 level_type = strtoul(cmdarg1, &endptr1, 16); 197 if (*cmdarg1 == '\0' || *endptr1 != '\0') { 198 WARNX(0, "incorrect or missing operand: %s", cmdarg); 199 usage(); 200 /* NOTREACHED */ 201 } 202 203 /* 204 * Fill ioctl argument structure. 205 */ 206 args.level = level; 207 args.level_type = level_type; 208 fd = open(dev, O_RDONLY); 209 if (fd < 0) { 210 WARN(0, "error opening %s for reading", dev); 211 return (1); 212 } 213 error = ioctl(fd, CPUCTL_CPUID_COUNT, &args); 214 if (error < 0) { 215 WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev); 216 close(fd); 217 return (error); 218 } 219 fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x " 220 "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1], 221 args.data[2], args.data[3]); 222 close(fd); 223 return (0); 224 } 225 226 static int 227 do_msr(const char *cmdarg, const char *dev) 228 { 229 unsigned int msr; 230 cpuctl_msr_args_t args; 231 size_t len; 232 uint64_t data = 0; 233 unsigned long command; 234 int do_invert = 0, op; 235 int fd, error; 236 const char *command_name; 237 char *endptr; 238 char *p; 239 240 assert(cmdarg != NULL); 241 assert(dev != NULL); 242 len = strlen(cmdarg); 243 if (len == 0) { 244 WARNX(0, "MSR register expected"); 245 usage(); 246 /* NOTREACHED */ 247 } 248 249 /* 250 * Parse command string. 251 */ 252 msr = strtoul(cmdarg, &endptr, 16); 253 switch (*endptr) { 254 case '\0': 255 op = OP_READ; 256 break; 257 case '=': 258 op = OP_WRITE; 259 break; 260 case '&': 261 op = OP_AND; 262 endptr++; 263 break; 264 case '|': 265 op = OP_OR; 266 endptr++; 267 break; 268 default: 269 op = OP_INVAL; 270 } 271 if (op != OP_READ) { /* Complex operation. */ 272 if (*endptr != '=') 273 op = OP_INVAL; 274 else { 275 p = ++endptr; 276 if (*p == '~') { 277 do_invert = 1; 278 p++; 279 } 280 data = strtoull(p, &endptr, 16); 281 if (*p == '\0' || *endptr != '\0') { 282 WARNX(0, "argument required: %s", cmdarg); 283 usage(); 284 /* NOTREACHED */ 285 } 286 } 287 } 288 if (op == OP_INVAL) { 289 WARNX(0, "invalid operator: %s", cmdarg); 290 usage(); 291 /* NOTREACHED */ 292 } 293 294 /* 295 * Fill ioctl argument structure. 296 */ 297 args.msr = msr; 298 if ((do_invert != 0) ^ (op == OP_AND)) 299 args.data = ~data; 300 else 301 args.data = data; 302 switch (op) { 303 case OP_READ: 304 command = CPUCTL_RDMSR; 305 command_name = "RDMSR"; 306 break; 307 case OP_WRITE: 308 command = CPUCTL_WRMSR; 309 command_name = "WRMSR"; 310 break; 311 case OP_OR: 312 command = CPUCTL_MSRSBIT; 313 command_name = "MSRSBIT"; 314 break; 315 case OP_AND: 316 command = CPUCTL_MSRCBIT; 317 command_name = "MSRCBIT"; 318 break; 319 default: 320 abort(); 321 } 322 fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY); 323 if (fd < 0) { 324 WARN(0, "error opening %s for %s", dev, 325 op == OP_READ ? "reading" : "writing"); 326 return (1); 327 } 328 error = ioctl(fd, command, &args); 329 if (error < 0) { 330 WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command); 331 close(fd); 332 return (1); 333 } 334 if (op == OP_READ) 335 fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr, 336 HIGH(args.data), LOW(args.data)); 337 close(fd); 338 return (0); 339 } 340 341 static int 342 do_update(const char *dev) 343 { 344 int fd; 345 unsigned int i; 346 int error; 347 struct ucode_handler *handler; 348 struct datadir *dir; 349 DIR *dirp; 350 struct dirent *direntry; 351 char buf[MAXPATHLEN]; 352 353 fd = open(dev, O_RDONLY); 354 if (fd < 0) { 355 WARN(0, "error opening %s for reading", dev); 356 return (1); 357 } 358 359 /* 360 * Find the appropriate handler for device. 361 */ 362 for (i = 0; i < NHANDLERS; i++) 363 if (handlers[i].probe(fd) == 0) 364 break; 365 if (i < NHANDLERS) 366 handler = &handlers[i]; 367 else { 368 WARNX(0, "cannot find the appropriate handler for device"); 369 close(fd); 370 return (1); 371 } 372 close(fd); 373 374 /* 375 * Process every image in specified data directories. 376 */ 377 SLIST_FOREACH(dir, &datadirs, next) { 378 dirp = opendir(dir->path); 379 if (dirp == NULL) { 380 WARNX(1, "skipping directory %s: not accessible", dir->path); 381 continue; 382 } 383 while ((direntry = readdir(dirp)) != NULL) { 384 if (direntry->d_namlen == 0) 385 continue; 386 error = snprintf(buf, sizeof(buf), "%s/%s", dir->path, 387 direntry->d_name); 388 if ((unsigned)error >= sizeof(buf)) 389 WARNX(0, "skipping %s, buffer too short", 390 direntry->d_name); 391 if (isdir(buf) != 0) { 392 WARNX(2, "skipping %s: is a directory", buf); 393 continue; 394 } 395 handler->update(dev, buf); 396 } 397 error = closedir(dirp); 398 if (error != 0) 399 WARN(0, "closedir(%s)", dir->path); 400 } 401 return (0); 402 } 403 404 /* 405 * Add new data directory to the search list. 406 */ 407 static void 408 datadir_add(const char *path) 409 { 410 struct datadir *newdir; 411 412 newdir = (struct datadir *)malloc(sizeof(*newdir)); 413 if (newdir == NULL) 414 err(EX_OSERR, "cannot allocate memory"); 415 newdir->path = path; 416 SLIST_INSERT_HEAD(&datadirs, newdir, next); 417 } 418 419 int 420 main(int argc, char *argv[]) 421 { 422 int c, flags; 423 const char *cmdarg; 424 const char *dev; 425 int error; 426 427 flags = 0; 428 error = 0; 429 cmdarg = ""; /* To keep gcc3 happy. */ 430 431 while ((c = getopt(argc, argv, "d:hi:m:nuv")) != -1) { 432 switch (c) { 433 case 'd': 434 datadir_add(optarg); 435 break; 436 case 'i': 437 flags |= FLAG_I; 438 cmdarg = optarg; 439 break; 440 case 'm': 441 flags |= FLAG_M; 442 cmdarg = optarg; 443 break; 444 case 'n': 445 flags |= FLAG_N; 446 break; 447 case 'u': 448 flags |= FLAG_U; 449 break; 450 case 'v': 451 verbosity_level++; 452 break; 453 case 'h': 454 /* FALLTHROUGH */ 455 default: 456 usage(); 457 /* NOTREACHED */ 458 } 459 } 460 argc -= optind; 461 argv += optind; 462 if (argc < 1) { 463 usage(); 464 /* NOTREACHED */ 465 } 466 if ((flags & FLAG_N) == 0) 467 datadir_add(DEFAULT_DATADIR); 468 dev = argv[0]; 469 c = flags & (FLAG_I | FLAG_M | FLAG_U); 470 switch (c) { 471 case FLAG_I: 472 if (strstr(cmdarg, ",") != NULL) 473 error = do_cpuid_count(cmdarg, dev); 474 else 475 error = do_cpuid(cmdarg, dev); 476 break; 477 case FLAG_M: 478 error = do_msr(cmdarg, dev); 479 break; 480 case FLAG_U: 481 error = do_update(dev); 482 break; 483 default: 484 usage(); /* Only one command can be selected. */ 485 } 486 SLIST_FREE(&datadirs, next, free); 487 return (error == 0 ? 0 : 1); 488 } 489