1181430Sstas/*- 2217046Sstas * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>. 3181430Sstas * All rights reserved. 4181430Sstas * 5181430Sstas * Redistribution and use in source and binary forms, with or without 6181430Sstas * modification, are permitted provided that the following conditions 7181430Sstas * are met: 8181430Sstas * 1. Redistributions of source code must retain the above copyright 9181430Sstas * notice, this list of conditions and the following disclaimer. 10181430Sstas * 2. Redistributions in binary form must reproduce the above copyright 11181430Sstas * notice, this list of conditions and the following disclaimer in the 12181430Sstas * documentation and/or other materials provided with the distribution. 13181430Sstas * 14181430Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15181430Sstas * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16181430Sstas * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17181430Sstas * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18181430Sstas * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19181430Sstas * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20181430Sstas * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21181430Sstas * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22181430Sstas * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23181430Sstas * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24181430Sstas */ 25181430Sstas 26181430Sstas/* 27181430Sstas * This utility provides userland access to the cpuctl(4) pseudo-device 28181430Sstas * features. 29181430Sstas */ 30181430Sstas 31181430Sstas#include <sys/cdefs.h> 32181430Sstas__FBSDID("$FreeBSD$"); 33181430Sstas 34181430Sstas#include <assert.h> 35181430Sstas#include <stdio.h> 36181430Sstas#include <stdlib.h> 37181430Sstas#include <string.h> 38181430Sstas#include <unistd.h> 39181430Sstas#include <fcntl.h> 40181430Sstas#include <err.h> 41181430Sstas#include <sysexits.h> 42181430Sstas#include <dirent.h> 43181430Sstas 44181430Sstas#include <sys/queue.h> 45181430Sstas#include <sys/param.h> 46181430Sstas#include <sys/types.h> 47181430Sstas#include <sys/stat.h> 48181430Sstas#include <sys/ioctl.h> 49181430Sstas#include <sys/cpuctl.h> 50181430Sstas 51181430Sstas#include "cpucontrol.h" 52181430Sstas#include "amd.h" 53181430Sstas#include "intel.h" 54229471Sfabient#include "via.h" 55181430Sstas 56181430Sstasint verbosity_level = 0; 57181430Sstas 58181430Sstas#define DEFAULT_DATADIR "/usr/local/share/cpucontrol" 59181430Sstas 60181430Sstas#define FLAG_I 0x01 61181430Sstas#define FLAG_M 0x02 62181430Sstas#define FLAG_U 0x04 63181430Sstas 64195189Sstas#define OP_INVAL 0x00 65195189Sstas#define OP_READ 0x01 66195189Sstas#define OP_WRITE 0x02 67195189Sstas#define OP_OR 0x04 68195189Sstas#define OP_AND 0x08 69195189Sstas 70181430Sstas#define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff) 71181430Sstas#define LOW(val) (uint32_t)((val) & 0xffffffff) 72181430Sstas 73181430Sstas/* 74181430Sstas * Macros for freeing SLISTs, probably must be in /sys/queue.h 75181430Sstas */ 76181430Sstas#define SLIST_FREE(head, field, freef) do { \ 77181430Sstas typeof(SLIST_FIRST(head)) __elm0; \ 78181430Sstas typeof(SLIST_FIRST(head)) __elm; \ 79181430Sstas SLIST_FOREACH_SAFE(__elm, (head), field, __elm0) \ 80181430Sstas (void)(freef)(__elm); \ 81181430Sstas} while(0); 82181430Sstas 83181430Sstasstruct datadir { 84181430Sstas const char *path; 85181430Sstas SLIST_ENTRY(datadir) next; 86181430Sstas}; 87201145Santoinestatic SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs); 88181430Sstas 89181430Sstasstruct ucode_handler { 90181430Sstas ucode_probe_t *probe; 91181430Sstas ucode_update_t *update; 92181430Sstas} handlers[] = { 93181430Sstas { intel_probe, intel_update }, 94181430Sstas { amd_probe, amd_update }, 95229471Sfabient { via_probe, via_update }, 96181430Sstas}; 97181430Sstas#define NHANDLERS (sizeof(handlers) / sizeof(*handlers)) 98181430Sstas 99181430Sstasstatic void usage(void); 100181430Sstasstatic int isdir(const char *path); 101181430Sstasstatic int do_cpuid(const char *cmdarg, const char *dev); 102181430Sstasstatic int do_msr(const char *cmdarg, const char *dev); 103181430Sstasstatic int do_update(const char *dev); 104181430Sstasstatic void datadir_add(const char *path); 105181430Sstas 106181430Sstasstatic void __dead2 107201227Sedusage(void) 108181430Sstas{ 109181430Sstas const char *name; 110181430Sstas 111181430Sstas name = getprogname(); 112181430Sstas if (name == NULL) 113181430Sstas name = "cpuctl"; 114181430Sstas fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | " 115181430Sstas "-i level | -u] device\n", name); 116181430Sstas exit(EX_USAGE); 117181430Sstas} 118181430Sstas 119181430Sstasstatic int 120181430Sstasisdir(const char *path) 121181430Sstas{ 122181430Sstas int error; 123181430Sstas struct stat st; 124181430Sstas 125181430Sstas error = stat(path, &st); 126181430Sstas if (error < 0) { 127181430Sstas WARN(0, "stat(%s)", path); 128181430Sstas return (error); 129181430Sstas } 130181430Sstas return (st.st_mode & S_IFDIR); 131181430Sstas} 132181430Sstas 133181430Sstasstatic int 134181430Sstasdo_cpuid(const char *cmdarg, const char *dev) 135181430Sstas{ 136181430Sstas unsigned int level; 137181430Sstas cpuctl_cpuid_args_t args; 138181430Sstas int fd, error; 139181430Sstas char *endptr; 140181430Sstas 141181430Sstas assert(cmdarg != NULL); 142181430Sstas assert(dev != NULL); 143181430Sstas 144181430Sstas level = strtoul(cmdarg, &endptr, 16); 145181430Sstas if (*cmdarg == '\0' || *endptr != '\0') { 146181430Sstas WARNX(0, "incorrect operand: %s", cmdarg); 147181430Sstas usage(); 148181430Sstas /* NOTREACHED */ 149181430Sstas } 150181430Sstas 151181430Sstas /* 152181430Sstas * Fill ioctl argument structure. 153181430Sstas */ 154181430Sstas args.level = level; 155181430Sstas fd = open(dev, O_RDONLY); 156181430Sstas if (fd < 0) { 157181628Sstas WARN(0, "error opening %s for reading", dev); 158181430Sstas return (1); 159181430Sstas } 160181430Sstas error = ioctl(fd, CPUCTL_CPUID, &args); 161181430Sstas if (error < 0) { 162181628Sstas WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev); 163181430Sstas close(fd); 164181430Sstas return (error); 165181430Sstas } 166181430Sstas fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n", 167181430Sstas level, args.data[0], args.data[1], args.data[2], args.data[3]); 168181430Sstas close(fd); 169181430Sstas return (0); 170181430Sstas} 171181430Sstas 172181430Sstasstatic int 173181430Sstasdo_msr(const char *cmdarg, const char *dev) 174181430Sstas{ 175181430Sstas unsigned int msr; 176181430Sstas cpuctl_msr_args_t args; 177195189Sstas size_t len; 178195189Sstas uint64_t data = 0; 179195189Sstas unsigned long command; 180195189Sstas int do_invert = 0, op; 181181430Sstas int fd, error; 182217046Sstas const char *command_name; 183195189Sstas char *endptr; 184181430Sstas char *p; 185181430Sstas 186181430Sstas assert(cmdarg != NULL); 187181430Sstas assert(dev != NULL); 188195189Sstas len = strlen(cmdarg); 189195189Sstas if (len == 0) { 190195189Sstas WARNX(0, "MSR register expected"); 191195189Sstas usage(); 192195189Sstas /* NOTREACHED */ 193195189Sstas } 194181430Sstas 195195189Sstas /* 196195189Sstas * Parse command string. 197195189Sstas */ 198195189Sstas msr = strtoul(cmdarg, &endptr, 16); 199195189Sstas switch (*endptr) { 200195189Sstas case '\0': 201195189Sstas op = OP_READ; 202195189Sstas break; 203195189Sstas case '=': 204195189Sstas op = OP_WRITE; 205195189Sstas break; 206195189Sstas case '&': 207195189Sstas op = OP_AND; 208195189Sstas endptr++; 209195189Sstas break; 210195189Sstas case '|': 211195189Sstas op = OP_OR; 212195189Sstas endptr++; 213195189Sstas break; 214195189Sstas default: 215195189Sstas op = OP_INVAL; 216195189Sstas } 217195189Sstas if (op != OP_READ) { /* Complex operation. */ 218195189Sstas if (*endptr != '=') 219195189Sstas op = OP_INVAL; 220195189Sstas else { 221195189Sstas p = ++endptr; 222195189Sstas if (*p == '~') { 223195189Sstas do_invert = 1; 224195189Sstas p++; 225195189Sstas } 226195189Sstas data = strtoull(p, &endptr, 16); 227195189Sstas if (*p == '\0' || *endptr != '\0') { 228195189Sstas WARNX(0, "argument required: %s", cmdarg); 229195189Sstas usage(); 230195189Sstas /* NOTREACHED */ 231195189Sstas } 232181430Sstas } 233181430Sstas } 234195189Sstas if (op == OP_INVAL) { 235195189Sstas WARNX(0, "invalid operator: %s", cmdarg); 236181430Sstas usage(); 237181430Sstas /* NOTREACHED */ 238181430Sstas } 239181430Sstas 240181430Sstas /* 241181430Sstas * Fill ioctl argument structure. 242181430Sstas */ 243181430Sstas args.msr = msr; 244195189Sstas if ((do_invert != 0) ^ (op == OP_AND)) 245195189Sstas args.data = ~data; 246195189Sstas else 247195189Sstas args.data = data; 248195189Sstas switch (op) { 249195189Sstas case OP_READ: 250195189Sstas command = CPUCTL_RDMSR; 251217119Sstas command_name = "RDMSR"; 252195189Sstas break; 253195189Sstas case OP_WRITE: 254195189Sstas command = CPUCTL_WRMSR; 255217119Sstas command_name = "WRMSR"; 256195189Sstas break; 257195189Sstas case OP_OR: 258195189Sstas command = CPUCTL_MSRSBIT; 259217119Sstas command_name = "MSRSBIT"; 260195189Sstas break; 261195189Sstas case OP_AND: 262195189Sstas command = CPUCTL_MSRCBIT; 263217119Sstas command_name = "MSRCBIT"; 264195189Sstas break; 265195189Sstas default: 266195189Sstas abort(); 267195189Sstas } 268195189Sstas fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY); 269181430Sstas if (fd < 0) { 270181628Sstas WARN(0, "error opening %s for %s", dev, 271195189Sstas op == OP_READ ? "reading" : "writing"); 272181430Sstas return (1); 273181430Sstas } 274195189Sstas error = ioctl(fd, command, &args); 275181430Sstas if (error < 0) { 276217119Sstas WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command); 277181430Sstas close(fd); 278181430Sstas return (1); 279181430Sstas } 280195189Sstas if (op == OP_READ) 281181430Sstas fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr, 282181430Sstas HIGH(args.data), LOW(args.data)); 283181430Sstas close(fd); 284181430Sstas return (0); 285181430Sstas} 286181430Sstas 287181430Sstasstatic int 288181430Sstasdo_update(const char *dev) 289181430Sstas{ 290181430Sstas int fd; 291181430Sstas unsigned int i; 292181430Sstas int error; 293181430Sstas struct ucode_handler *handler; 294181430Sstas struct datadir *dir; 295181430Sstas DIR *dirfd; 296181430Sstas struct dirent *direntry; 297181430Sstas char buf[MAXPATHLEN]; 298181430Sstas 299181430Sstas fd = open(dev, O_RDONLY); 300181430Sstas if (fd < 0) { 301181628Sstas WARN(0, "error opening %s for reading", dev); 302181430Sstas return (1); 303181430Sstas } 304181430Sstas 305181430Sstas /* 306181430Sstas * Find the appropriate handler for device. 307181430Sstas */ 308181430Sstas for (i = 0; i < NHANDLERS; i++) 309181430Sstas if (handlers[i].probe(fd) == 0) 310181430Sstas break; 311181430Sstas if (i < NHANDLERS) 312181430Sstas handler = &handlers[i]; 313181430Sstas else { 314181430Sstas WARNX(0, "cannot find the appropriate handler for device"); 315181430Sstas close(fd); 316181430Sstas return (1); 317181430Sstas } 318181430Sstas close(fd); 319181430Sstas 320181430Sstas /* 321181430Sstas * Process every image in specified data directories. 322181430Sstas */ 323181430Sstas SLIST_FOREACH(dir, &datadirs, next) { 324181430Sstas dirfd = opendir(dir->path); 325181430Sstas if (dirfd == NULL) { 326181430Sstas WARNX(1, "skipping directory %s: not accessible", dir->path); 327181430Sstas continue; 328181430Sstas } 329181430Sstas while ((direntry = readdir(dirfd)) != NULL) { 330181430Sstas if (direntry->d_namlen == 0) 331181430Sstas continue; 332181430Sstas error = snprintf(buf, sizeof(buf), "%s/%s", dir->path, 333181430Sstas direntry->d_name); 334181430Sstas if ((unsigned)error >= sizeof(buf)) 335181430Sstas WARNX(0, "skipping %s, buffer too short", 336181430Sstas direntry->d_name); 337181430Sstas if (isdir(buf) != 0) { 338181430Sstas WARNX(2, "skipping %s: is a directory", buf); 339181430Sstas continue; 340181430Sstas } 341181430Sstas handler->update(dev, buf); 342181430Sstas } 343181430Sstas error = closedir(dirfd); 344181430Sstas if (error != 0) 345181430Sstas WARN(0, "closedir(%s)", dir->path); 346181430Sstas } 347181430Sstas return (0); 348181430Sstas} 349181430Sstas 350181430Sstas/* 351181430Sstas * Add new data directory to the search list. 352181430Sstas */ 353181430Sstasstatic void 354181430Sstasdatadir_add(const char *path) 355181430Sstas{ 356181430Sstas struct datadir *newdir; 357181430Sstas 358181430Sstas newdir = (struct datadir *)malloc(sizeof(*newdir)); 359181430Sstas if (newdir == NULL) 360181430Sstas err(EX_OSERR, "cannot allocate memory"); 361181430Sstas newdir->path = path; 362181430Sstas SLIST_INSERT_HEAD(&datadirs, newdir, next); 363181430Sstas} 364181430Sstas 365181430Sstasint 366181430Sstasmain(int argc, char *argv[]) 367181430Sstas{ 368181430Sstas int c, flags; 369181430Sstas const char *cmdarg; 370181430Sstas const char *dev; 371181430Sstas int error; 372181430Sstas 373181430Sstas flags = 0; 374181430Sstas error = 0; 375181430Sstas cmdarg = ""; /* To keep gcc3 happy. */ 376181430Sstas 377181430Sstas /* 378181430Sstas * Add all default data dirs to the list first. 379181430Sstas */ 380181430Sstas datadir_add(DEFAULT_DATADIR); 381181430Sstas while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) { 382181430Sstas switch (c) { 383181430Sstas case 'd': 384181430Sstas datadir_add(optarg); 385181430Sstas break; 386181430Sstas case 'i': 387181430Sstas flags |= FLAG_I; 388181430Sstas cmdarg = optarg; 389181430Sstas break; 390181430Sstas case 'm': 391181430Sstas flags |= FLAG_M; 392181430Sstas cmdarg = optarg; 393181430Sstas break; 394181430Sstas case 'u': 395181430Sstas flags |= FLAG_U; 396181430Sstas break; 397181430Sstas case 'v': 398181430Sstas verbosity_level++; 399181430Sstas break; 400181430Sstas case 'h': 401181430Sstas /* FALLTHROUGH */ 402181430Sstas default: 403181430Sstas usage(); 404181430Sstas /* NOTREACHED */ 405181430Sstas } 406181430Sstas } 407181430Sstas argc -= optind; 408181430Sstas argv += optind; 409181430Sstas if (argc < 1) { 410181430Sstas usage(); 411181430Sstas /* NOTREACHED */ 412181430Sstas } 413181430Sstas dev = argv[0]; 414181430Sstas c = flags & (FLAG_I | FLAG_M | FLAG_U); 415181430Sstas switch (c) { 416181430Sstas case FLAG_I: 417181430Sstas error = do_cpuid(cmdarg, dev); 418181430Sstas break; 419181430Sstas case FLAG_M: 420181430Sstas error = do_msr(cmdarg, dev); 421181430Sstas break; 422181430Sstas case FLAG_U: 423181430Sstas error = do_update(dev); 424181430Sstas break; 425181430Sstas default: 426181430Sstas usage(); /* Only one command can be selected. */ 427181430Sstas } 428181430Sstas SLIST_FREE(&datadirs, next, free); 429181430Sstas return (error); 430181430Sstas} 431