diff options
Diffstat (limited to 'usr.sbin/cpucontrol/cpucontrol.c')
| -rw-r--r-- | usr.sbin/cpucontrol/cpucontrol.c | 574 |
1 files changed, 574 insertions, 0 deletions
diff --git a/usr.sbin/cpucontrol/cpucontrol.c b/usr.sbin/cpucontrol/cpucontrol.c new file mode 100644 index 000000000000..9cc3968de01d --- /dev/null +++ b/usr.sbin/cpucontrol/cpucontrol.c @@ -0,0 +1,574 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008-2011 Stanislav Sedov <stas@FreeBSD.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This utility provides userland access to the cpuctl(4) pseudo-device + * features. + */ + +#include <sys/cdefs.h> +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sysexits.h> + +#include <sys/queue.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/cpuctl.h> + +#include "cpucontrol.h" +#include "amd.h" +#include "intel.h" +#include "via.h" + +int verbosity_level = 0; + +#define DEFAULT_DATADIR _PATH_LOCALBASE "/share/cpucontrol" + +#define FLAG_I 0x01 +#define FLAG_M 0x02 +#define FLAG_U 0x04 +#define FLAG_N 0x08 +#define FLAG_E 0x10 + +#define OP_INVAL 0x00 +#define OP_READ 0x01 +#define OP_WRITE 0x02 +#define OP_OR 0x04 +#define OP_AND 0x08 + +#define HIGH(val) (uint32_t)(((val) >> 32) & 0xffffffff) +#define LOW(val) (uint32_t)((val) & 0xffffffff) + +struct datadir { + const char *path; + SLIST_ENTRY(datadir) next; +}; +static SLIST_HEAD(, datadir) datadirs = SLIST_HEAD_INITIALIZER(datadirs); + +static struct ucode_handler { + ucode_probe_t *probe; + ucode_update_t *update; +} handlers[] = { + { intel_probe, intel_update }, + { amd10h_probe, amd10h_update }, + { amd_probe, amd_update }, + { via_probe, via_update }, +}; +#define NHANDLERS (sizeof(handlers) / sizeof(*handlers)) + +static void usage(void); +static int do_cpuid(const char *cmdarg, const char *dev); +static int do_cpuid_count(const char *cmdarg, const char *dev); +static int do_msr(const char *cmdarg, const char *dev); +static int do_update(const char *dev); +static void datadir_add(const char *path); + +static void __dead2 +usage(void) +{ + const char *name; + + name = getprogname(); + if (name == NULL) + name = "cpuctl"; + fprintf(stderr, "Usage: %s [-vh] [-d datadir] [-m msr[=value] | " + "-i level | -i level,level_type | -e | -u] device\n", name); + exit(EX_USAGE); +} + +static int +do_cpuid(const char *cmdarg, const char *dev) +{ + unsigned int level; + cpuctl_cpuid_args_t args; + int fd, error; + char *endptr; + + assert(cmdarg != NULL); + assert(dev != NULL); + + level = strtoul(cmdarg, &endptr, 16); + if (*cmdarg == '\0' || *endptr != '\0') { + WARNX(0, "incorrect operand: %s", cmdarg); + usage(); + /* NOTREACHED */ + } + + /* + * Fill ioctl argument structure. + */ + args.level = level; + fd = open(dev, O_RDONLY); + if (fd < 0) { + WARN(0, "error opening %s for reading", dev); + return (1); + } + error = ioctl(fd, CPUCTL_CPUID, &args); + if (error < 0) { + WARN(0, "ioctl(%s, CPUCTL_CPUID)", dev); + close(fd); + return (error); + } + fprintf(stdout, "cpuid level 0x%x: 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n", + level, args.data[0], args.data[1], args.data[2], args.data[3]); + close(fd); + return (0); +} + +static int +do_cpuid_count(const char *cmdarg, const char *dev) +{ + char *cmdarg1, *endptr, *endptr1; + unsigned int level, level_type; + cpuctl_cpuid_count_args_t args; + int fd, error; + + assert(cmdarg != NULL); + assert(dev != NULL); + + level = strtoul(cmdarg, &endptr, 16); + if (*cmdarg == '\0' || *endptr == '\0') { + WARNX(0, "incorrect or missing operand: %s", cmdarg); + usage(); + /* NOTREACHED */ + } + /* Locate the comma... */ + cmdarg1 = strstr(endptr, ","); + /* ... and skip past it */ + cmdarg1 += 1; + level_type = strtoul(cmdarg1, &endptr1, 16); + if (*cmdarg1 == '\0' || *endptr1 != '\0') { + WARNX(0, "incorrect or missing operand: %s", cmdarg); + usage(); + /* NOTREACHED */ + } + + /* + * Fill ioctl argument structure. + */ + args.level = level; + args.level_type = level_type; + fd = open(dev, O_RDONLY); + if (fd < 0) { + WARN(0, "error opening %s for reading", dev); + return (1); + } + error = ioctl(fd, CPUCTL_CPUID_COUNT, &args); + if (error < 0) { + WARN(0, "ioctl(%s, CPUCTL_CPUID_COUNT)", dev); + close(fd); + return (error); + } + fprintf(stdout, "cpuid level 0x%x, level_type 0x%x: 0x%.8x 0x%.8x " + "0x%.8x 0x%.8x\n", level, level_type, args.data[0], args.data[1], + args.data[2], args.data[3]); + close(fd); + return (0); +} + +static int +do_msr(const char *cmdarg, const char *dev) +{ + unsigned int msr; + cpuctl_msr_args_t args; + size_t len; + uint64_t data = 0; + unsigned long command; + int do_invert = 0, op; + int fd, error; + const char *command_name; + char *endptr; + char *p; + + assert(cmdarg != NULL); + assert(dev != NULL); + len = strlen(cmdarg); + if (len == 0) { + WARNX(0, "MSR register expected"); + usage(); + /* NOTREACHED */ + } + + /* + * Parse command string. + */ + msr = strtoul(cmdarg, &endptr, 16); + switch (*endptr) { + case '\0': + op = OP_READ; + break; + case '=': + op = OP_WRITE; + break; + case '&': + op = OP_AND; + endptr++; + break; + case '|': + op = OP_OR; + endptr++; + break; + default: + op = OP_INVAL; + } + if (op != OP_READ) { /* Complex operation. */ + if (*endptr != '=') + op = OP_INVAL; + else { + p = ++endptr; + if (*p == '~') { + do_invert = 1; + p++; + } + data = strtoull(p, &endptr, 16); + if (*p == '\0' || *endptr != '\0') { + WARNX(0, "argument required: %s", cmdarg); + usage(); + /* NOTREACHED */ + } + } + } + if (op == OP_INVAL) { + WARNX(0, "invalid operator: %s", cmdarg); + usage(); + /* NOTREACHED */ + } + + /* + * Fill ioctl argument structure. + */ + args.msr = msr; + if ((do_invert != 0) ^ (op == OP_AND)) + args.data = ~data; + else + args.data = data; + switch (op) { + case OP_READ: + command = CPUCTL_RDMSR; + command_name = "RDMSR"; + break; + case OP_WRITE: + command = CPUCTL_WRMSR; + command_name = "WRMSR"; + break; + case OP_OR: + command = CPUCTL_MSRSBIT; + command_name = "MSRSBIT"; + break; + case OP_AND: + command = CPUCTL_MSRCBIT; + command_name = "MSRCBIT"; + break; + default: + abort(); + } + fd = open(dev, op == OP_READ ? O_RDONLY : O_WRONLY); + if (fd < 0) { + WARN(0, "error opening %s for %s", dev, + op == OP_READ ? "reading" : "writing"); + return (1); + } + error = ioctl(fd, command, &args); + if (error < 0) { + WARN(0, "ioctl(%s, CPUCTL_%s (%#x))", dev, command_name, msr); + close(fd); + return (1); + } + if (op == OP_READ) + fprintf(stdout, "MSR 0x%x: 0x%.8x 0x%.8x\n", msr, + HIGH(args.data), LOW(args.data)); + close(fd); + return (0); +} + +static int +do_eval_cpu_features(const char *dev) +{ + int fd, error; + + assert(dev != NULL); + + fd = open(dev, O_RDWR); + if (fd < 0) { + WARN(0, "error opening %s for writing", dev); + return (1); + } + error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL); + if (error < 0) + WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev); + close(fd); + return (error); +} + +static int +try_a_fw_image(const char *dev_path, int devfd, int fwdfd, const char *dpath, + const char *fname, struct ucode_handler *handler) +{ + struct ucode_update_params parm; + struct stat st; + char *fw_path; + void *fw_map; + int fwfd, rc; + + rc = 0; + fw_path = NULL; + fw_map = MAP_FAILED; + fwfd = openat(fwdfd, fname, O_RDONLY); + if (fwfd < 0) { + WARN(0, "openat(%s, %s)", dpath, fname); + goto out; + } + + rc = asprintf(&fw_path, "%s/%s", dpath, fname); + if (rc == -1) { + WARNX(0, "out of memory"); + rc = ENOMEM; + goto out; + } + + rc = fstat(fwfd, &st); + if (rc != 0) { + WARN(0, "fstat(%s)", fw_path); + rc = 0; + goto out; + } + if (!S_ISREG(st.st_mode)) + goto out; + if (st.st_size <= 0) { + WARN(0, "%s: empty", fw_path); + goto out; + } + + fw_map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fwfd, 0); + if (fw_map == MAP_FAILED) { + WARN(0, "mmap(%s)", fw_path); + goto out; + } + + + memset(&parm, 0, sizeof(parm)); + parm.devfd = devfd; + parm.fwimage = fw_map; + parm.fwsize = st.st_size; + parm.dev_path = dev_path; + parm.fw_path = fw_path; + + handler->update(&parm); + +out: + if (fw_map != MAP_FAILED) + munmap(fw_map, st.st_size); + free(fw_path); + if (fwfd >= 0) + close(fwfd); + return (rc); +} + +static int +do_update(const char *dev) +{ + int fd, fwdfd; + unsigned int i; + int error; + struct ucode_handler *handler; + struct datadir *dir; + DIR *dirp; + struct dirent *direntry; + + fd = open(dev, O_RDONLY); + if (fd < 0) { + WARN(0, "error opening %s for reading", dev); + return (1); + } + + /* + * Find the appropriate handler for CPU. + */ + for (i = 0; i < NHANDLERS; i++) + if (handlers[i].probe(fd) == 0) + break; + if (i < NHANDLERS) + handler = &handlers[i]; + else { + WARNX(0, "cannot find the appropriate handler for %s", dev); + close(fd); + return (1); + } + close(fd); + + fd = open(dev, O_RDWR); + if (fd < 0) { + WARN(0, "error opening %s for writing", dev); + return (1); + } + + /* + * Process every image in specified data directories. + */ + SLIST_FOREACH(dir, &datadirs, next) { + fwdfd = open(dir->path, O_RDONLY); + if (fwdfd < 0) { + WARN(1, "skipping directory %s: not accessible", dir->path); + continue; + } + dirp = fdopendir(fwdfd); + if (dirp == NULL) { + WARNX(0, "out of memory"); + close(fwdfd); + close(fd); + return (1); + } + + while ((direntry = readdir(dirp)) != NULL) { + if (direntry->d_namlen == 0) + continue; + if (direntry->d_type == DT_DIR) + continue; + + error = try_a_fw_image(dev, fd, fwdfd, dir->path, + direntry->d_name, handler); + if (error != 0) { + closedir(dirp); + close(fd); + return (1); + } + } + error = closedir(dirp); + if (error != 0) + WARN(0, "closedir(%s)", dir->path); + } + close(fd); + return (0); +} + +/* + * Add new data directory to the search list. + */ +static void +datadir_add(const char *path) +{ + struct datadir *newdir; + + newdir = (struct datadir *)malloc(sizeof(*newdir)); + if (newdir == NULL) + err(EX_OSERR, "cannot allocate memory"); + newdir->path = path; + SLIST_INSERT_HEAD(&datadirs, newdir, next); +} + +int +main(int argc, char *argv[]) +{ + struct datadir *elm; + int c, flags; + const char *cmdarg; + const char *dev; + int error; + + flags = 0; + error = 0; + cmdarg = ""; /* To keep gcc3 happy. */ + + while ((c = getopt(argc, argv, "d:ehi:m:nuv")) != -1) { + switch (c) { + case 'd': + datadir_add(optarg); + break; + case 'e': + flags |= FLAG_E; + break; + case 'i': + flags |= FLAG_I; + cmdarg = optarg; + break; + case 'm': + flags |= FLAG_M; + cmdarg = optarg; + break; + case 'n': + flags |= FLAG_N; + break; + case 'u': + flags |= FLAG_U; + break; + case 'v': + verbosity_level++; + break; + case 'h': + /* FALLTHROUGH */ + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + if (argc < 1) { + usage(); + /* NOTREACHED */ + } + if ((flags & FLAG_N) == 0) + datadir_add(DEFAULT_DATADIR); + dev = argv[0]; + c = flags & (FLAG_E | FLAG_I | FLAG_M | FLAG_U); + switch (c) { + case FLAG_I: + if (strstr(cmdarg, ",") != NULL) + error = do_cpuid_count(cmdarg, dev); + else + error = do_cpuid(cmdarg, dev); + break; + case FLAG_M: + error = do_msr(cmdarg, dev); + break; + case FLAG_U: + error = do_update(dev); + break; + case FLAG_E: + error = do_eval_cpu_features(dev); + break; + default: + usage(); /* Only one command can be selected. */ + } + while ((elm = SLIST_FIRST(&datadirs)) != NULL) { + SLIST_REMOVE_HEAD(&datadirs, next); + free(elm); + } + return (error == 0 ? 0 : 1); +} |
