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