#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <sysexits.h>
#include <dirent.h>
#include <sys/queue.h>
#include <sys/param.h>
#include <sys/types.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 "/usr/local/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 (NELEM(handlers))
static void usage(void);
static int isdir(const char *path);
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
isdir(const char *path)
{
int error;
struct stat st;
error = stat(path, &st);
if (error < 0) {
WARN(0, "stat(%s)", path);
return (error);
}
return (st.st_mode & S_IFDIR);
}
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();
}
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();
}
cmdarg1 = strstr(endptr, ",");
cmdarg1 += 1;
level_type = strtoul(cmdarg1, &endptr1, 16);
if (*cmdarg1 == '\0' || *endptr1 != '\0') {
WARNX(0, "incorrect or missing operand: %s", cmdarg);
usage();
}
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();
}
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) {
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();
}
}
}
if (op == OP_INVAL) {
WARNX(0, "invalid operator: %s", cmdarg);
usage();
}
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 (%lu))", dev, command_name, command);
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);
}
#if 1
error = 0;
#else
error = ioctl(fd, CPUCTL_EVAL_CPU_FEATURES, NULL);
if (error < 0)
WARN(0, "ioctl(%s, CPUCTL_EVAL_CPU_FEATURES)", dev);
#endif
close(fd);
return (error);
}
static int
do_update(const char *dev)
{
int fd;
unsigned int i;
int error;
struct ucode_handler *handler;
struct datadir *dir;
DIR *dirp;
struct dirent *direntry;
char buf[MAXPATHLEN];
fd = open(dev, O_RDONLY);
if (fd < 0) {
WARN(0, "error opening %s for reading", dev);
return (1);
}
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 device");
close(fd);
return (1);
}
close(fd);
SLIST_FOREACH(dir, &datadirs, next) {
dirp = opendir(dir->path);
if (dirp == NULL) {
WARNX(1, "skipping directory %s: not accessible", dir->path);
continue;
}
while ((direntry = readdir(dirp)) != NULL) {
if (direntry->d_namlen == 0)
continue;
error = snprintf(buf, sizeof(buf), "%s/%s", dir->path,
direntry->d_name);
if ((unsigned)error >= sizeof(buf))
WARNX(0, "skipping %s, buffer too short",
direntry->d_name);
if (isdir(buf) != 0) {
WARNX(2, "skipping %s: is a directory", buf);
continue;
}
handler->update(dev, buf);
}
error = closedir(dirp);
if (error != 0)
WARN(0, "closedir(%s)", dir->path);
}
return (0);
}
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[])
{
int c, flags;
const char *cmdarg;
const char *dev;
int error;
flags = 0;
error = 0;
cmdarg = "";
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':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
usage();
}
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();
}
return (error == 0 ? 0 : 1);
}