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