[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-changelog] [xen master] x86/microcode: Scan the initramfs payload for microcode blob



commit 155587481e392e4261038364e2761aab27f597ed
Author:     Konrad Rzeszutek Wilk <konrad.wilk@xxxxxxxxxx>
AuthorDate: Fri Sep 27 10:22:55 2013 +0200
Commit:     Jan Beulich <jbeulich@xxxxxxxx>
CommitDate: Fri Sep 27 10:22:55 2013 +0200

    x86/microcode: Scan the initramfs payload for microcode blob
    
    The Linux kernel is able to update the microcode during early bootup
    via inspection of the initramfs blob to see if there is an cpio image
    with certain microcode files. Linux is able to function with two (or
    more) cpio archives in the initrd b/c it unpacks all of the cpio
    archives.
    
    The format of the early initramfs is nicely documented in Linux's
    Documentation/x86/early-microcode.txt:
    
    Early load microcode
    ====================
    By Fenghua Yu <fenghua.yu@xxxxxxxxx>
    
    Kernel can update microcode in early phase of boot time. Loading microcode 
early
    can fix CPU issues before they are observed during kernel boot time.
    
    Microcode is stored in an initrd file. The microcode is read from the initrd
    file and loaded to CPUs during boot time.
    
    The format of the combined initrd image is microcode in cpio format 
followed by
    the initrd image (maybe compressed). Kernel parses the combined initrd image
    during boot time. The microcode file in cpio name space is:
    kernel/x86/microcode/GenuineIntel.bin
    
    During BSP boot (before SMP starts), if the kernel finds the microcode file 
in
    the initrd file, it parses the microcode and saves matching microcode in 
memory.
    If matching microcode is found, it will be uploaded in BSP and later on in 
all
    APs.
    
    The cached microcode patch is applied when CPUs resume from a sleep state.
    
    There are two legacy user space interfaces to load microcode, either through
    /dev/cpu/microcode or through /sys/devices/system/cpu/microcode/reload file
    in sysfs.
    
    In addition to these two legacy methods, the early loading method described
    here is the third method with which microcode can be uploaded to a system's
    CPUs.
    
    The following example script shows how to generate a new combined initrd 
file in
    /boot/initrd-3.5.0.ucode.img with original microcode microcode.bin and
    original initrd image /boot/initrd-3.5.0.img.
    
    mkdir initrd
    cd initrd
    mkdir kernel
    mkdir kernel/x86
    mkdir kernel/x86/microcode
    cp ../microcode.bin kernel/x86/microcode/GenuineIntel.bin
    find .|cpio -oc >../ucode.cpio
    cd ..
    cat ucode.cpio /boot/initrd-3.5.0.img >/boot/initrd-3.5.0.ucode.img
    
    As such this code inspects the initrd to see if the microcode
    signatures are present and if so updates the hypervisor.
    
    The option to turn this scan on/off is gated by the 'ucode'
    parameter. The options are now:
     'scan'      Scan for the microcode in any multiboot payload.
     <index>     Attempt to load microcode blob (not the cpio archive
                 format) from the multiboot payload number.
    
    This option alters slightly the 'ucode' parameter by only allowing
    either parameter:
      ucode=[<index>|scan]
    
    Implementation wise the ucode_blob is defined as __initdata.
    That is OK from the viewpoint of suspend/resume as the the underlaying
    architecture microcode (microcode_intel or microcode_amd) end up saving
    the blob in 'struct ucode_cpu_info' which is a per-cpu data
    structure (see ucode_cpu_info). They end up saving it when doing the
    pre-SMP (for CPU0) and SMP (for the rest) microcode loading.
    
    Naturally if one does a hypercall to update the microcode and it is
    newer, then the old per-cpu data is replaced.
    
    Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@xxxxxxxxxx>
    Acked-by: Keir Fraser <keir@xxxxxxx>
---
 docs/misc/xen-command-line.markdown |   14 +++-
 xen/arch/x86/microcode.c            |  158 +++++++++++++++++++++++++++++++----
 xen/common/Makefile                 |    2 +-
 xen/common/earlycpio.c              |  151 +++++++++++++++++++++++++++++++++
 xen/include/xen/earlycpio.h         |   14 +++
 5 files changed, 320 insertions(+), 19 deletions(-)

