Update the Intel microcode once the root filesystem has been mounted.
authorpatrick <patrick@openbsd.org>
Thu, 11 Jan 2018 22:31:09 +0000 (22:31 +0000)
committerpatrick <patrick@openbsd.org>
Thu, 11 Jan 2018 22:31:09 +0000 (22:31 +0000)
This depends on the intel-firmware package that contains newer Intel
microcode which will be installed automatically by fw_update(1).

The update should happen much earlier since updating the microcode can
add or remove not only feature flags but also whole features.  For now
only update feature flags that are relevant to Spectre.

Initial diff from sf@
Tested by bluhm@
ok deraadt@

sys/arch/amd64/amd64/acpi_machdep.c
sys/arch/amd64/amd64/cpu.c
sys/arch/amd64/amd64/ucode.c [new file with mode: 0644]
sys/arch/amd64/conf/files.amd64
sys/arch/amd64/include/cpufunc.h
sys/arch/amd64/include/specialreg.h

index 5bd9464..268bab1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: acpi_machdep.c,v 1.79 2017/10/14 04:44:43 jsg Exp $   */
+/*     $OpenBSD: acpi_machdep.c,v 1.80 2018/01/11 22:31:09 patrick Exp $       */
 /*
  * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
  *
@@ -429,6 +429,7 @@ acpi_resume_cpu(struct acpi_softc *sc)
        fpuinit(&cpu_info_primary);
 
        cpu_init(&cpu_info_primary);
+       cpu_ucode_apply(&cpu_info_primary);
 
        /* Re-initialise memory range handling on BSP */
        if (mem_range_softc.mr_op != NULL)
index 7afed65..6454ecd 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.c,v 1.108 2017/10/14 04:44:43 jsg Exp $   */
+/*     $OpenBSD: cpu.c,v 1.109 2018/01/11 22:31:09 patrick Exp $       */
 /* $NetBSD: cpu.c,v 1.1 2003/04/26 18:39:26 fvdl Exp $ */
 
 /*-
@@ -412,6 +412,7 @@ cpu_attach(struct device *parent, struct device *self, void *aux)
 #endif /* MTRR */
                cpu_init(ci);
                cpu_init_mwait(sc);
+               config_mountroot(NULL, cpu_ucode_attachhook);
                break;
 
        case CPU_ROLE_BP:
@@ -435,6 +436,7 @@ cpu_attach(struct device *parent, struct device *self, void *aux)
                ioapic_bsp_id = caa->cpu_apicid;
 #endif
                cpu_init_mwait(sc);
+               config_mountroot(NULL, cpu_ucode_attachhook);
                break;
 
        case CPU_ROLE_AP:
@@ -732,6 +734,8 @@ cpu_hatch(void *v)
        lldt(0);
 
        cpu_init(ci);
+       cpu_ucode_apply(ci);
+       cpu_flags_update(ci);
 #if NPVBUS > 0
        pvbus_init_cpu();
 #endif
@@ -931,3 +935,20 @@ cpu_activate(struct device *self, int act)
 
        return (0);
 }
