xref: /dragonfly/usr.sbin/cpucontrol/cpucontrol.c (revision e62ef63cd8bf9a1124d7110b8e05ff70a95d58e1)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  * This utility provides userland access to the cpuctl(4) pseudo-device
30  * features.
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <assert.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <err.h>
43 #include <sysexits.h>
44 #include <dirent.h>
45 
46 #include <sys/queue.h>
47 #include <sys/param.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <sys/ioctl.h>
51 #include <sys/cpuctl.h>
52 
53 #include "cpucontrol.h"
54 #include "amd.h"
55 #include "intel.h"
56 #include "via.h"
57 
58 int       verbosity_level = 0;
59 
60 #define   DEFAULT_DATADIR     "/usr/local/share/cpucontrol"
61 
62 #define   FLAG_I    0x01
63 #define   FLAG_M    0x02
64 #define   FLAG_U    0x04
65 #define   FLAG_N    0x08
66 #define   FLAG_E    0x10
67 
68 #define   OP_INVAL  0x00
69 #define   OP_READ             0x01
70 #define   OP_WRITE  0x02
71 #define   OP_OR               0x04
72 #define   OP_AND              0x08
73 
74 #define   HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff)
75 #define   LOW(val)  (uint32_t)((val) & 0xffffffff)
76 
77 /*
78  * Macros for freeing SLISTs, probably must be in /sys/queue.h
79  */
80 struct datadir {
81           const char                    *path;
82           SLIST_ENTRY(datadir)          next;
83 };
84 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
85 
86 static struct ucode_handler {
87           ucode_probe_t *probe;
88           ucode_update_t *update;
89 } handlers[] = {
90           { intel_probe, intel_update },
91           { amd10h_probe, amd10h_update },
92           { amd_probe, amd_update },
93           { via_probe, via_update },
94 };
95 #define NHANDLERS (NELEM(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_cpuid_count(const char *cmdarg, const char *dev);
101 static int          do_msr(const char *cmdarg, const char *dev);
102 static int          do_update(const char *dev);
103 static void         datadir_add(const char *path);
104 
105 static void __dead2
usage(void)106 usage(void)
107 {
108           const char *name;
109 
110           name = getprogname();
111           if (name == NULL)
112                     name = "cpuctl";
113           fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
114               "-i level | -i level,level_type | -e | -u] device\n", name);
115           exit(EX_USAGE);
116 }
117 
118 static int
isdir(const char * path)119 isdir(const char *path)
120 {
121           int error;
122           struct stat st;
123 
124           error = stat(path, &st);
125           if (error < 0) {
126                     WARN(0, "stat(%s)", path);
127                     return (error);
128           }
129           return (st.st_mode & S_IFDIR);
130 }
131 
132 static int
do_cpuid(const char * cmdarg,const char * dev)133 do_cpuid(const char *cmdarg, const char *dev)
134 {
135           unsigned int level;
136           cpuctl_cpuid_args_t args;
137           int fd, error;
138           char *endptr;
139 
140           assert(cmdarg != NULL);
141           assert(dev != NULL);
142 
143           level = strtoul(cmdarg, &endptr, 16);
144           if (*cmdarg == '\0' || *endptr != '\0') {
145                     WARNX(0, "incorrect operand: %s", cmdarg);
146                     usage();
147                     /* NOTREACHED */
148           }
149 
150           /*
151            * Fill ioctl argument structure.
152            */
153           args.level = level;
154           fd = open(dev, O_RDONLY);
155           if (fd < 0) {
156                     WARN(0, "error opening %s for reading", dev);
157                     return (1);
158           }
159           error = ioctl(fd, CPUCTL_CPUID, &args);
160           if (error < 0) {
161                     WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
162                     close(fd);
163                     return (error);
164           }
165           fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
166               level, args.data[0], args.data[1], args.data[2], args.data[3]);
167           close(fd);
168           return (0);
169 }
170 
171 static int
do_cpuid_count(const char * cmdarg,const char * dev)172 do_cpuid_count(const char *cmdarg, const char *dev)
173 {
174           char *cmdarg1, *endptr, *endptr1;
175           unsigned int level, level_type;
176           cpuctl_cpuid_count_args_t args;
177           int fd, error;
178 
179           assert(cmdarg != NULL);
180           assert(dev != NULL);
181 
182           level = strtoul(cmdarg, &endptr, 16);
183           if (*cmdarg == '\0' || *endptr == '\0') {
184                     WARNX(0, "incorrect or missing operand: %s", cmdarg);
185                     usage();
186                     /* NOTREACHED */
187           }
188           /* Locate the comma... */
189           cmdarg1 = strstr(endptr, ",");
190           /* ... and skip past it */
191           cmdarg1 += 1;
192           level_type = strtoul(cmdarg1, &endptr1, 16);
193           if (*cmdarg1 == '\0' || *endptr1 != '\0') {
194                     WARNX(0, "incorrect or missing operand: %s", cmdarg);
195                     usage();
196                     /* NOTREACHED */
197           }
198 
199           /*
200            * Fill ioctl argument structure.
201            */
202           args.level = level;
203           args.level_type = level_type;
204           fd = open(dev, O_RDONLY);
205           if (fd < 0) {
206                     WARN(0, "error opening %s for reading", dev);
207                     return (1);
208           }
209           error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
210           if (error < 0) {
211                     WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
212                     close(fd);
213                     return (error);
214           }
215           fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
216               "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
217               args.data[2], args.data[3]);
218           close(fd);
219           return (0);
220 }
221 
222 static int
do_msr(const char * cmdarg,const char * dev)223 do_msr(const char *cmdarg, const char *dev)
224 {
225           unsigned int msr;
226           cpuctl_msr_args_t args;
227           size_t len;
228           uint64_t data = 0;
229           unsigned long command;
230           int do_invert = 0, op;
231           int fd, error;
232           const char *command_name;
233           char *endptr;
234           char *p;
235 
236           assert(cmdarg != NULL);
237           assert(dev != NULL);
238           len = strlen(cmdarg);
239           if (len == 0) {
240                     WARNX(0, "MSR register expected");
241                     usage();
242                     /* NOTREACHED */
243           }
244 
245           /*
246            * Parse command string.
247            */
248           msr = strtoul(cmdarg, &endptr, 16);
249           switch (*endptr) {
250           case '\0':
251                     op = OP_READ;
252                     break;
253           case '=':
254                     op = OP_WRITE;
255                     break;
256           case '&':
257                     op = OP_AND;
258                     endptr++;
259                     break;
260           case '|':
261                     op = OP_OR;
262                     endptr++;
263                     break;
264           default:
265                     op = OP_INVAL;
266           }
267           if (op != OP_READ) {          /* Complex operation. */
268                     if (*endptr != '=')
269                               op = OP_INVAL;
270                     else {
271                               p = ++endptr;
272                               if (*p == '~') {
273                                         do_invert = 1;
274                                         p++;
275                               }
276                               data = strtoull(p, &endptr, 16);
277                               if (*p == '\0' || *endptr != '\0') {
278                                         WARNX(0, "argument required: %s", cmdarg);
279                                         usage();
280                                         /* NOTREACHED */
281                               }
282                     }
283           }
284           if (op == OP_INVAL) {
285                     WARNX(0, "invalid operator: %s", cmdarg);
286                     usage();
287                     /* NOTREACHED */
288           }
289 
290           /*
291            * Fill ioctl argument structure.
292            */
293           args.msr = msr;
294           if ((do_invert != 0) ^ (op == OP_AND))
295                     args.data = ~data;
296           else
297                     args.data = data;
298           switch (op) {
299           case OP_READ:
300                     command = CPUCTL_RDMSR;
301                     command_name = "RDMSR";
302                     break;
303           case OP_WRITE:
304                     command = CPUCTL_WRMSR;
305                     command_name = "WRMSR";
306                     break;
307           case OP_OR:
308                     command = CPUCTL_MSRSBIT;
309                     command_name = "MSRSBIT";
310                     break;
311           case OP_AND:
312                     command = CPUCTL_MSRCBIT;
313                     command_name = "MSRCBIT";
314                     break;
315           default:
316                     abort();
317           }
318           fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
319           if (fd < 0) {
320                     WARN(0, "error opening %s for %s", dev,
321                         op == OP_READ ? "reading" : "writing");
322                     return (1);
323           }
324           error = ioctl(fd, command, &args);
325           if (error < 0) {
326                     WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
327                     close(fd);
328                     return (1);
329           }
330           if (op == OP_READ)
331                     fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
332                         HIGH(args.data), LOW(args.data));
333           close(fd);
334           return (0);
335 }
336 
337 static int
do_eval_cpu_features(const char * dev)338 do_eval_cpu_features(const char *dev)
339 {
340           int fd, error;
341 
342           assert(dev != NULL);
343 
344           fd = open(dev, O_RDWR);
345           if (fd < 0) {
346                     WARN(0, "error opening %s for writing", dev);
347                     return (1);
348           }
349 #if 1
350           error = 0; /* XXX not implemented yet in cpuctl(4) */
351 #else
352           error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
353           if (error < 0)
354                     WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
355 #endif
356           close(fd);
357           return (error);
358 }
359 
360 static int
do_update(const char * dev)361 do_update(const char *dev)
362 {
363           int fd;
364           unsigned int i;
365           int error;
366           struct ucode_handler *handler;
367           struct datadir *dir;
368           DIR *dirp;
369           struct dirent *direntry;
370           char buf[MAXPATHLEN];
371 
372           fd = open(dev, O_RDONLY);
373           if (fd < 0) {
374                     WARN(0, "error opening %s for reading", dev);
375                     return (1);
376           }
377 
378           /*
379            * Find the appropriate handler for device.
380            */
381           for (i = 0; i < NHANDLERS; i++)
382                     if (handlers[i].probe(fd) == 0)
383                               break;
384           if (i < NHANDLERS)
385                     handler = &handlers[i];
386           else {
387                     WARNX(0, "cannot find the appropriate handler for device");
388                     close(fd);
389                     return (1);
390           }
391           close(fd);
392 
393           /*
394            * Process every image in specified data directories.
395            */
396           SLIST_FOREACH(dir, &datadirs, next) {
397                     dirp = opendir(dir->path);
398                     if (dirp == NULL) {
399                               WARNX(1, "skipping directory %s: not accessible", dir->path);
400                               continue;
401                     }
402                     while ((direntry = readdir(dirp)) != NULL) {
403                               if (direntry->d_namlen == 0)
404                                         continue;
405                               error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
406                                   direntry->d_name);
407                               if ((unsigned)error >= sizeof(buf))
408                                         WARNX(0, "skipping %s, buffer too short",
409                                             direntry->d_name);
410                               if (isdir(buf) != 0) {
411                                         WARNX(2, "skipping %s: is a directory", buf);
412                                         continue;
413                               }
414                               handler->update(dev, buf);
415                     }
416                     error = closedir(dirp);
417                     if (error != 0)
418                               WARN(0, "closedir(%s)", dir->path);
419           }
420           return (0);
421 }
422 
423 /*
424  * Add new data directory to the search list.
425  */
426 static void
datadir_add(const char * path)427 datadir_add(const char *path)
428 {
429           struct datadir *newdir;
430 
431           newdir = (struct datadir *)malloc(sizeof(*newdir));
432           if (newdir == NULL)
433                     err(EX_OSERR, "cannot allocate memory");
434           newdir->path = path;
435           SLIST_INSERT_HEAD(&datadirs, newdir, next);
436 }
437 
438 int
main(int argc,char * argv[])439 main(int argc, char *argv[])
440 {
441           int c, flags;
442           const char *cmdarg;
443           const char *dev;
444           int error;
445 
446           flags = 0;
447           error = 0;
448           cmdarg = "";        /* To keep gcc3 happy. */
449 
450           while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) {
451                     switch (c) {
452                     case 'd':
453                               datadir_add(optarg);
454                               break;
455                     case 'e':
456                               flags |= FLAG_E;
457                               break;
458                     case 'i':
459                               flags |= FLAG_I;
460                               cmdarg = optarg;
461                               break;
462                     case 'm':
463                               flags |= FLAG_M;
464                               cmdarg = optarg;
465                               break;
466                     case 'n':
467                               flags |= FLAG_N;
468                               break;
469                     case 'u':
470                               flags |= FLAG_U;
471                               break;
472                     case 'v':
473                               verbosity_level++;
474                               break;
475                     case 'h':
476                               /* FALLTHROUGH */
477                     default:
478                               usage();
479                               /* NOTREACHED */
480                     }
481           }
482           argc -= optind;
483           argv += optind;
484           if (argc < 1) {
485                     usage();
486                     /* NOTREACHED */
487           }
488           if ((flags & FLAG_N) == 0)
489                     datadir_add(DEFAULT_DATADIR);
490           dev = argv[0];
491           c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U);
492           switch (c) {
493           case FLAG_I:
494                     if (strstr(cmdarg, ",") != NULL)
495                               error = do_cpuid_count(cmdarg, dev);
496                     else
497                               error = do_cpuid(cmdarg, dev);
498                     break;
499           case FLAG_M:
500                     error = do_msr(cmdarg, dev);
501                     break;
502           case FLAG_U:
503                     error = do_update(dev);
504                     break;
505           case FLAG_E:
506                     error = do_eval_cpu_features(dev);
507                     break;
508           default:
509                     usage();  /* Only one command can be selected. */
510           }
511           return (error == 0 ? 0 : 1);
512 }
513