Import cpuctl pseudo device from FreeBSD
authorJoris Giovannangeli <joris@giovannangeli.fr>
Tue, 18 Nov 2014 20:49:21 +0000 (21:49 +0100)
committerJoris Giovannangeli <joris@giovannangeli.fr>
Tue, 18 Nov 2014 20:51:35 +0000 (21:51 +0100)
The cpuctl pseudo device allows to perform cpu microcode updates.

24 files changed:
share/man/man4/Makefile
share/man/man4/cpuctl.4 [new file with mode: 0644]
sys/conf/files
sys/config/LINT
sys/config/LINT64
sys/cpu/i386/include/cpufunc.h
sys/cpu/x86_64/include/cpufunc.h
sys/dev/misc/Makefile
sys/dev/misc/cpuctl/Makefile [new file with mode: 0644]
sys/dev/misc/cpuctl/cpuctl.c [new file with mode: 0644]
sys/platform/pc32/i386/support.s
sys/platform/pc64/x86_64/support.s
sys/sys/cpuctl.h [new file with mode: 0644]
usr.sbin/Makefile
usr.sbin/cpucontrol/Makefile [new file with mode: 0644]
usr.sbin/cpucontrol/amd.c [new file with mode: 0644]
usr.sbin/cpucontrol/amd.h [new file with mode: 0644]
usr.sbin/cpucontrol/cpucontrol.8 [new file with mode: 0644]
usr.sbin/cpucontrol/cpucontrol.c [new file with mode: 0644]
usr.sbin/cpucontrol/cpucontrol.h [new file with mode: 0644]
usr.sbin/cpucontrol/intel.c [new file with mode: 0644]
usr.sbin/cpucontrol/intel.h [new file with mode: 0644]
usr.sbin/cpucontrol/via.c [new file with mode: 0644]
usr.sbin/cpucontrol/via.h [new file with mode: 0644]

index 49325a5..4e753ce 100644 (file)
@@ -67,6 +67,7 @@ MAN=  aac.4 \
        clockmod.4 \
        cmx.4 \
        coretemp.4 \
+       cpuctl.4 \
        crypto.4 \
        cue.4 \
        cxm.4 \
diff --git a/share/man/man4/cpuctl.4 b/share/man/man4/cpuctl.4
new file mode 100644 (file)
index 0000000..dbff6eb
--- /dev/null
@@ -0,0 +1,154 @@
+.\" Copyright (c) 2006-2008 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.\" $FreeBSD: release/10.0.0/share/man/man4/cpuctl.4 235317 2012-05-12 03:25:46Z gjb $
+.\"
+.Dd June 30, 2009
+.Dt CPUCTL 4
+.Os
+.Sh NAME
+.Nm cpuctl
+.Nd cpuctl pseudo device
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your kernel
+configuration file:
+.Bd -ragged -offset indent
+.Cd "device cpuctl"
+.Ed
+.Pp
+Alternatively, to load the driver as a module
+at boot time, place the following in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+cpuctl_load="YES"
+.Ed
+.Sh DESCRIPTION
+The special device
+.Pa /dev/cpuctl
+presents interface to the system CPU.
+It provides functionality to retrieve
+CPUID information, read/write machine specific registers (MSR) and perform
+CPU firmware updates.
+.Pp
+For each CPU present in the system, the special device
+.Pa /dev/cpuctl%d
+with the appropriate index will be created.
+For multicore CPUs such a
+special device will be created for each core.
+.Pp
+Currently, only i386 and amd64 processors are
+supported.
+.Sh IOCTL INTERFACE
+All of the supported operations are invoked using the
+.Xr ioctl 2
+system call.
+Currently, the following ioctls are defined:
+.Bl -tag -width CPUCTL_UPDATE
+.It Dv CPUCTL_RDMSR Fa cpuctl_msr_args_t *args
+.It Dv CPUCTL_WRMSR Fa cpuctl_msr_args_t *args
+Read/write CPU machine specific register.
+The
+.Vt cpuctl_msr_args_t
+structure is defined in
+.In sys/cpuctl.h
+as:
+.Bd -literal
+typedef struct {
+       int             msr;    /* MSR to read */
+       uint64_t        data;
+} cpuctl_msr_args_t;
+.Ed
+.It Dv CPUCTL_MSRSBIT Fa cpuctl_msr_args_t *args
+.It Dv CPUCTL_MSRCBIT Fa cpuctl_msr_args_t *args
+Set/clear MSR bits according to the mask given in the
+.Va data
+field.
+.It Dv CPUCTL_CPUID Fa cpuctl_cpuid_args_t *args
+Retrieve CPUID information.
+Arguments are supplied in
+the following struct:
+.Bd -literal
+typedef struct {
+       int             level;  /* CPUID level */
+       uint32_t        data[4];
+} cpuctl_cpuid_args_t;
+.Ed
+.Pp
+The
+.Va level
+field indicates the CPUID level to retrieve information for, while the
+.Va data
+field is used to store the received CPUID data.
+.It Dv CPUCTL_UPDATE cpuctl_update_args_t *args
+Update CPU firmware (microcode).
+The structure is defined in
+.In sys/cpuctl.h
+as:
+.Bd -literal
+typedef struct {
+       void    *data;
+       size_t  size;
+} cpuctl_update_args_t;
+.Ed
+.Pp
+The
+.Va data
+field should point to the firmware image of size
+.Va size .
+.El
+.Pp
+For additional information refer to
+.Pa cpuctl.h .
+.Sh RETURN VALUES
+.Bl -tag -width Er
+.It Bq Er ENXIO
+The operation requested is not supported by the device (e.g., unsupported
+architecture or the CPU is disabled).
+.It Bq Er EINVAL
+Incorrect request was supplied, or microcode image is not correct.
+.It Bq Er ENOMEM
+No physical memory was available to complete the request.
+.It Bq Er EFAULT
+The firmware image address points outside the process address space.
+.El
+.Sh FILES
+.Bl -tag -width /dev/cpuctl -compact
+.It Pa /dev/cpuctl
+.El
+.Sh SEE ALSO
+.Xr hwpmc 4 ,
+.Xr cpucontrol 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 7.2 .
+.Sh AUTHORS
+The
+.Nm
+module and this manual page were written by
+.An Stanislav Sedov Aq stas@FreeBSD.org .
+.Sh BUGS
+Yes, probably, report if any.
index 9317ed9..2aa55f3 100644 (file)
@@ -428,6 +428,7 @@ dev/powermng/perfbias/perfbias.c    optional perfbias
 dev/powermng/coretemp/coretemp.c       optional coretemp
 dev/powermng/kate/kate.c       optional kate pci
 dev/powermng/km/km.c           optional km pci
+dev/cpuctl/cpuctl.c            optional        cpuctl
 dev/raid/ida/ida.c             optional ida
 dev/raid/ida/ida_disk.c        optional ida
 dev/raid/ida/ida_pci.c optional ida pci
index 676de74..94862d4 100644 (file)
@@ -1852,6 +1852,10 @@ device           clockmod
 # Intel Core and newer CPUs on-die digital thermal sensor support
 device         coretemp
 
+# CPU control pseudo-device. Provides access to MSRs, CPUID info and
+# microcode update feature.
+device         cpuctl
+
 # AMD Family 0Fh, 10h and 11h temperature sensors
 device         kate
 device         km
index d4a05af..4c6f98c 100644 (file)
@@ -1696,6 +1696,10 @@ device           clockmod
 # Intel Core and newer CPUs on-die digital thermal sensor support
 device         coretemp
 
