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