diff --git a/docs/misc/xen-command-line.markdown 
b/docs/misc/xen-command-line.markdown
index 56c9729..dc72473 100644
--- a/docs/misc/xen-command-line.markdown
+++ b/docs/misc/xen-command-line.markdown
@@ -897,10 +897,12 @@ pages) must also be specified via the tbuf\_size 
parameter.
 > `= unstable | skewed`
 
 ### ucode
-> `= <integer>`
+> `= [<integer> | scan]`
+
+Specify how and where to find CPU microcode update blob.
 
-Specify the CPU microcode update blob module index. When positive, this
-specifies the n-th module (in the GrUB entry, zero based) to be used
+'integer' specifies the CPU microcode update blob module index. When positive,
+this specifies the n-th module (in the GrUB entry, zero based) to be used
 for updating CPU micrcode. When negative, counting starts at the end of
 the modules in the GrUB entry (so with the blob commonly being last,
 one could specify `ucode=-1`). Note that the value of zero is not valid
@@ -910,6 +912,12 @@ when used with xen.efi (there the concept of modules 
doesn't exist, and
 the blob gets specified via the `ucode=<filename>` config file/section
 entry; see [EFI configuration file description](efi.html)).
 
+'scan' instructs the hypervisor to scan the multiboot images for an cpio
+image that contains microcode. Depending on the platform the blob with the
+microcode in the cpio name space must be:
+  - on Intel: kernel/x86/microcode/GenuineIntel.bin
+  - on AMD  : kernel/x86/microcode/AuthenticAMD.bin
+
 ### unrestricted\_guest
 > `= <boolean>`
 
diff --git a/xen/arch/x86/microcode.c b/xen/arch/x86/microcode.c
index fbbf95b..e6344cf 100644
--- a/xen/arch/x86/microcode.c
+++ b/xen/arch/x86/microcode.c
@@ -33,6 +33,7 @@
 #include <xen/spinlock.h>
 #include <xen/tasklet.h>
 #include <xen/guest_access.h>
+#include <xen/earlycpio.h>
 
 #include <asm/msr.h>
 #include <asm/processor.h>
@@ -45,19 +46,123 @@ static signed int __initdata ucode_mod_idx;
 static bool_t __initdata ucode_mod_forced;
 static cpumask_t __initdata init_mask;
 
+/*
+ * If we scan the initramfs.cpio for the early microcode code
+ * and find it, then 'ucode_blob' will contain the pointer
+ * and the size of said blob. It is allocated from Xen's heap
+ * memory.
+ */
+struct ucode_mod_blob {
+    void *data;
+    size_t size;
+};
+
+static struct ucode_mod_blob __initdata ucode_blob;
+/*
+ * By default we will NOT parse the multiboot modules to see if there is
+ * cpio image with the microcode images.
+ */
+static bool_t __initdata ucode_scan;
+
 void __init microcode_set_module(unsigned int idx)
 {
     ucode_mod_idx = idx;
     ucode_mod_forced = 1;
 }
 
+/*
+ * The format is '[<integer>|scan]'. Both options are optional.
+ * If the EFI has forced which of the multiboot payloads is to be used,
+ * no parsing will be attempted.
+ */
 static void __init parse_ucode(char *s)
 {
-    if ( !ucode_mod_forced )
+    if ( ucode_mod_forced ) /* Forced by EFI */
+       return;
+
+    if ( !strncmp(s, "scan", 4) )
+        ucode_scan = 1;
+    else
         ucode_mod_idx = simple_strtol(s, NULL, 0);
 }
 custom_param("ucode", parse_ucode);
 