+# CPU control pseudo-device. Provides access to MSRs, CPUID info and
+# microcode update feature.
+device         cpuctl
+
 # AMD Family 0Fh, 10h and 11h temperature sensors
 device         kate
 device         km
index 15f0b5e..d4c2d9d 100644 (file)
@@ -790,6 +790,7 @@ u_int       rcr0            (void);
 u_int  rcr3            (void);
 u_int  rcr4            (void);
 int    rdmsr_safe      (u_int msr, uint64_t *val);
+int    wrmsr_safe(u_int msr, uint64_t newval);
 void    reset_dbregs    (void);
 __END_DECLS
 
index 9486fc0..23806cb 100644 (file)
@@ -979,6 +979,7 @@ void        intr_restore(register_t rf);
 #endif /* __GNUC__ */
 
 int    rdmsr_safe(u_int msr, uint64_t *val);
+int wrmsr_safe(u_int msr, uint64_t newval);
 void   reset_dbregs(void);
 
 __END_DECLS
index 46b14c2..81d5646 100644 (file)
@@ -1,3 +1,3 @@
-SUBDIR= amdsbwd cmx dcons ecc ichwd joy kbdmux lpbb pcfclock nmdm putter syscons snp tbridge
+SUBDIR= amdsbwd cmx cpuctl dcons ecc ichwd joy kbdmux lpbb pcfclock nmdm putter syscons snp tbridge
 
 .include <bsd.subdir.mk>
