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