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