diff --git a/sys/dev/misc/cpuctl/Makefile b/sys/dev/misc/cpuctl/Makefile
new file mode 100644 (file)
index 0000000..2c52ce9
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD: release/10.0.0/sys/modules/cpuctl/Makefile 181430 2008-08-08 16:26:53Z stas $
+
+KMOD=  cpuctl
+SRCS=  cpuctl.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/dev/misc/cpuctl/cpuctl.c b/sys/dev/misc/cpuctl/cpuctl.c
new file mode 100644 (file)
index 0000000..7896f20
--- /dev/null
@@ -0,0 +1,471 @@
+/*-
+ * Copyright (c) 2006-2008 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ *
+ */
+
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/ioccom.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/sched.h>
+#include <sys/kernel.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+#include <sys/cpuctl.h>
+#include <sys/device.h>
+#include <sys/thread2.h>
+
+#include <machine/cpufunc.h>
+#include <machine/md_var.h>
+#include <machine/specialreg.h>
+
+static d_open_t cpuctl_open;
+static d_ioctl_t cpuctl_ioctl;
+
+#define        CPUCTL_VERSION 1
+
+#ifdef DEBUG
+# define DPRINTF(format,...)    kprintf(format, __VA_ARGS__); 
+#else
+# define DPRINTF(format,...)
+#endif
+
+#define        UCODE_SIZE_MAX  (16 * 1024)
+
+static int cpuctl_do_msr(int cpu, cpuctl_msr_args_t *data, u_long cmd);
+static int cpuctl_do_cpuid(int cpu, cpuctl_cpuid_args_t *data);
+static int cpuctl_do_update(int cpu, cpuctl_update_args_t *data);
+static int update_intel(int cpu, cpuctl_update_args_t *args);
+static int update_amd(int cpu, cpuctl_update_args_t *args);
+static int update_via(int cpu, cpuctl_update_args_t *args);
+
+static cdev_t *cpuctl_devs;
+static MALLOC_DEFINE(M_CPUCTL, "cpuctl", "CPUCTL buffer");
+
+static struct dev_ops cpuctl_cdevsw = {
+        .d_open =       cpuctl_open,
+        .d_ioctl =      cpuctl_ioctl,
+        .head = { .name =       "cpuctl" },
+};
+
+int
+cpuctl_ioctl(struct dev_ioctl_args *ap)
+{
+       int ret;
+       int cpu = dev2unit(ap->a_head.a_dev);
+       u_long cmd = ap->a_cmd;
+       int flags = ap->a_fflag;
+       caddr_t data = ap->a_data;
+
+       if (cpu >= ncpus) {
+               DPRINTF("[cpuctl,%d]: bad cpu number %d\n", __LINE__, cpu);
+               return (ENXIO);
+       }
+       /* Require write flag for "write" requests. */
+       if ((cmd == CPUCTL_WRMSR || cmd == CPUCTL_UPDATE) &&
+           ((flags & FWRITE) == 0))
+               return (EPERM);
+       switch (cmd) {
+       case CPUCTL_RDMSR:
+               ret = cpuctl_do_msr(cpu, (cpuctl_msr_args_t *)data, cmd);
+               break;
+       case CPUCTL_MSRSBIT:
+       case CPUCTL_MSRCBIT:
+       case CPUCTL_WRMSR:
+               ret = priv_check(curthread, PRIV_CPUCTL_WRMSR);
+               if (ret != 0)
+                       goto fail;
+               ret = cpuctl_do_msr(cpu, (cpuctl_msr_args_t *)data, cmd);
+               break;
+       case CPUCTL_CPUID:
+               ret = cpuctl_do_cpuid(cpu, (cpuctl_cpuid_args_t *)data);
+               break;
+       case CPUCTL_UPDATE:
+               ret = priv_check(curthread, PRIV_CPUCTL_UPDATE);
+               if (ret != 0)
+                       goto fail;
+               ret = cpuctl_do_update(cpu, (cpuctl_update_args_t *)data);
+               break;
+       default:
+               ret = EINVAL;
+               break;
+       }
+fail:
+       return (ret);
+}
+
+/*
+ * Actually perform cpuid operation.
+ */
+static int
+cpuctl_do_cpuid(int cpu, cpuctl_cpuid_args_t *data)
+{
+       int oldcpu;
+
+       KASSERT(cpu >= 0 && cpu < ncpus,
+           ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu));
+
+       /* Explicitly clear cpuid data to avoid returning stale info. */
+       bzero(data->data, sizeof(data->data));
+       DPRINTF("[cpuctl,%d]: retriving cpuid level %#0x for %d cpu\n",
+           __LINE__, data->level, cpu);
+       oldcpu = mycpuid;
+       lwkt_migratecpu(cpu);
+       cpuid_count(data->level, 0, data->data);
+       lwkt_migratecpu(oldcpu);
+       return (0);
+}
+
+/*
+ * Actually perform MSR operations.
+ */
+static int
+cpuctl_do_msr(int cpu, cpuctl_msr_args_t *data, u_long cmd)
+{
+       uint64_t reg;
+       int oldcpu;
+       int ret;
+
+       KASSERT(cpu >= 0 && cpu < ncpus ,
+           ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu));
+
+       /*
+        * Explicitly clear cpuid data to avoid returning stale
+        * info
+        */
+       DPRINTF("[cpuctl,%d]: operating on MSR %#0x for %d cpu\n", __LINE__,
+           data->msr, cpu);
+       oldcpu = mycpuid;
+       lwkt_migratecpu(cpu);
+       if (cmd == CPUCTL_RDMSR) {
+               data->data = 0;
+               ret = rdmsr_safe(data->msr, &data->data);
+       } else if (cmd == CPUCTL_WRMSR) {
+               ret = wrmsr_safe(data->msr, data->data);
+       } else if (cmd == CPUCTL_MSRSBIT) {
+               crit_enter();
+               ret = rdmsr_safe(data->msr, &reg);
+               if (ret == 0)
+                       ret = wrmsr_safe(data->msr, reg | data->data);
+               crit_exit();
+       } else if (cmd == CPUCTL_MSRCBIT) {
+               crit_enter();
+               ret = rdmsr_safe(data->msr, &reg);
+               if (ret == 0)
+                       ret = wrmsr_safe(data->msr, reg & ~data->data);
+               crit_exit();
+       } else
+               panic("[cpuctl,%d]: unknown operation requested: %lu", __LINE__, cmd);
+       lwkt_migratecpu(oldcpu);
+       return (ret);
+}
+
+/*
+ * Actually perform microcode update.
+ */
+static int
+cpuctl_do_update(int cpu, cpuctl_update_args_t *data)
+{
+       cpuctl_cpuid_args_t args = {
+               .level = 0,
+       };
+       char vendor[13];
+       int ret;
+
+       KASSERT(cpu >= 0 && cpu < ncpus,
+           ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu));
+       DPRINTF("[cpuctl,%d]: XXX %d", __LINE__, cpu);
+
+       ret = cpuctl_do_cpuid(cpu, &args);
+       if (ret != 0) {
+               DPRINTF("[cpuctl,%d]: cannot retrive cpuid info for cpu %d",
+                   __LINE__, cpu);
+               return (ENXIO);
+       }
+       ((uint32_t *)vendor)[0] = args.data[1];
+       ((uint32_t *)vendor)[1] = args.data[3];
+       ((uint32_t *)vendor)[2] = args.data[2];
+       vendor[12] = '\0';
+       if (strncmp(vendor, INTEL_VENDOR_ID, sizeof(INTEL_VENDOR_ID)) == 0)
+               ret = update_intel(cpu, data);
+       else if(strncmp(vendor, AMD_VENDOR_ID, sizeof(AMD_VENDOR_ID)) == 0)
+               ret = update_amd(cpu, data);
+       else if(strncmp(vendor, CENTAUR_VENDOR_ID, sizeof(CENTAUR_VENDOR_ID)) == 0)
+               ret = update_via(cpu, data);
+       else
+               ret = ENXIO;
+       return (ret);
+}
+
+static int
+update_intel(int cpu, cpuctl_update_args_t *args)
+{
+       void *ptr;
+       uint64_t rev0, rev1;
+       uint32_t tmp[4];
+       int oldcpu;
+       int ret;
+
+       if (args->size == 0 || args->data == NULL) {
+               DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__);
+               return (EINVAL);
+       }
+       if (args->size > UCODE_SIZE_MAX) {
+               DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__);
+               return (EINVAL);
+       }
+
+       /*
+        * 16 byte alignment required.  Rely on the fact that
+        * malloc(9) always returns the pointer aligned at least on
+        * the size of the allocation.
+        */
+       ptr = kmalloc(args->size + 16, M_CPUCTL, M_WAITOK);
+       if (copyin(args->data, ptr, args->size) != 0) {
+               DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed",
+                   __LINE__, args->data, ptr, args->size);
+               ret = EFAULT;
+               goto fail;
+       }
+       oldcpu = mycpuid;
+       lwkt_migratecpu(cpu);
+       crit_enter();
+       rdmsr_safe(MSR_BIOS_SIGN, &rev0); /* Get current microcode revision. */
+
+       /*
+        * Perform update.
+        */
+       wrmsr_safe(MSR_BIOS_UPDT_TRIG, (uintptr_t)(ptr));
+       wrmsr_safe(MSR_BIOS_SIGN, 0);
+
+       /*
+        * Serialize instruction flow.
+        */
+       do_cpuid(0, tmp);
+       crit_exit();
+       rdmsr_safe(MSR_BIOS_SIGN, &rev1); /* Get new microcode revision. */
+       lwkt_migratecpu(oldcpu);
+       kprintf("[cpu %d]: updated microcode from rev=0x%x to rev=0x%x\n", cpu,
+           (unsigned)(rev0 >> 32), (unsigned)(rev1 >> 32));
+
+       if (rev1 > rev0)
+               ret = 0;
+       else
+               ret = EEXIST;
+fail:
+       kfree(ptr, M_CPUCTL);
+       return (ret);
+}
+
+static int
+update_amd(int cpu, cpuctl_update_args_t *args)
+{
+       void *ptr = NULL;
+       uint32_t tmp[4];
+       int oldcpu;
+       int ret;
+
+       if (args->size == 0 || args->data == NULL) {
+               DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__);
+               return (EINVAL);
+       }
+       if (args->size > UCODE_SIZE_MAX) {
+               DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__);
+               return (EINVAL);
+       }
+       /*
+        * XXX Might not require contignous address space - needs check
+        */
+       ptr = contigmalloc(args->size, M_CPUCTL, 0, 0, 0xffffffff, 16, 0);
+       if (ptr == NULL) {
+               DPRINTF("[cpuctl,%d]: cannot allocate %zd bytes of memory",
+                   __LINE__, args->size);
+               return (ENOMEM);
+       }
+       if (copyin(args->data, ptr, args->size) != 0) {
+               DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed",
+                   __LINE__, args->data, ptr, args->size);
+               ret = EFAULT;
+               goto fail;
+       }
+       oldcpu = mycpuid;
+       lwkt_migratecpu(cpu);
+       crit_enter();
+
+       /*
+        * Perform update.
+        */
+       wrmsr_safe(MSR_K8_UCODE_UPDATE, (uintptr_t)ptr);
+
+       /*
+        * Serialize instruction flow.
+        */
+       do_cpuid(0, tmp);
+       crit_exit();
+       lwkt_migratecpu(oldcpu);
+       ret = 0;
+fail:
+       if (ptr != NULL)
+               contigfree(ptr, args->size, M_CPUCTL);
+       return (ret);
+}
+
+static int
+update_via(int cpu, cpuctl_update_args_t *args)
+{
+       void *ptr;
+       uint64_t rev0, rev1, res;
+       uint32_t tmp[4];
+       int oldcpu;
+       int ret;
+
+       if (args->size == 0 || args->data == NULL) {
+               DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__);
+               return (EINVAL);
+       }
+       if (args->size > UCODE_SIZE_MAX) {
+               DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__);
+               return (EINVAL);
+       }
+
+       /*
+        * 4 byte alignment required.
+        */
+       ptr = kmalloc(args->size, M_CPUCTL, M_WAITOK);
+       if (copyin(args->data, ptr, args->size) != 0) {
+               DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed",
+                   __LINE__, args->data, ptr, args->size);
+               ret = EFAULT;
+               goto fail;
+       }
+       oldcpu = mycpuid;
+       lwkt_migratecpu(cpu);
+       crit_enter();
+       rdmsr_safe(MSR_BIOS_SIGN, &rev0); /* Get current microcode revision. */
+
+       /*
+        * Perform update.
+        */
+       wrmsr_safe(MSR_BIOS_UPDT_TRIG, (uintptr_t)(ptr));
+       do_cpuid(1, tmp);
+
+       /*
+        * Result are in low byte of MSR FCR5:
+        * 0x00: No update has been attempted since RESET.
+        * 0x01: The last attempted update was successful.
+        * 0x02: The last attempted update was unsuccessful due to a bad
+        *       environment. No update was loaded and any preexisting
+        *       patches are still active.
+        * 0x03: The last attempted update was not applicable to this processor.
+        *       No update was loaded and any preexisting patches are still
+        *       active.
+        * 0x04: The last attempted update was not successful due to an invalid
+        *       update data block. No update was loaded and any preexisting
+        *       patches are still active
+        */
+       rdmsr_safe(0x1205, &res);
+       res &= 0xff;
+       crit_exit();
+       rdmsr_safe(MSR_BIOS_SIGN, &rev1); /* Get new microcode revision. */
+       lwkt_migratecpu(oldcpu);
+
+       DPRINTF("[cpu,%d]: rev0=%x rev1=%x res=%x\n", __LINE__,
+           (unsigned)(rev0 >> 32), (unsigned)(rev1 >> 32), (unsigned)res);
+
+       if (res != 0x01)
+               ret = EINVAL;
+       else
+               ret = 0;
+fail:
+       kfree(ptr, M_CPUCTL);
+       return (ret);
+}
+
+int
+cpuctl_open(struct dev_open_args *ap)
+{
+       int ret = 0;
+       int cpu;
+
+       cpu = dev2unit(ap->a_head.a_dev);
+       if (cpu > ncpus) {
+               DPRINTF("[cpuctl,%d]: incorrect cpu number %d\n", __LINE__,
+                   cpu);
+               return (ENXIO);
+       }
+       if (ap->a_oflags & FWRITE)
+               ret = securelevel > 0 ? EPERM : 0;
+       return (ret);
+}
+
+static int
+cpuctl_modevent(module_t mod __unused, int type, void *data __unused)
+{
+       int cpu;
+
+       switch(type) {
+       case MOD_LOAD:
+               if ((cpu_feature & CPUID_MSR) == 0) {
+                       if (bootverbose)
+                               kprintf("cpuctl: not available.\n");
+                       return (ENODEV);
+               }
+               if (bootverbose)
+                       kprintf("cpuctl: access to MSR registers/cpuid info.\n");
+               cpuctl_devs = (struct cdev **)kmalloc(sizeof(void *) * ncpus,
+                   M_CPUCTL, M_WAITOK | M_ZERO);
+               if (cpuctl_devs == NULL) {
+                       DPRINTF("[cpuctl,%d]: cannot allocate memory\n",
+                           __LINE__);
+                       return (ENOMEM);
+               }
+               for (cpu = 0; cpu < ncpus; cpu++)
+                       cpuctl_devs[cpu] = make_dev(&cpuctl_cdevsw, cpu,
+                                       UID_ROOT, GID_KMEM, 0640, "cpuctl%d", cpu);
+               break;
+       case MOD_UNLOAD:
+               for (cpu = 0; cpu < ncpus; cpu++) {
+                       if (cpuctl_devs[cpu] != NULL)
+                               destroy_dev(cpuctl_devs[cpu]);
+               }
+               kfree(cpuctl_devs, M_CPUCTL);
+               break;
+       case MOD_SHUTDOWN:
+               break;
+       default:
+               return (EOPNOTSUPP);
+        }
+       return (0);
+}
+
+DEV_MODULE(cpuctl, cpuctl_modevent, NULL);
+MODULE_VERSION(cpuctl, CPUCTL_VERSION);
index 4504f4a..a751c47 100644 (file)
@@ -787,6 +787,28 @@ ENTRY(rdmsr_safe)
 
        ret
 
