]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/cpucontrol/cpucontrol.c
sys/{x86,amd64}: remove one of doubled ;s
[FreeBSD/FreeBSD.git] / usr.sbin / cpucontrol / cpucontrol.c
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 <err.h>
38 #include <errno.h>
39 #include <dirent.h>
40 #include <fcntl.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <sysexits.h>
46
47 #include <sys/queue.h>
48 #include <sys/param.h>
49 #include <sys/types.h>
50 #include <sys/mman.h>
51 #include <sys/stat.h>
52 #include <sys/ioctl.h>
53 #include <sys/cpuctl.h>
54
55 #include "cpucontrol.h"
56 #include "amd.h"
57 #include "intel.h"
58 #include "via.h"
59
60 int     verbosity_level = 0;
61
62 #define DEFAULT_DATADIR "/usr/local/share/cpucontrol"
63
64 #define FLAG_I  0x01
65 #define FLAG_M  0x02
66 #define FLAG_U  0x04
67 #define FLAG_N  0x08
68 #define FLAG_E  0x10
69
70 #define OP_INVAL        0x00
71 #define OP_READ         0x01
72 #define OP_WRITE        0x02
73 #define OP_OR           0x04
74 #define OP_AND          0x08
75
76 #define HIGH(val)       (uint32_t)(((val) >> 32) & 0xffffffff)
77 #define LOW(val)        (uint32_t)((val) & 0xffffffff)
78
79 struct datadir {
80         const char              *path;
81         SLIST_ENTRY(datadir)    next;
82 };
83 static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs);
84
85 static struct ucode_handler {
86         ucode_probe_t *probe;
87         ucode_update_t *update;
88 } handlers[] = {
89         { intel_probe, intel_update },
90         { amd10h_probe, amd10h_update },
91         { amd_probe, amd_update },
92         { via_probe, via_update },
93 };
94 #define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
95
96 static void     usage(void);
97 static int      do_cpuid(const char *cmdarg, const char *dev);
98 static int      do_cpuid_count(const char *cmdarg, const char *dev);
99 static int      do_msr(const char *cmdarg, const char *dev);
100 static int      do_update(const char *dev);
101 static void     datadir_add(const char *path);
102
103 static void __dead2
104 usage(void)
105 {
106         const char *name;
107
108         name = getprogname();
109         if (name == NULL)
110                 name = "cpuctl";
111         fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | "
112             "-i level | -i level,level_type | -e | -u] device\n", name);
113         exit(EX_USAGE);
114 }
115
116 static int
117 do_cpuid(const char *cmdarg, const char *dev)
118 {
119         unsigned int level;
120         cpuctl_cpuid_args_t args;
121         int fd, error;
122         char *endptr;
123
124         assert(cmdarg != NULL);
125         assert(dev != NULL);
126
127         level = strtoul(cmdarg, &endptr, 16);
128         if (*cmdarg == '\0' || *endptr != '\0') {
129                 WARNX(0, "incorrect operand: %s", cmdarg);
130                 usage();
131                 /* NOTREACHED */
132         }
133
134         /*
135          * Fill ioctl argument structure.
136          */
137         args.level = level;
138         fd = open(dev, O_RDONLY);
139         if (fd < 0) {
140                 WARN(0, "error opening %s for reading", dev);
141                 return (1);
142         }
143         error = ioctl(fd, CPUCTL_CPUID, &args);
144         if (error < 0) {
145                 WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev);
146                 close(fd);
147                 return (error);
148         }
149         fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n",
150             level, args.data[0], args.data[1], args.data[2], args.data[3]);
151         close(fd);
152         return (0);
153 }
154
155 static int
156 do_cpuid_count(const char *cmdarg, const char *dev)
157 {
158         char *cmdarg1, *endptr, *endptr1;
159         unsigned int level, level_type;
160         cpuctl_cpuid_count_args_t args;
161         int fd, error;
162
163         assert(cmdarg != NULL);
164         assert(dev != NULL);
165
166         level = strtoul(cmdarg, &endptr, 16);
167         if (*cmdarg == '\0' || *endptr == '\0') {
168                 WARNX(0, "incorrect or missing operand: %s", cmdarg);
169                 usage();
170                 /* NOTREACHED */
171         }
172         /* Locate the comma... */
173         cmdarg1 = strstr(endptr, ",");
174         /* ... and skip past it */
175         cmdarg1 += 1;
176         level_type = strtoul(cmdarg1, &endptr1, 16);
177         if (*cmdarg1 == '\0' || *endptr1 != '\0') {
178                 WARNX(0, "incorrect or missing operand: %s", cmdarg);
179                 usage();
180                 /* NOTREACHED */
181         }
182
183         /*
184          * Fill ioctl argument structure.
185          */
186         args.level = level;
187         args.level_type = level_type;
188         fd = open(dev, O_RDONLY);
189         if (fd < 0) {
190                 WARN(0, "error opening %s for reading", dev);
191                 return (1);
192         }
193         error = ioctl(fd, CPUCTL_CPUID_COUNT, &args);
194         if (error < 0) {
195                 WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev);
196                 close(fd);
197                 return (error);
198         }
199         fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x "
200             "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1],
201             args.data[2], args.data[3]);
202         close(fd);
203         return (0);
204 }
205
206 static int
207 do_msr(const char *cmdarg, const char *dev)
208 {
209         unsigned int msr;
210         cpuctl_msr_args_t args;
211         size_t len;
212         uint64_t data = 0;
213         unsigned long command;
214         int do_invert = 0, op;
215         int fd, error;
216         const char *command_name;
217         char *endptr;
218         char *p;
219
220         assert(cmdarg != NULL);
221         assert(dev != NULL);
222         len = strlen(cmdarg);
223         if (len == 0) {
224                 WARNX(0, "MSR register expected");
225                 usage();
226                 /* NOTREACHED */
227         }
228
229         /*
230          * Parse command string.
231          */
232         msr = strtoul(cmdarg, &endptr, 16);
233         switch (*endptr) {
234         case '\0':
235                 op = OP_READ;
236                 break;
237         case '=':
238                 op = OP_WRITE;
239                 break;
240         case '&':
241                 op = OP_AND;
242                 endptr++;
243                 break;
244         case '|':
245                 op = OP_OR;
246                 endptr++;
247                 break;
248         default:
249                 op = OP_INVAL;
250         }
251         if (op != OP_READ) {    /* Complex operation. */
252                 if (*endptr != '=')
253                         op = OP_INVAL;
254                 else {
255                         p = ++endptr;
256                         if (*p == '~') {
257                                 do_invert = 1;
258                                 p++;
259                         }
260                         data = strtoull(p, &endptr, 16);
261                         if (*p == '\0' || *endptr != '\0') {
262                                 WARNX(0, "argument required: %s", cmdarg);
263                                 usage();
264                                 /* NOTREACHED */
265                         }
266                 }
267         }
268         if (op == OP_INVAL) {
269                 WARNX(0, "invalid operator: %s", cmdarg);
270                 usage();
271                 /* NOTREACHED */
272         }
273
274         /*
275          * Fill ioctl argument structure.
276          */
277         args.msr = msr;
278         if ((do_invert != 0) ^ (op == OP_AND))
279                 args.data = ~data;
280         else
281                 args.data = data;
282         switch (op) {
283         case OP_READ:
284                 command = CPUCTL_RDMSR;
285                 command_name = "RDMSR";
286                 break;
287         case OP_WRITE:
288                 command = CPUCTL_WRMSR;
289                 command_name = "WRMSR";
290                 break;
291         case OP_OR:
292                 command = CPUCTL_MSRSBIT;
293                 command_name = "MSRSBIT";
294                 break;
295         case OP_AND:
296                 command = CPUCTL_MSRCBIT;
297                 command_name = "MSRCBIT";
298                 break;
299         default:
300                 abort();
301         }
302         fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY);
303         if (fd < 0) {
304                 WARN(0, "error opening %s for %s", dev,
305                     op == OP_READ ? "reading" : "writing");
306                 return (1);
307         }
308         error = ioctl(fd, command, &args);
309         if (error < 0) {
310                 WARN(0, "ioctl(%s, CPUCTL_%s (%lu))", dev, command_name, command);
311                 close(fd);
312                 return (1);
313         }
314         if (op == OP_READ)
315                 fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr,
316                     HIGH(args.data), LOW(args.data));
317         close(fd);
318         return (0);
319 }
320
321 static int
322 do_eval_cpu_features(const char *dev)
323 {
324         int fd, error;
325
326         assert(dev != NULL);
327
328         fd = open(dev, O_RDWR);
329         if (fd < 0) {
330                 WARN(0, "error opening %s for writing", dev);
331                 return (1);
332         }
333         error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
334         if (error < 0)
335                 WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
336         close(fd);
337         return (error);
338 }
339
340 static int
341 try_a_fw_image(const char *dev_path, int devfd, int fwdfd, const char *dpath,
342     const char *fname, struct ucode_handler *handler)
343 {
344         struct ucode_update_params parm;
345         struct stat st;
346         char *fw_path;
347         void *fw_map;
348         int fwfd, rc;
349
350         rc = 0;
351         fw_path = NULL;
352         fw_map = MAP_FAILED;
353         fwfd = openat(fwdfd, fname, O_RDONLY);
354         if (fwfd < 0) {
355                 WARN(0, "openat(%s, %s)", dpath, fname);
356                 goto out;
357         }
358
359         rc = asprintf(&fw_path, "%s/%s", dpath, fname);
360         if (rc == -1) {
361                 WARNX(0, "out of memory");
362                 rc = ENOMEM;
363                 goto out;
364         }
365
366         rc = fstat(fwfd, &st);
367         if (rc != 0) {
368                 WARN(0, "fstat(%s)", fw_path);
369                 rc = 0;
370                 goto out;
371         }
372         if (!S_ISREG(st.st_mode))
373                 goto out;
374         if (st.st_size <= 0) {
375                 WARN(0, "%s: empty", fw_path);
376                 goto out;
377         }
378
379         fw_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fwfd, 0);
380         if (fw_map == MAP_FAILED) {
381                 WARN(0, "mmap(%s)", fw_path);
382                 goto out;
383         }
384
385
386         memset(&parm, 0, sizeof(parm));
387         parm.devfd = devfd;
388         parm.fwimage = fw_map;
389         parm.fwsize = st.st_size;
390         parm.dev_path = dev_path;
391         parm.fw_path = fw_path;
392
393         handler->update(&parm);
394
395 out:
396         if (fw_map != MAP_FAILED)
397                 munmap(fw_map, st.st_size);
398         free(fw_path);
399         if (fwfd >= 0)
400                 close(fwfd);
401         return (rc);
402 }
403
404 static int
405 do_update(const char *dev)
406 {
407         int fd, fwdfd;
408         unsigned int i;
409         int error;
410         struct ucode_handler *handler;
411         struct datadir *dir;
412         DIR *dirp;
413         struct dirent *direntry;
414
415         fd = open(dev, O_RDONLY);
416         if (fd < 0) {
417                 WARN(0, "error opening %s for reading", dev);
418                 return (1);
419         }
420
421         /*
422          * Find the appropriate handler for CPU.
423          */
424         for (i = 0; i < NHANDLERS; i++)
425                 if (handlers[i].probe(fd) == 0)
426                         break;
427         if (i < NHANDLERS)
428                 handler = &handlers[i];
429         else {
430                 WARNX(0, "cannot find the appropriate handler for %s", dev);
431                 close(fd);
432                 return (1);
433         }
434         close(fd);
435
436         fd = open(dev, O_RDWR);
437         if (fd < 0) {
438                 WARN(0, "error opening %s for writing", dev);
439                 return (1);
440         }
441
442         /*
443          * Process every image in specified data directories.
444          */
445         SLIST_FOREACH(dir, &datadirs, next) {
446                 fwdfd = open(dir->path, O_RDONLY);
447                 if (fwdfd < 0) {
448                         WARN(1, "skipping directory %s: not accessible", dir->path);
449                         continue;
450                 }
451                 dirp = fdopendir(fwdfd);
452                 if (dirp == NULL) {
453                         WARNX(0, "out of memory");
454                         close(fwdfd);
455                         close(fd);
456                         return (1);
457                 }
458
459                 while ((direntry = readdir(dirp)) != NULL) {
460                         if (direntry->d_namlen == 0)
461                                 continue;
462                         if (direntry->d_type == DT_DIR)
463                                 continue;
464
465                         error = try_a_fw_image(dev, fd, fwdfd, dir->path,
466                             direntry->d_name, handler);
467                         if (error != 0) {
468                                 closedir(dirp);
469                                 close(fd);
470                                 return (1);
471                         }
472                 }
473                 error = closedir(dirp);
474                 if (error != 0)
475                         WARN(0, "closedir(%s)", dir->path);
476         }
477         close(fd);
478         return (0);
479 }
480
481 /*
482  * Add new data directory to the search list.
483  */
484 static void
485 datadir_add(const char *path)
486 {
487         struct datadir *newdir;
488
489         newdir = (struct datadir *)malloc(sizeof(*newdir));
490         if (newdir == NULL)
491                 err(EX_OSERR, "cannot allocate memory");
492         newdir->path = path;
493         SLIST_INSERT_HEAD(&datadirs, newdir, next);
494 }
495
496 int
497 main(int argc, char *argv[])
498 {
499         struct datadir *elm;
500         int c, flags;
501         const char *cmdarg;
502         const char *dev;
503         int error;
504
505         flags = 0;
506         error = 0;
507         cmdarg = "";    /* To keep gcc3 happy. */
508
509         while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) {
510                 switch (c) {
511                 case 'd':
512                         datadir_add(optarg);
513                         break;
514                 case 'e':
515                         flags |= FLAG_E;
516                         break;
517                 case 'i':
518                         flags |= FLAG_I;
519                         cmdarg = optarg;
520                         break;
521                 case 'm':
522                         flags |= FLAG_M;
523                         cmdarg = optarg;
524                         break;
525                 case 'n':
526                         flags |= FLAG_N;
527                         break;
528                 case 'u':
529                         flags |= FLAG_U;
530                         break;
531                 case 'v':
532                         verbosity_level++;
533                         break;
534                 case 'h':
535                         /* FALLTHROUGH */
536                 default:
537                         usage();
538                         /* NOTREACHED */
539                 }
540         }
541         argc -= optind;
542         argv += optind;
543         if (argc < 1) {
544                 usage();
545                 /* NOTREACHED */
546         }
547         if ((flags & FLAG_N) == 0)
548                 datadir_add(DEFAULT_DATADIR);
549         dev = argv[0];
550         c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U);
551         switch (c) {
552         case FLAG_I:
553                 if (strstr(cmdarg, ",") != NULL)
554                         error = do_cpuid_count(cmdarg, dev);
555                 else
556                         error = do_cpuid(cmdarg, dev);
557                 break;
558         case FLAG_M:
559                 error = do_msr(cmdarg, dev);
560                 break;
561         case FLAG_U:
562                 error = do_update(dev);
563                 break;
564         case FLAG_E:
565                 error = do_eval_cpu_features(dev);
566                 break;
567         default:
568                 usage();        /* Only one command can be selected. */
569         }
570         while ((elm = SLIST_FIRST(&datadirs)) != NULL) {
571                 SLIST_REMOVE_HEAD(&datadirs, next);
572                 free(elm);
573         }
574         return (error == 0 ? 0 : 1);
575 }