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