+/*
+ * Support for writing MSRs in the safe manner.
+ */
+ENTRY(wrmsr_safe)
+/* int wrmsr_safe(u_int msr, uint64_t data) */
+       movl    PCPU(curthread),%ecx
+       movl    TD_PCB(%ecx), %ecx
+       movl    $msr_onfault,PCB_ONFAULT(%ecx)
+       movl    %esp,PCB_ONFAULT_SP(%ecx)
+
+       movl    4(%esp),%ecx
+       movl    8(%esp),%eax
+       movl    12(%esp),%edx
+       wrmsr
+       xorl    %eax,%eax
+
+       movl    PCPU(CURPCB),%ecx
+       movl    %eax,PCB_ONFAULT(%ecx)
+
+       ret
+
+
 /*
  * MSR operations fault handler
  */
index db1c203..6ce2852 100644 (file)
@@ -731,6 +731,25 @@ ENTRY(rdmsr_safe)
        movq    %rax,PCB_ONFAULT(%r8)
        ret
 
+/*
+ * Support for writing MSRs in the safe manner.
+ */
+ENTRY(wrmsr_safe)
+/* int wrmsr_safe(u_int msr, uint64_t data) */
+       movq    PCPU(curthread),%r8
+       movq    TD_PCB(%r8), %r8
+       movq    $msr_onfault,PCB_ONFAULT(%r8)
+       movq    %rsp,PCB_ONFAULT_SP(%rcx)
+       movl    %edi,%ecx
+       movl    %esi,%eax
+       sarq    $32,%rsi
+       movl    %esi,%edx
+       wrmsr                   /* Write MSR pointed by %ecx. Accepts
+                                  hi byte in edx, lo in %eax. */
+       xorq    %rax,%rax
+       movq    %rax,PCB_ONFAULT(%r8)
+       ret
+
 /*
  * MSR operations fault handler
  */
diff --git a/sys/sys/cpuctl.h b/sys/sys/cpuctl.h
new file mode 100644 (file)
index 0000000..19f4176
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 2006-2008 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD: release/10.0.0/sys/sys/cpuctl.h 195189 2009-06-30 12:35:47Z stas $
+ */
+
+#ifndef _CPUCTL_H_
+#define        _CPUCTL_H_
+
+typedef struct {
+       int             msr;    /* MSR to read */
+       uint64_t        data;
+} cpuctl_msr_args_t;
+
+typedef struct {
+       int             level;  /* CPUID level */
+       uint32_t        data[4];
+} cpuctl_cpuid_args_t;
+
+typedef struct {
+       void    *data;
+       size_t  size;
+} cpuctl_update_args_t;
+
+#define        CPUCTL_RDMSR    _IOWR('c', 1, cpuctl_msr_args_t)
+#define        CPUCTL_WRMSR    _IOWR('c', 2, cpuctl_msr_args_t)
+#define        CPUCTL_CPUID    _IOWR('c', 3, cpuctl_cpuid_args_t)
+#define        CPUCTL_UPDATE   _IOWR('c', 4, cpuctl_update_args_t)
+#define        CPUCTL_MSRSBIT  _IOWR('c', 5, cpuctl_msr_args_t)
+#define        CPUCTL_MSRCBIT  _IOWR('c', 6, cpuctl_msr_args_t)
+
+#endif /* _CPUCTL_H_ */
index 6407508..b48663f 100644 (file)
@@ -24,6 +24,7 @@ SUBDIR= 802_11 \
        ckdist \
        clog \
        config \
+       cpucontrol \
        crashinfo \
        cron \
        daemon \