+/*
+ * 8MB ought to be enough.
+ */
+#define MAX_EARLY_CPIO_MICROCODE (8 << 20)
+
+void __init microcode_scan_module(
+    unsigned long *module_map,
+    const multiboot_info_t *mbi,
+    void *(*bootmap)(const module_t *))
+{
+    module_t *mod = (module_t *)__va(mbi->mods_addr);
+    uint64_t *_blob_start;
+    unsigned long _blob_size;
+    struct cpio_data cd;
+    long offset;
+    const char *p = NULL;
+    int i;
+
+    ucode_blob.size = 0;
+    if ( !ucode_scan )
+        return;
+
+    if ( boot_cpu_data.x86_vendor == X86_VENDOR_AMD )
+        p = "kernel/x86/microcode/AuthenticAMD.bin";
+    else if ( boot_cpu_data.x86_vendor == X86_VENDOR_INTEL )
+        p = "kernel/x86/microcode/GenuineIntel.bin";
+    else
+        return;
+
+    /*
+     * Try all modules and see whichever could be the microcode blob.
+     */
+    for ( i = 1 /* Ignore dom0 kernel */; i < mbi->mods_count; i++ )
+    {
+        if ( !test_bit(i, module_map) )
+            continue;
+
+        _blob_start = bootmap(&mod[i]);
+        _blob_size = mod[i].mod_end;
+        if ( !_blob_start )
+        {
+            printk("Could not map multiboot module #%d (size: %ld)\n",
+                   i, _blob_size);
+            continue;
+        }
+        cd.data = NULL;
+        cd.size = 0;
+        cd = find_cpio_data(p, _blob_start, _blob_size, &offset /* ignore */);
+        if ( cd.data )
+        {
+                /*
+                 * This is an arbitrary check - it would be sad if the blob
+                 * consumed most of the memory and did not allow guests
+                 * to launch.
+                 */
+                if ( cd.size > MAX_EARLY_CPIO_MICROCODE )
+                {
+                    printk("Multiboot %d microcode payload too big! (%ld, we 
can do %d)\n",
+                           i, cd.size, MAX_EARLY_CPIO_MICROCODE);
+                    goto err;
+                }
+                ucode_blob.size = cd.size;
+                ucode_blob.data = xmalloc_bytes(cd.size);
+                if ( !ucode_blob.data )
+                    cd.data = NULL;
+                else
+                    memcpy(ucode_blob.data, cd.data, cd.size);
+        }
+        bootmap(NULL);
+        if ( cd.data )
+            break;
+    }
+    return;
+err:
+    bootmap(NULL);
+}
 void __init microcode_grab_module(
     unsigned long *module_map,
     const multiboot_info_t *mbi,
@@ -69,9 +174,12 @@ void __init microcode_grab_module(
         ucode_mod_idx += mbi->mods_count;
     if ( ucode_mod_idx <= 0 || ucode_mod_idx >= mbi->mods_count ||
          !__test_and_clear_bit(ucode_mod_idx, module_map) )
-        return;
+        goto scan;
     ucode_mod = mod[ucode_mod_idx];
     ucode_mod_map = map;
+scan:
+    if ( ucode_scan )
+        microcode_scan_module(module_map, mbi, map);
 }
 
 const struct microcode_ops *microcode_ops;
@@ -236,7 +344,10 @@ int microcode_update(XEN_GUEST_HANDLE_PARAM(const_void) 
buf, unsigned long len)
 
 static void __init _do_microcode_update(unsigned long data)
 {
-    microcode_update_cpu((void *)data, ucode_mod.mod_end);
+    void *_data = (void *)data;
+    size_t len = ucode_blob.size ? ucode_blob.size : ucode_mod.mod_end;
+
+    microcode_update_cpu(_data, len);
     cpumask_set_cpu(smp_processor_id(), &init_mask);
 }
 
@@ -246,18 +357,19 @@ static int __init microcode_init(void)
     static struct tasklet __initdata tasklet;
     unsigned int cpu;
 
-    if ( !microcode_ops || !ucode_mod.mod_end )
+    if ( !microcode_ops )
+        return 0;
+
+    if ( !ucode_mod.mod_end && !ucode_blob.size )
         return 0;
 
-    data = ucode_mod_map(&ucode_mod);
+    data = ucode_blob.size ? ucode_blob.data : ucode_mod_map(&ucode_mod);
+
     if ( !data )
         return -ENOMEM;
 
     if ( microcode_ops->start_update && microcode_ops->start_update() != 0 )
-    {
-        ucode_mod_map(NULL);
-        return 0;
-    }
+        goto out;
 
     softirq_tasklet_init(&tasklet, _do_microcode_update, (unsigned long)data);
 
@@ -269,7 +381,11 @@ static int __init microcode_init(void)
         } while ( !cpumask_test_cpu(cpu, &init_mask) );
     }
 