+
+void
+cpu_flags_update(struct cpu_info *ci)
+{
+       uint32_t dummy;
+
+       /* XXX this update is much later than we want it to be */
+       if (cpuid_level >= 0x07) {
+               CPUID_LEAF(0x7, 0, dummy, dummy, dummy,
+                   ci->ci_feature_sefflags_edx);
+       }
+       if (!strcmp(cpu_vendor, "AuthenticAMD") &&
+           ci->ci_pnfeatset >= 0x80000008) {
+               CPUID(0x80000008, dummy, ci->ci_feature_amdspec_ebx,
+                   dummy, dummy);
+       }
+}
diff --git a/sys/arch/amd64/amd64/ucode.c b/sys/arch/amd64/amd64/ucode.c
new file mode 100644 (file)
index 0000000..a52e3b4
--- /dev/null
@@ -0,0 +1,309 @@
+/*     $OpenBSD: ucode.c,v 1.1 2018/01/11 22:31:09 patrick Exp $       */
+/*
+ * Copyright (c) 2018 Stefan Fritsch <fritsch@genua.de>
+ * Copyright (c) 2018 Patrick Wildt <patrick@blueri.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/mutex.h>
+
+#include <machine/cpu.h>
+#include <machine/cpufunc.h>
+#include <machine/specialreg.h>
+
+/* #define UCODE_DEBUG */
+#ifdef UCODE_DEBUG
+#define DPRINTF(x)     do { if (cpu_ucode_debug > 0) printf x; } while (0)
+#define DPRINTFN(n, x) do { if (cpu_ucode_debug >= (n)) printf x; } while (0)
+static int cpu_ucode_debug = 1;
+#else
+#define DPRINTF(x)     do { ; } while (0)
+#define DPRINTFN(n, x) do { ; } while (0)
+#endif
+
+struct intel_ucode_header {
+       uint32_t        header_version;
+       uint32_t        update_revision;
+       uint32_t        date;
+       uint32_t        processor_sig;
+       uint32_t        checksum;
+       uint32_t        loader_rev;
+       uint32_t        processor_flags;
+       uint32_t        data_size;
+       uint32_t        total_size;
+       uint32_t        reserved[3];
+};
+
+struct intel_ucode_ext_sig_header {
+       uint32_t        ext_sig_count;
+       uint32_t        checksum;
+       uint32_t        reserved[3];
+};
+
+struct intel_ucode_ext_sig {
+       uint32_t        processor_sig;
+       uint32_t        processor_flags;
+       uint32_t        checksum;
+};
+
+#define INTEL_UCODE_DEFAULT_DATA_SIZE          2000
+
+/* Generic */
+char *  cpu_ucode_data;
+size_t  cpu_ucode_size;
+
+void    cpu_ucode_setup(struct cpu_info *);
+void    cpu_ucode_apply(struct cpu_info *);
+
+/* Intel */
+void    cpu_ucode_intel_apply(struct cpu_info *);
+struct intel_ucode_header *
+        cpu_ucode_intel_find(char *, size_t, uint32_t);
+int     cpu_ucode_intel_verify(struct intel_ucode_header *);
+int     cpu_ucode_intel_match(struct intel_ucode_header *, uint32_t, uint32_t,
+           uint32_t);
+uint32_t cpu_ucode_intel_rev(void);
+
+struct intel_ucode_header      *cpu_ucode_intel_applied;
+struct mutex                    cpu_ucode_intel_mtx = MUTEX_INITIALIZER(IPL_HIGH);
+
+void
+cpu_ucode_attachhook(struct device *dv)
+{
+       struct cpu_info *ci = curcpu();
+
+       cpu_ucode_setup(ci);
+       cpu_ucode_apply(ci);
+       cpu_flags_update(ci);
+}
+
+void
+cpu_ucode_setup(struct cpu_info *ci)
+{
+       char name[128];
+       u_char *ucode;
+       size_t size;
+
+       snprintf(name, sizeof(name), "intel/%02x-%02x-%02x", ci->ci_family,
+           ci->ci_model, (ci->ci_signature >> 0) & 0x0f);
+
+       if (loadfirmware(name, &ucode, &size) != 0) {
+               DPRINTF(("%s: no microcode found: %s\n", __func__, name));
+               return;
+       }
+
+       cpu_ucode_data = ucode;
+       cpu_ucode_size = size;
+}
+
+/*
+ * Called per-CPU.
+ */
+void
+cpu_ucode_apply(struct cpu_info *ci)
+{
+       if (strcmp(cpu_vendor, "GenuineIntel") == 0)
+               cpu_ucode_intel_apply(ci);
+}
+
+void
+cpu_ucode_intel_apply(struct cpu_info *ci)
+{
+       struct intel_ucode_header *update;
+       uint32_t old_rev, new_rev;
+       paddr_t data;
+
+       if (cpu_ucode_data == NULL || cpu_ucode_size == 0)
+               return;
+
+       /*
+        * Grab a mutex, because we are not allowed to run updates
+        * simultaneously on HT siblings.
+        */
+       mtx_enter(&cpu_ucode_intel_mtx);
+
+       old_rev = cpu_ucode_intel_rev();
+       update = cpu_ucode_intel_applied;
+       if (update == NULL)
+               update = cpu_ucode_intel_find(cpu_ucode_data,
+                   cpu_ucode_size, old_rev);
+       if (update == NULL)
+               goto out;
+       if (update->update_revision == old_rev)
+               goto out;
+
+       /* Apply microcode. */
+       data = (paddr_t)update;
+       data += sizeof(struct intel_ucode_header);
+       wrmsr(MSR_BIOS_UPDT_TRIG, data);
+
+       new_rev = cpu_ucode_intel_rev();
+       if (new_rev != old_rev) {
+               DPRINTF(("%s: microcode updated cpu %d rev %#x->%#x (%x)\n",
+                   __func__, ci->ci_cpuid, old_rev, new_rev, update->date));
+               if (cpu_ucode_intel_applied == NULL)
+                       cpu_ucode_intel_applied = update;
+       } else {
+               DPRINTF(("%s: microcode update failed cpu %d rev %#x->%#x != %#x\n",
+                    __func__, ci->ci_cpuid, old_rev, update->update_revision, new_rev));
+       }
+
+out:
+       mtx_leave(&cpu_ucode_intel_mtx);
+}
+
+struct intel_ucode_header *
+cpu_ucode_intel_find(char *data, size_t left, uint32_t current)
+{
+       uint64_t platform_id = (rdmsr(MSR_PLATFORM_ID) >> 50) & 0xff;
+       uint32_t sig, dummy1, dummy2, dummy3;
+       uint32_t mask = 1UL << platform_id;
+       struct intel_ucode_header *hdr;
+       uint32_t total_size;
+       int n = 0;
+
+       CPUID(1, sig, dummy1, dummy2, dummy3);
+
+       while (left > 0) {
+               hdr = (struct intel_ucode_header *)data;
+               if (left < sizeof(struct intel_ucode_header)) {
+                       DPRINTF(("%s:%d: not enough data for header (%zd)\n",
+                           __func__, n, left));
+                       break;
+               }
+               /*
+                * Older microcode has an empty length.  In that case we
+                * have to use the default length of 2000.
+                */
+               if (hdr->data_size)
+                       total_size = hdr->total_size;
+               else
+                       total_size = INTEL_UCODE_DEFAULT_DATA_SIZE +
+                            sizeof(struct intel_ucode_header);
+               if (total_size > left) {
+                       DPRINTF(("%s:%d: size %u out of range (%zd)\n",
+                           __func__, n, total_size, left));
+                       break;
+               }
+               if (cpu_ucode_intel_verify(hdr)) {
+                       DPRINTF(("%s:%d: broken data\n", __func__, n));
+                       break;
+               }
+               if (cpu_ucode_intel_match(hdr, sig, mask, current))
+                       return hdr;
+               n++;
+               left -= total_size;
+               data += total_size;
+       }
+       DPRINTF(("%s: no update found\n", __func__));
+       return NULL;
+}
+
+int
+cpu_ucode_intel_verify(struct intel_ucode_header *hdr)
+{
+       uint32_t *data = (uint32_t *)hdr;
+       size_t total_size;
+       uint32_t sum;
+       int i;
+
+       CTASSERT(sizeof(struct intel_ucode_header) == 48);
+
+       if ((paddr_t)data % 16 != 0) {
+               DPRINTF(("%s: misaligned microcode update\n", __func__));
+               return 1;
+       }
+       if (hdr->loader_rev != 1) {
+               DPRINTF(("%s: unsupported loader rev\n", __func__));
+               return 1;
+       }
+
+       if (hdr->data_size)
+               total_size = hdr->total_size;
+       else
+               total_size = INTEL_UCODE_DEFAULT_DATA_SIZE +
+                   sizeof(struct intel_ucode_header);
+       if (total_size % 4 != 0) {
+               DPRINTF(("%s: inconsistent size\n", __func__));
+               return 1;
+       }
+
+       sum = 0;
+       for (i = 0; i < total_size / 4; i++)
+               sum += data[i];
+       if (sum != 0) {
+               DPRINTF(("%s: wrong checksum (%#x)\n", __func__, sum));
+               return 1;
+       }
+
+       return 0;
+}
+
+int
+cpu_ucode_intel_match(struct intel_ucode_header *hdr,
+    uint32_t processor_sig, uint32_t processor_mask,
+    uint32_t ucode_revision)
+{
+       struct intel_ucode_ext_sig_header *ehdr;
+       struct intel_ucode_ext_sig *esig;
+       uint32_t data_size, total_size;
+
+       data_size = hdr->data_size;
+       total_size = hdr->total_size;
+
+       /*
+        * Older microcode has an empty length.  In that case we
+        * have to use the default length of 2000.
+        */
+       if (!data_size) {
+               data_size = INTEL_UCODE_DEFAULT_DATA_SIZE;
+               total_size = INTEL_UCODE_DEFAULT_DATA_SIZE +
+                   sizeof(struct intel_ucode_header);
+       }
+
+       if (ucode_revision > hdr->update_revision)
+               return 0;
+       if (hdr->processor_sig == processor_sig &&
+           (hdr->processor_flags & processor_mask))
+               return 1;
+       if (total_size <= sizeof(struct intel_ucode_header) +
+           data_size + sizeof(struct intel_ucode_ext_sig_header))
+               return 0;
+
+       ehdr = (void *)((char *)hdr + sizeof(struct intel_ucode_header) +
+           data_size);
+       esig = (void *)&ehdr[1];
+
+       for (unsigned i = 0; i < ehdr->ext_sig_count; i++) {
+               if (esig->processor_sig == processor_sig &&
+                   (esig->processor_flags & processor_mask))
+                       return 1;
+       }
+
+       return 0;
+}
+
+uint32_t
+cpu_ucode_intel_rev(void)
+{
+       uint32_t eax, ebx, ecx, edx;
+       uint64_t rev;
+
+       wrmsr(MSR_BIOS_SIGN, 0);
+       CPUID(1, eax, ebx, ecx, edx);
+       rev = rdmsr(MSR_BIOS_SIGN);
+       return rev >> 32;
+}
index 5e742fa..68e39ff 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.amd64,v 1.92 2017/12/20 11:08:44 mpi Exp $
+#      $OpenBSD: files.amd64,v 1.93 2018/01/11 22:31:09 patrick Exp $
 
 maxpartitions 16
 maxusers 2 16 128
