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