diff --git a/usr.sbin/cpucontrol/Makefile b/usr.sbin/cpucontrol/Makefile
new file mode 100644 (file)
index 0000000..5643ba7
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/Makefile 242159 2012-10-26 20:25:05Z eadler $
+
+PROG=  cpucontrol
+MAN=   cpucontrol.8
+SRCS=  cpucontrol.c intel.c amd.c via.c
+
+NO_WCAST_ALIGN=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/cpucontrol/amd.c b/usr.sbin/cpucontrol/amd.c
new file mode 100644 (file)
index 0000000..2696e6d
--- /dev/null
@@ -0,0 +1,181 @@
+/*-
+ * Copyright (c) 2006, 2008 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: release/10.0.0/usr.sbin/cpucontrol/amd.c 236504 2012-06-03 08:30:00Z avg $");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "amd.h"
+
+int
+amd_probe(int fd)
+{
+       char vendor[13];
+       int error;
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 0,
+       };
+
+       error = ioctl(fd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl()");
+               return (1);
+       }
+       ((uint32_t *)vendor)[0] = idargs.data[1];
+       ((uint32_t *)vendor)[1] = idargs.data[3];
+       ((uint32_t *)vendor)[2] = idargs.data[2];
+       vendor[12] = '\0';
+       if (strncmp(vendor, AMD_VENDOR_ID, sizeof(AMD_VENDOR_ID)) != 0)
+               return (1);
+       return (0);
+}
+
+void
+amd_update(const char *dev, const char *path)
+{
+       int fd, devfd;
+       unsigned int i;
+       struct stat st;
+       uint32_t *fw_image;
+       amd_fw_header_t *fw_header;
+       uint32_t sum;
+       uint32_t signature;
+       uint32_t *fw_data;
+       size_t fw_size;
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 1,    /* Request signature. */
+       };
+       cpuctl_update_args_t args;
+       int error;
+
+       assert(path);
+       assert(dev);
+
+       fd  = -1;
+       fw_image = MAP_FAILED;
+       devfd = open(dev, O_RDWR);
+       if (devfd < 0) {
+               WARN(0, "could not open %s for writing", dev);
+               return;
+       }
+       error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl()");
+               goto fail;
+       }
+       signature = idargs.data[0];
+       WARNX(2, "found cpu family %#x model %#x "
+           "stepping %#x extfamily %#x extmodel %#x.",
+           (signature >> 8) & 0x0f, (signature >> 4) & 0x0f,
+           (signature >> 0) & 0x0f, (signature >> 20) & 0xff,
+           (signature >> 16) & 0x0f);
+
+       /*
+        * Open the firmware file.
+        */
+       fd = open(path, O_RDONLY, 0);
+       if (fd < 0) {
+               WARN(0, "open(%s)", path);
+               goto fail;
+       }
+       error = fstat(fd, &st);
+       if (error != 0) {
+               WARN(0, "fstat(%s)", path);
+               goto fail;
+       }
+       if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+               WARNX(2, "file too short: %s", path);
+               goto fail;
+       }
+       /*
+        * mmap the whole image.
+        */
+       fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+           MAP_PRIVATE, fd, 0);
+       if  (fw_image == MAP_FAILED) {
+               WARN(0, "mmap(%s)", path);
+               goto fail;
+       }
+       fw_header = (amd_fw_header_t *)fw_image;
+       if ((fw_header->magic >> 8) != AMD_MAGIC) {
+               WARNX(2, "%s is not a valid amd firmware: version mismatch",
+                   path);
+               goto fail;
+       }
+       fw_data = (uint32_t *)(fw_header + 1);
+       fw_size = (st.st_size - sizeof(*fw_header)) / sizeof(uint32_t);
+
+       /*
+        * Check the primary checksum.
+        */
+       sum = 0;
+       for (i = 0; i < fw_size; i++)
+               sum += fw_data[i];
+       if (sum != fw_header->checksum) {
+               WARNX(2, "%s: update data checksum invalid", path);
+               goto fail;
+       }
+       if (signature == fw_header->signature) {
+               fprintf(stderr, "%s: updating cpu %s... ", path, dev);
+
+               args.data = fw_image;
+               args.size = st.st_size;
+               error = ioctl(devfd, CPUCTL_UPDATE, &args);
+               if (error < 0) {
+                       fprintf(stderr, "failed.\n");
+                       warn("ioctl()");
+                       goto fail;
+               }
+               fprintf(stderr, "done.\n");
+       }
+
+fail:
+       if (fd >= 0)
+               close(fd);
+       if (devfd >= 0)
+               close(devfd);
+       if (fw_image != MAP_FAILED)
+               if(munmap(fw_image, st.st_size) != 0)
+                       warn("munmap(%s)", path);
+       return;
+}
diff --git a/usr.sbin/cpucontrol/amd.h b/usr.sbin/cpucontrol/amd.h
new file mode 100644 (file)
index 0000000..3187eb8
--- /dev/null
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2006, 2008 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.
+ *
+ * $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/amd.h 181430 2008-08-08 16:26:53Z stas $
+ */
+
+#ifndef AMD_H
+#define        AMD_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t  amd_probe;
+ucode_update_t amd_update;
+
+typedef struct amd_fw_header {
+       uint32_t        date;           /* Update creation date. */
+       uint32_t        xz0[2];
+       uint32_t        checksum;       /* ucode checksum. */
+       uint32_t        xz1[2];
+       uint32_t        signature;      /* Low byte of cpuid(0). */
+       uint32_t        magic;          /* 0x0Xaaaaaa */
+       uint32_t        xz2[8];
+} amd_fw_header_t;
+
+#define        AMD_MAGIC       0xaaaaaa
+
+#endif /* !AMD_H */
diff --git a/usr.sbin/cpucontrol/cpucontrol.8 b/usr.sbin/cpucontrol/cpucontrol.8
new file mode 100644 (file)
index 0000000..c1e03e1
--- /dev/null
@@ -0,0 +1,173 @@
+.\" Copyright (c) 2006, 2008 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.\" $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/cpucontrol.8 235873 2012-05-24 02:24:03Z wblock $
+.\"
+.Dd June 30, 2009
+.Dt CPUCONTROL 8
+.Os
+.Sh NAME
+.Nm cpucontrol
+.Nd control utility for the
+.Xr cpuctl 4
+device
+.Sh SYNOPSIS
+.Nm
+.Op Fl vh
+.Fl m Ar msr
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns = Ns Ar value
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns &= Ns Ar mask
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl m Ar msr Ns |= Ns Ar mask
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Fl i Ar level
+.Bk
+.Ar device
+.Ek
+.Nm
+.Op Fl vh
+.Op Fl d Ar datadir
+.Fl u
+.Bk
+.Ar device
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to read and write arbitrary machine-specific
+CPU registers via the
+.Xr cpuctl 4
+special device.
+It can also be used to apply CPU firmware updates.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar datadir
+Where to look for microcode images.
+The option can be specified multiple times.
+.It Fl m Ar msr Ns Op = Ns Ar value
+Show value of the specified MSR.
+MSR register number should be given as a hexadecimal number.
+.It Fl m Ar msr Ns = Ns Ar value
+Store the
+.Ar value
+in the specified MSR register.
+The
+.Ar value
+argument can be prefixed with ~ operator.
+In this case the inverted value of argument will be stored in the register.
+.It Fl m Ar msr Ns &= Ns Ar mask
+Store the result of bitwise AND operation between
+.Ar mask
+and the current MSR value in the MSR register.
+The
+.Ar mask
+argument can be prefixed with ~ operator.
+In this case the inverted value of mask will be used.
+.It Fl m Ar msr Ns |= Ns Ar mask
+Store the result of bitwise OR operation between
+.Ar mask
+and the current MSR value in the MSR register.
+The
+.Ar mask
+argument can be prefixed with ~ operator.
+In this case the inverted value of mask will be used.
+.It Fl i Ar level
+Retrieve CPUID info.
+Level should be given as a hex number.
+.It Fl u
+Apply CPU firmware updates.
+The
+.Nm
+utility will walk through the configured data directories
+and apply all firmware updates available for this CPU.
+.It Fl v
+Increase the verbosity level.
+.It Fl h
+Show help message.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+The command
+.Pp
+.Dq Li "cpucontrol -m 0x10 /dev/cpuctl0"
+.Pp
+will read the contents of TSC MSR from CPU 0.
+.Pp
+To set the CPU 0 TSC MSR register value to 0x1 issue
+.Pp
+.Dq Li "cpucontrol -m 0x10=0x1 /dev/cpuctl0" .
+.Pp
+The following command will clear the second bit of TSC register:
+.Pp
+.Dq Li "cpucontrol -m 0x10&=~0x02 /dev/cpuctl0" .
+.Pp
+The following command will set the forth and second bit of TSC register:
+.Pp
+.Dq Li "cpucontrol -m 0x10|=0x0a /dev/cpuctl0" .
+.Pp
+The command
+.Pp
+.Dq Li "cpucontrol -i 0x1 /dev/cpuctl1"
+.Pp
+will retrieve the CPUID level 0x1 from CPU 1.
+.Pp
+To perform firmware updates on CPU 0 from images located at
+.Pa /usr/local/share/cpuctl/
+use the following command:
+.Pp
+.Dq Li "cpucontrol -d /usr/local/share/cpuctl/ -u /dev/cpuctl0"
+.Sh SEE ALSO
+.Xr cpuctl 4
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 7.2 .
+.Sh AUTHORS
+The
+.Nm
+utility and this manual page was written by
+.An Stanislav Sedov Aq stas@FreeBSD.org .
+.Sh BUGS
+Yes, probably, report if any.
diff --git a/usr.sbin/cpucontrol/cpucontrol.c b/usr.sbin/cpucontrol/cpucontrol.c
new file mode 100644 (file)
index 0000000..2df3e64
--- /dev/null
@@ -0,0 +1,430 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: release/10.0.0/usr.sbin/cpucontrol/cpucontrol.c 241737 2012-10-19 14:49:42Z ed $");
+
+#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        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)
+
+/*
+ * Macros for freeing SLISTs, probably must be in /sys/queue.h
+ */
+#define        SLIST_FREE(head, field, freef) do {                             \
+               typeof(SLIST_FIRST(head)) __elm0;                       \
+               typeof(SLIST_FIRST(head)) __elm;                        \
+               SLIST_FOREACH_MUTABLE(__elm, (head), field, __elm0)     \
+                       (void)(freef)(__elm);                           \
+} while(0);
+
+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 },
+       { amd_probe, amd_update },
+       { via_probe, via_update },
+};
+#define NHANDLERS (sizeof(handlers) / sizeof(*handlers))
+
+static void    usage(void);
+static int     isdir(const char *path);
+static int     do_cpuid(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 | -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();
+               /* 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_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 (%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_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);
+       }
+
+       /*
+        * Find the appropriate handler for device.
+        */
+       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);
+
+       /*
+        * Process every image in specified data directories.
+        */
+       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);
+}
+
+/*
+ * 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[])
+{
+       int c, flags;
+       const char *cmdarg;
+       const char *dev;
+       int error;
+
+       flags = 0;
+       error = 0;
+       cmdarg = "";    /* To keep gcc3 happy. */
+
+       /*
+        * Add all default data dirs to the list first.
+        */
+       datadir_add(DEFAULT_DATADIR);
+       while ((c = getopt(argc, argv, "d:hi:m:uv")) != -1) {
+               switch (c) {
+               case 'd':
+                       datadir_add(optarg);
+                       break;
+               case 'i':
+                       flags |= FLAG_I;
+                       cmdarg = optarg;
+                       break;
+               case 'm':
+                       flags |= FLAG_M;
+                       cmdarg = optarg;
+                       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 */
+       }
+       dev = argv[0];
+       c = flags & (FLAG_I | FLAG_M | FLAG_U);
+       switch (c) {
+               case FLAG_I:
+                       error = do_cpuid(cmdarg, dev);
+                       break;
+               case FLAG_M:
+                       error = do_msr(cmdarg, dev);
+                       break;
+               case FLAG_U:
+                       error = do_update(dev);
+                       break;
+               default:
+                       usage();        /* Only one command can be selected. */
+       }
+       SLIST_FREE(&datadirs, next, free);
+       return (error);
+}
diff --git a/usr.sbin/cpucontrol/cpucontrol.h b/usr.sbin/cpucontrol/cpucontrol.h
new file mode 100644 (file)
index 0000000..2a4f848
--- /dev/null
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2008 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.
+ *
+ * $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/cpucontrol.h 181430 2008-08-08 16:26:53Z stas $
+ */
+
+#ifndef CPUCONTROL_H
+#define        CPUCONTROL_H
+
+typedef int ucode_probe_t(int fd);
+typedef void ucode_update_t(const char *dev, const char *image);
+
+extern int verbosity_level;
+
+#ifdef DEBUG
+# define WARNX(level, ...)                                     \
+       if ((level) <= verbosity_level) {                       \
+               fprintf(stderr, "%s:%d ", __FILE__, __LINE__);  \
+               warnx(__VA_ARGS__);                             \
+       }
+# define WARN(level, ...)                                      \
+       if ((level) <= verbosity_level) {                       \
+               fprintf(stderr, "%s:%d ", __FILE__, __LINE__);  \
+               warn(__VA_ARGS__);                              \
+       }
+#else
+# define WARNX(level, ...)                                     \
+       if ((level) <= verbosity_level)                         \
+               warnx(__VA_ARGS__);
+# define WARN(level, ...)                                      \
+       if ((level) <= verbosity_level)                         \
+               warn(__VA_ARGS__);
+#endif
+
+#endif /* !CPUCONTROL_H */
diff --git a/usr.sbin/cpucontrol/intel.c b/usr.sbin/cpucontrol/intel.c
new file mode 100644 (file)
index 0000000..9c479d1
--- /dev/null
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 2006, 2008 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: release/10.0.0/usr.sbin/cpucontrol/intel.c 245491 2013-01-16 05:00:51Z eadler $");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "intel.h"
+
+#define        DEFAULT_UCODE_SIZE      2000 /* Size of update data if not specified. */
+
+int
+intel_probe(int fd)
+{
+       char vendor[13];
+       int error;
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 0,
+       };
+
+       error = ioctl(fd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl()");
+               return (1);
+       }
+       ((uint32_t *)vendor)[0] = idargs.data[1];
+       ((uint32_t *)vendor)[1] = idargs.data[3];
+       ((uint32_t *)vendor)[2] = idargs.data[2];
+       vendor[12] = '\0';
+       if (strncmp(vendor, INTEL_VENDOR_ID, sizeof(INTEL_VENDOR_ID)) != 0)
+               return (1);
+       return (0);
+}
+
+void
+intel_update(const char *dev, const char *path)
+{
+       int fd, devfd;
+       struct stat st;
+       uint32_t *fw_image;
+       int have_ext_table;
+       uint32_t sum;
+       unsigned int i;
+       size_t payload_size;
+       intel_fw_header_t *fw_header;
+       intel_cpu_signature_t *ext_table;
+       intel_ext_header_t *ext_header;
+       uint32_t signature, flags;
+       int32_t revision;
+       ssize_t ext_size;
+       size_t ext_table_size;
+       void *fw_data;
+       size_t data_size, total_size;
+       cpuctl_msr_args_t msrargs = {
+               .msr = MSR_IA32_PLATFORM_ID,
+       };
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 1,    /* Signature. */
+       };
+       cpuctl_update_args_t args;
+       int error;
+
+       assert(path);
+       assert(dev);
+
+       fd = -1;
+       fw_image = MAP_FAILED;
+       ext_table = NULL;
+       ext_header = NULL;
+       devfd = open(dev, O_RDWR);
+       if (devfd < 0) {
+               WARN(0, "could not open %s for writing", dev);
+               return;
+       }
+       error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+       signature = idargs.data[0];
+       error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+
+       /*
+        * MSR_IA32_PLATFORM_ID contains flag in BCD in bits 52-50.
+        */
+       flags = 1 << ((msrargs.data >> 50) & 7);
+       msrargs.msr = MSR_BIOS_SIGN;
+       error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+       revision = msrargs.data >> 32; /* Revision in the high dword. */
+       WARNX(2, "found cpu type %#x family %#x model %#x stepping %#x.",
+           (signature >> 12) & 0x03, (signature >> 8) & 0x0f,
+           (signature >> 4) & 0x0f, (signature >> 0) & 0x0f);
+       /*
+        * Open firmware image.
+        */
+       fd = open(path, O_RDONLY, 0);
+       if (fd < 0) {
+               WARN(0, "open(%s)", path);
+               return;
+       }
+       error = fstat(fd, &st);
+       if (error != 0) {
+               WARN(0, "fstat(%s)", path);
+               goto fail;
+       }
+       if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+               WARNX(2, "file too short: %s", path);
+               goto fail;
+       }
+
+       /*
+        * mmap the whole image.
+        */
+       fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+           MAP_PRIVATE, fd, 0);
+       if  (fw_image == MAP_FAILED) {
+               WARN(0, "mmap(%s)", path);
+               goto fail;
+       }
+       fw_header = (intel_fw_header_t *)fw_image;
+       if (fw_header->header_version != INTEL_HEADER_VERSION ||
+           fw_header->loader_revision != INTEL_LOADER_REVISION) {
+               WARNX(2, "%s is not a valid intel firmware: version mismatch",
+                   path);
+               goto fail;
+       }
+       /*
+        * According to spec, if data_size == 0, then size of ucode = 2000.
+        */
+       if (fw_header->data_size == 0)
+               data_size = DEFAULT_UCODE_SIZE;
+       else
+               data_size = fw_header->data_size;
+       if (fw_header->total_size == 0)
+               total_size = data_size + sizeof(*fw_header);
+       else
+               total_size = fw_header->total_size;
+       if (total_size > (unsigned)st.st_size || st.st_size < 0) {
+               WARNX(2, "file too short: %s", path);
+               goto fail;
+       }
+       payload_size = data_size + sizeof(*fw_header);
+
+       /*
+        * Check the primary checksum.
+        */
+       sum = 0;
+       for (i = 0; i < (payload_size / sizeof(uint32_t)); i++)
+               sum += *((uint32_t *)fw_image + i);
+       if (sum != 0) {
+               WARNX(2, "%s: update data checksum invalid", path);
+               goto fail;
+       }
+
+       /*
+        * Check if there is an extended signature table.
+        */
+       ext_size = total_size - payload_size;
+       have_ext_table = 0;
+
+       if (ext_size > (signed)sizeof(*ext_header)) {
+               ext_header =
+                   (intel_ext_header_t *)((char *)fw_image + payload_size);
+               ext_table = (intel_cpu_signature_t *)(ext_header + 1);
+
+               /*
+                * Check the extended table size.
+                */
+               ext_table_size = sizeof(*ext_header) +
+                   ext_header->sig_count * sizeof(*ext_table);
+               if (ext_table_size + payload_size > total_size) {
+                       WARNX(2, "%s: broken extended signature table", path);
+                       goto no_table;
+               }
+
+               /*
+                * Check the extended table signature.
+                */
+               sum = 0;
+               for (i = 0; i < (ext_table_size / sizeof(uint32_t)); i++)
+                       sum += *((uint32_t *)ext_header + i);
+               if (sum != 0) {
+                       WARNX(2, "%s: extended signature table checksum invalid",
+                           path);
+                       goto no_table;
+               }
+               have_ext_table = 1;
+       }
+
+no_table:
+       fw_data = fw_header + 1; /* Pointer to the update data. */
+
+       /*
+        * Check if the given image is ok for this cpu.
+        */
+       if (signature == fw_header->cpu_signature &&
+           (flags & fw_header->cpu_flags) != 0)
+                       goto matched;
+       else if (have_ext_table != 0) {
+               for (i = 0; i < ext_header->sig_count; i++) {
+                       uint32_t sig = ext_table[i].cpu_signature;
+                       if (signature == sig &&
+                           (flags & ext_table[i].cpu_flags) != 0)
+                               goto matched;
+               }
+       } else
+               goto fail;
+
+matched:
+       if (revision >= fw_header->revision) {
+               WARNX(1, "skipping %s of rev %#x: up to date",
+                   path, fw_header->revision);
+               return;
+       }
+       fprintf(stderr, "%s: updating cpu %s from rev %#x to rev %#x... ",
+                       path, dev, revision, fw_header->revision);
+       args.data = fw_data;
+       args.size = data_size;
+       error = ioctl(devfd, CPUCTL_UPDATE, &args);
+       if (error < 0) {
+               error = errno;
+               fprintf(stderr, "failed.\n");
+               errno = error;
+               WARN(0, "ioctl()");
+               goto fail;
+       }
+       fprintf(stderr, "done.\n");
+
+fail:
+       if (fw_image != MAP_FAILED)
+               if (munmap(fw_image, st.st_size) != 0)
+                       warn("munmap(%s)", path);
+       if (devfd >= 0)
+               close(devfd);
+       if (fd >= 0)
+               close(fd);
+       return;
+}
diff --git a/usr.sbin/cpucontrol/intel.h b/usr.sbin/cpucontrol/intel.h
new file mode 100644 (file)
index 0000000..c7215be
--- /dev/null
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2006, 2008 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.
+ *
+ * $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/intel.h 181430 2008-08-08 16:26:53Z stas $
+ */
+
+#ifndef INTEL_H
+#define        INTEL_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t  intel_probe;
+ucode_update_t intel_update;
+
+typedef struct intel_fw_header {
+       uint32_t        header_version;         /* Version of the header. */
+       int32_t         revision;               /* Unique version number. */
+       uint32_t        date;                   /* Date of creation in BCD. */
+       uint32_t        cpu_signature;          /* Extended family, extended
+                                                  model, type, family, model
+                                                  and stepping. */
+       uint32_t        checksum;               /* Sum of all DWORDS should
+                                                  be 0. */
+       uint32_t        loader_revision;        /* Version of the loader
+                                                  required to load update. */
+       uint32_t        cpu_flags;              /* Platform IDs encoded in
+                                                  the lower 8 bits. */
+       uint32_t        data_size;
+       uint32_t        total_size;
+       uint8_t         reserved[12];
+} intel_fw_header_t;
+
+typedef struct intel_cpu_signature {
+       uint32_t        cpu_signature;
+       uint32_t        cpu_flags;
+       uint32_t        checksum;
+} intel_cpu_signature_t;
+
+typedef struct intel_ext_header {
+       uint32_t        sig_count;
+       uint32_t        checksum;
+       uint8_t         reserved[12];
+} intel_ext_header_t;
+
+#define        INTEL_HEADER_VERSION    0x00000001
+#define        INTEL_LOADER_REVISION   0x00000001
+
+#endif /* !INTEL_H */
diff --git a/usr.sbin/cpucontrol/via.c b/usr.sbin/cpucontrol/via.c
new file mode 100644 (file)
index 0000000..003265c
--- /dev/null
@@ -0,0 +1,224 @@
+/*-
+ * Copyright (c) 2011 Fabien Thomas <fabient@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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: release/10.0.0/usr.sbin/cpucontrol/via.c 245491 2013-01-16 05:00:51Z eadler $");
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/ioccom.h>
+#include <sys/cpuctl.h>
+
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+#include "cpucontrol.h"
+#include "via.h"
+
+int
+via_probe(int fd)
+{
+       char vendor[13];
+       int error;
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 0,
+       };
+
+       error = ioctl(fd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl()");
+               return (1);
+       }
+       ((uint32_t *)vendor)[0] = idargs.data[1];
+       ((uint32_t *)vendor)[1] = idargs.data[3];
+       ((uint32_t *)vendor)[2] = idargs.data[2];
+       vendor[12] = '\0';
+       if (strncmp(vendor, CENTAUR_VENDOR_ID, sizeof(CENTAUR_VENDOR_ID)) != 0)
+               return (1);
+
+       /* TODO: detect Nano CPU. */
+       return (0);
+}
+
+void
+via_update(const char *dev, const char *path)
+{
+       int fd, devfd;
+       struct stat st;
+       uint32_t *fw_image;
+       uint32_t sum;
+       unsigned int i;
+       size_t payload_size;
+       via_fw_header_t *fw_header;
+       uint32_t signature;
+       int32_t revision;
+       void *fw_data;
+       size_t data_size, total_size;
+       cpuctl_msr_args_t msrargs = {
+               .msr = MSR_IA32_PLATFORM_ID,
+       };
+       cpuctl_cpuid_args_t idargs = {
+               .level  = 1,    /* Signature. */
+       };
+       cpuctl_update_args_t args;
+       int error;
+
+       assert(path);
+       assert(dev);
+
+       fd = -1;
+       devfd = -1;
+       fw_image = MAP_FAILED;
+       devfd = open(dev, O_RDWR);
+       if (devfd < 0) {
+               WARN(0, "could not open %s for writing", dev);
+               return;
+       }
+       error = ioctl(devfd, CPUCTL_CPUID, &idargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+       signature = idargs.data[0];
+       error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+
+       /*
+        * MSR_IA32_PLATFORM_ID contains flag in BCD in bits 52-50.
+        */
+       msrargs.msr = MSR_BIOS_SIGN;
+       error = ioctl(devfd, CPUCTL_RDMSR, &msrargs);
+       if (error < 0) {
+               WARN(0, "ioctl(%s)", dev);
+               goto fail;
+       }
+       revision = msrargs.data >> 32; /* Revision in the high dword. */
+       WARNX(2, "found cpu type %#x family %#x model %#x stepping %#x.",
+           (signature >> 12) & 0x03, (signature >> 8) & 0x0f,
+           (signature >> 4) & 0x0f, (signature >> 0) & 0x0f);
+       /*
+        * Open firmware image.
+        */
+       fd = open(path, O_RDONLY, 0);
+       if (fd < 0) {
+               WARN(0, "open(%s)", path);
+               return;
+       }
+       error = fstat(fd, &st);
+       if (error != 0) {
+               WARN(0, "fstat(%s)", path);
+               goto fail;
+       }
+       if (st.st_size < 0 || (unsigned)st.st_size < sizeof(*fw_header)) {
+               WARNX(2, "file too short: %s", path);
+               goto fail;
+       }
+
+       /*
+        * mmap the whole image.
+        */
+       fw_image = (uint32_t *)mmap(NULL, st.st_size, PROT_READ,
+           MAP_PRIVATE, fd, 0);
+       if  (fw_image == MAP_FAILED) {
+               WARN(0, "mmap(%s)", path);
+               goto fail;
+       }
+       fw_header = (via_fw_header_t *)fw_image;
+       if (fw_header->signature != VIA_HEADER_SIGNATURE ||
+           fw_header->loader_revision != VIA_LOADER_REVISION) {
+               WARNX(2, "%s is not a valid via firmware: version mismatch",
+                   path);
+               goto fail;
+       }
+       data_size = fw_header->data_size;
+       total_size = fw_header->total_size;
+       if (total_size > (unsigned)st.st_size || st.st_size < 0) {
+               WARNX(2, "file too short: %s", path);
+               goto fail;
+       }
+       payload_size = data_size + sizeof(*fw_header);
+
+       /*
+        * Check the primary checksum.
+        */
+       sum = 0;
+       for (i = 0; i < (payload_size / sizeof(uint32_t)); i++)
+               sum += *((uint32_t *)fw_image + i);
+       if (sum != 0) {
+               WARNX(2, "%s: update data checksum invalid", path);
+               goto fail;
+       }
+
+       fw_data = fw_header + 1; /* Pointer to the update data. */
+
+       /*
+        * Check if the given image is ok for this cpu.
+        */
+       if (signature != fw_header->cpu_signature)
+               goto fail;
+
+       if (fw_header->revision != 0 && revision >= fw_header->revision) {
+               WARNX(1, "skipping %s of rev %#x: up to date",
+                   path, fw_header->revision);
+               goto fail;
+       }
+       fprintf(stderr, "%s: updating cpu %s from rev %#x to rev %#x... ",
+                       path, dev, revision, fw_header->revision);
+       args.data = fw_data;
+       args.size = data_size;
+       error = ioctl(devfd, CPUCTL_UPDATE, &args);
+       if (error < 0) {
+               error = errno;
+               fprintf(stderr, "failed.\n");
+               errno = error;
+               WARN(0, "ioctl()");
+               goto fail;
+       }
+       fprintf(stderr, "done.\n");
+
+fail:
+       if (fw_image != MAP_FAILED)
+               if (munmap(fw_image, st.st_size) != 0)
+                       warn("munmap(%s)", path);
+       if (devfd >= 0)
+               close(devfd);
+       if (fd >= 0)
+               close(fd);
+       return;
+}
diff --git a/usr.sbin/cpucontrol/via.h b/usr.sbin/cpucontrol/via.h
new file mode 100644 (file)
index 0000000..c8ca740
--- /dev/null
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2011 Fabien Thomas <fabient@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.
+ *
+ * $FreeBSD: release/10.0.0/usr.sbin/cpucontrol/via.h 228436 2011-12-12 12:30:44Z fabient $
+ */
+
+#ifndef VIA_H
+#define        VIA_H
+
+/*
+ * Prototypes.
+ */
+ucode_probe_t  via_probe;
+ucode_update_t via_update;
+
+typedef struct via_fw_header {
+       uint32_t        signature;              /* Signature. */
+       int32_t         revision;               /* Unique version number. */
+       uint32_t        date;                   /* Date of creation in BCD. */
+       uint32_t        cpu_signature;          /* Extended family, extended
+                                                  model, type, family, model
+                                                  and stepping. */
+       uint32_t        checksum;               /* Sum of all DWORDS should
+                                                  be 0. */
+       uint32_t        loader_revision;        /* Version of the loader
+                                                  required to load update. */
+       uint32_t        reserverd1;             /* Platform IDs encoded in
+                                                  the lower 8 bits. */
+       uint32_t        data_size;
+       uint32_t        total_size;
+       uint8_t         reserved2[12];
+} via_fw_header_t;
+
+typedef struct via_cpu_signature {
+       uint32_t        cpu_signature;
+       uint32_t        checksum;
+} via_cpu_signature_t;
+
+#define VIA_HEADER_SIGNATURE   0x53415252
+#define VIA_LOADER_REVISION    0x00000001
+
+#endif /* !VIA_H */