-    ucode_mod_map(NULL);
+out:
+    if ( ucode_blob.size )
+        xfree(data);
+    else
+        ucode_mod_map(NULL);
 
     return 0;
 }
@@ -298,14 +414,26 @@ static int __init microcode_presmp_init(void)
 {
     if ( microcode_ops )
     {
-        if ( ucode_mod.mod_end )
+        if ( ucode_mod.mod_end || ucode_blob.size )
         {
-            void *data = ucode_mod_map(&ucode_mod);
-
+            void *data;
+            size_t len;
+
+            if ( ucode_blob.size )
+            {
+                len = ucode_blob.size;
+                data = ucode_blob.data;
+            }
+            else
+            {
+                len = ucode_mod.mod_end;
+                data = ucode_mod_map(&ucode_mod);
+            }
             if ( data )
-                microcode_update_cpu(data, ucode_mod.mod_end);
+                microcode_update_cpu(data, len);
 
-            ucode_mod_map(NULL);
+            if ( !ucode_blob.size )
+                ucode_mod_map(NULL);
         }
 
         register_cpu_notifier(&microcode_percpu_nfb);
diff --git a/xen/common/Makefile b/xen/common/Makefile
index 5486140..6da4651 100644
--- a/xen/common/Makefile
+++ b/xen/common/Makefile
@@ -49,7 +49,7 @@ obj-y += radix-tree.o
 obj-y += rbtree.o
 obj-y += lzo.o
 
-obj-bin-$(CONFIG_X86) += $(foreach n,decompress bunzip2 unxz unlzma 
unlzo,$(n).init.o)
+obj-bin-$(CONFIG_X86) += $(foreach n,decompress bunzip2 unxz unlzma unlzo 
earlycpio,$(n).init.o)
 
 obj-$(perfc)       += perfc.o
 obj-$(crash_debug) += gdbstub.o
diff --git a/xen/common/earlycpio.c b/xen/common/earlycpio.c
new file mode 100644
index 0000000..5e54142
--- /dev/null
+++ b/xen/common/earlycpio.c
@@ -0,0 +1,151 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2012 Intel Corporation; author H. Peter Anvin
+ *
+ *   This file is part of the Linux kernel, and is made available
+ *   under the terms of the GNU General Public License version 2, as
+ *   published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * earlycpio.c
+ *
+ * Find a specific cpio member; must precede any compressed content.
+ * This is used to locate data items in the initramfs used by the
+ * kernel itself during early boot (before the main initramfs is
+ * decompressed.)  It is the responsibility of the initramfs creator
+ * to ensure that these items are uncompressed at the head of the
+ * blob.  Depending on the boot loader or package tool that may be a
+ * separate file or part of the same file.
+ */
+
+#include <xen/config.h>
+#include <xen/init.h>
+#include <xen/lib.h>
+#include <xen/string.h>
+#include <xen/earlycpio.h>
+
+#define ALIGN(x, a) ((x + (a) - 1) & ~((a) - 1))
+#define PTR_ALIGN(p, a)         ((typeof(p))ALIGN((unsigned long)(p), (a)))
+
+enum cpio_fields {
+       C_MAGIC,
+       C_INO,
+       C_MODE,
+       C_UID,
+       C_GID,
+       C_NLINK,
+       C_MTIME,
+       C_FILESIZE,
+       C_MAJ,
+       C_MIN,
+       C_RMAJ,
+       C_RMIN,
+       C_NAMESIZE,
+       C_CHKSUM,
+       C_NFIELDS
+};
+
+/**
+ * cpio_data find_cpio_data - Search for files in an uncompressed cpio
+ * @path:   The directory to search for, including a slash at the end
+ * @data:   Pointer to the the cpio archive or a header inside
+ * @len:    Remaining length of the cpio based on data pointer
+ * @offset: When a matching file is found, this is the offset to the
+ *          beginning of the cpio. It can be used to iterate through
+ *          the cpio to find all files inside of a directory path
+ *
+ * @return: struct cpio_data containing the address, length and
+ *          filename (with the directory path cut off) of the found file.
+ *          If you search for a filename and not for files in a directory,
+ *          pass the absolute path of the filename in the cpio and make sure
+ *          the match returned an empty filename string.
+ */
+
+struct cpio_data __init find_cpio_data(const char *path, void *data,
+                                         size_t len,  long *offset)
+{
+       const size_t cpio_header_len = 8*C_NFIELDS - 2;
+       struct cpio_data cd = { NULL, 0 };
+       const char *p, *dptr, *nptr;
+       unsigned int ch[C_NFIELDS], *chp, v;
+       unsigned char c, x;
+       size_t mypathsize = strlen(path);
+       int i, j;
+
+       p = data;
+
+       while (len > cpio_header_len) {
+               if (!*p) {
+                       /* All cpio headers need to be 4-byte aligned */
+                       p += 4;
+                       len -= 4;
+                       continue;
+               }
+
+               j = 6;          /* The magic field is only 6 characters */
+               chp = ch;
+               for (i = C_NFIELDS; i; i--) {
+                       v = 0;
+                       while (j--) {
+                               v <<= 4;
+                               c = *p++;
+
+                               x = c - '0';
+                               if (x < 10) {
+                                       v += x;
+                                       continue;
+                               }
+
+                               x = (c | 0x20) - 'a';
+                               if (x < 6) {
+                                       v += x + 10;
+                                       continue;
+                               }
+
+                               goto quit; /* Invalid hexadecimal */
+                       }
+                       *chp++ = v;
+                       j = 8;  /* All other fields are 8 characters */
+               }
+
+               if ((ch[C_MAGIC] - 0x070701) > 1)
+                       goto quit; /* Invalid magic */
+
+               len -= cpio_header_len;
+
+               dptr = PTR_ALIGN(p + ch[C_NAMESIZE], 4);
+               nptr = PTR_ALIGN(dptr + ch[C_FILESIZE], 4);
+
+               if (nptr > p + len || dptr < p || nptr < dptr)
+                       goto quit; /* Buffer overrun */
+
+               if ((ch[C_MODE] & 0170000) == 0100000 &&
+                   ch[C_NAMESIZE] >= mypathsize &&
+                   !memcmp(p, path, mypathsize)) {
+                       *offset = (long)nptr - (long)data;
+                       if (ch[C_NAMESIZE] - mypathsize >= MAX_CPIO_FILE_NAME) {
+                               printk(
+                               "File %s exceeding MAX_CPIO_FILE_NAME [%d]\n",
+                               p, MAX_CPIO_FILE_NAME);
+                       }
+                       if (ch[C_NAMESIZE] - 1 /* includes \0 */ == mypathsize) 
{
+                               cd.data = (void *)dptr;
+                               cd.size = ch[C_FILESIZE];
+                               return cd; /* Found it! */
+                       }
+               }
+               len -= (nptr - p);
+               p = nptr;
+       }
+
+quit:
+       return cd;
+}
+
diff --git a/xen/include/xen/earlycpio.h b/xen/include/xen/earlycpio.h
new file mode 100644
index 0000000..85d144a
--- /dev/null
+++ b/xen/include/xen/earlycpio.h
@@ -0,0 +1,14 @@
+#ifndef _EARLYCPIO_H
+#define _EARLYCPIO_H
+
+#define MAX_CPIO_FILE_NAME 18
+
+struct cpio_data {
+       void *data;
+       size_t size;
+};
+
+struct cpio_data find_cpio_data(const char *path, void *data, size_t len,
+                               long *offset);
+
+#endif /* _EARLYCPIO_H */
--
generated by git-patchbot for /home/xen/git/xen.git#master

_______________________________________________
Xen-changelog mailing list
Xen-changelog@xxxxxxxxxxxxx
http://lists.xensource.com/xen-changelog


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.