@@ -16,6 +16,7 @@ file  arch/amd64/amd64/locore.S
 file   arch/amd64/amd64/aes_intel.S            crypto
 file   arch/amd64/amd64/aesni.c                crypto
 file   arch/amd64/amd64/amd64errata.c
+file   arch/amd64/amd64/ucode.c
 file   arch/amd64/amd64/mem.c
 file   arch/amd64/amd64/amd64_mem.c            mtrr
 file   arch/amd64/amd64/mtrr.c                 mtrr
index 427a337..a86ac71 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpufunc.h,v 1.21 2017/10/17 20:38:49 deraadt Exp $    */
+/*     $OpenBSD: cpufunc.h,v 1.22 2018/01/11 22:31:09 patrick Exp $    */
 /*     $NetBSD: cpufunc.h,v 1.3 2003/05/08 10:27:43 fvdl Exp $ */
 
 /*-
@@ -314,6 +314,9 @@ breakpoint(void)
 }
 
 void amd64_errata(struct cpu_info *);
+void cpu_flags_update(struct cpu_info *);
+void cpu_ucode_apply(struct cpu_info *);
+void cpu_ucode_attachhook(struct device *);
 
 #endif /* _KERNEL */
 
index c1c0a47..85a8ecd 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: specialreg.h,v 1.63 2018/01/07 01:08:20 mlarkin Exp $ */
+/*     $OpenBSD: specialreg.h,v 1.64 2018/01/11 22:31:09 patrick Exp $ */
 /*     $NetBSD: specialreg.h,v 1.1 2003/04/26 18:39:48 fvdl Exp $      */
 /*     $NetBSD: x86/specialreg.h,v 1.2 2003/04/25 21:54:30 fvdl Exp $  */
 
 #define        MSR_CESR                0x011   /* P5 only (trap on P6) */
 #define        MSR_CTR0                0x012   /* P5 only (trap on P6) */
 #define        MSR_CTR1                0x013   /* P5 only (trap on P6) */
+#define        MSR_PLATFORM_ID         0x017   /* Platform ID for microcode */
 #define MSR_APICBASE           0x01b
 #define APICBASE_BSP           0x100
 #define APICBASE_ENABLE_X2APIC 0x400