Merge branch 'x86-microcode-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 6 Jan 2012 23:02:14 +0000 (15:02 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 6 Jan 2012 23:02:14 +0000 (15:02 -0800)
* 'x86-microcode-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86, microcode, AMD: Update copyrights
  x86, microcode, AMD: Exit early on success
  x86, microcode, AMD: Simplify ucode verification
  x86, microcode, AMD: Add a reusable buffer
  x86, microcode, AMD: Add a vendor-specific exit function

arch/x86/include/asm/microcode.h
arch/x86/kernel/microcode_amd.c
arch/x86/kernel/microcode_core.c

index 24215072d0e1e5894d4643634bfe1ef9786eef55..4ebe157bf73db184b4e73df8f391db399fd874a7 100644 (file)
@@ -48,6 +48,7 @@ static inline struct microcode_ops * __init init_intel_microcode(void)
 
 #ifdef CONFIG_MICROCODE_AMD
 extern struct microcode_ops * __init init_amd_microcode(void);
+extern void __exit exit_amd_microcode(void);
 
 static inline void get_ucode_data(void *to, const u8 *from, size_t n)
 {
@@ -59,6 +60,7 @@ static inline struct microcode_ops * __init init_amd_microcode(void)
 {
        return NULL;
 }
+static inline void __exit exit_amd_microcode(void) {}
 #endif
 
 #endif /* _ASM_X86_MICROCODE_H */
index d494799aafcd46b7e444940557321a42fce07175..fe86493f3ed1cc37912290e7f5461a2471785fc7 100644 (file)
@@ -1,14 +1,18 @@
 /*
  *  AMD CPU Microcode Update Driver for Linux
- *  Copyright (C) 2008 Advanced Micro Devices Inc.
+ *  Copyright (C) 2008-2011 Advanced Micro Devices Inc.
  *
  *  Author: Peter Oruba <peter.oruba@amd.com>
  *
  *  Based on work by:
  *  Tigran Aivazian <tigran@aivazian.fsnet.co.uk>
  *
- *  This driver allows to upgrade microcode on AMD
- *  family 0x10 and 0x11 processors.
+ *  Maintainers:
+ *  Andreas Herrmann <andreas.herrmann3@amd.com>
+ *  Borislav Petkov <borislav.petkov@amd.com>
+ *
+ *  This driver allows to upgrade microcode on F10h AMD
+ *  CPUs and later.
  *
  *  Licensed under the terms of the GNU General Public
  *  License version 2. See file COPYING for details.
@@ -71,6 +75,9 @@ struct microcode_amd {
 
 static struct equiv_cpu_entry *equiv_cpu_table;
 
+/* page-sized ucode patch buffer */
+void *patch;
+
 static int collect_cpu_info_amd(int cpu, struct cpu_signature *csig)
 {
        struct cpuinfo_x86 *c = &cpu_data(cpu);
@@ -86,27 +93,76 @@ static int collect_cpu_info_amd(int cpu, struct cpu_signature *csig)
        return 0;
 }
 
-static int get_matching_microcode(int cpu, struct microcode_header_amd *mc_hdr,
-                                 int rev)
+static unsigned int verify_ucode_size(int cpu, u32 patch_size,
+                                     unsigned int size)
 {
-       unsigned int current_cpu_id;
-       u16 equiv_cpu_id = 0;
-       unsigned int i = 0;
+       struct cpuinfo_x86 *c = &cpu_data(cpu);
+       u32 max_size;
+
+#define F1XH_MPB_MAX_SIZE 2048
+#define F14H_MPB_MAX_SIZE 1824
+#define F15H_MPB_MAX_SIZE 4096
+
+       switch (c->x86) {
+       case 0x14:
+               max_size = F14H_MPB_MAX_SIZE;
+               break;
+       case 0x15:
+               max_size = F15H_MPB_MAX_SIZE;
+               break;
+       default:
+               max_size = F1XH_MPB_MAX_SIZE;
+               break;
+       }
+
+       if (patch_size > min_t(u32, size, max_size)) {
+               pr_err("patch size mismatch\n");
+               return 0;
+       }
+
+       return patch_size;
+}
+
+static u16 find_equiv_id(void)
+{
+       unsigned int current_cpu_id, i = 0;
 
        BUG_ON(equiv_cpu_table == NULL);
+
        current_cpu_id = cpuid_eax(0x00000001);
 
        while (equiv_cpu_table[i].installed_cpu != 0) {
-               if (current_cpu_id == equiv_cpu_table[i].installed_cpu) {
-                       equiv_cpu_id = equiv_cpu_table[i].equiv_cpu;
-                       break;
-               }
+               if (current_cpu_id == equiv_cpu_table[i].installed_cpu)
+                       return equiv_cpu_table[i].equiv_cpu;
+
                i++;
        }
+       return 0;
+}
 
+/*
+ * we signal a good patch is found by returning its size > 0
+ */
+static int get_matching_microcode(int cpu, const u8 *ucode_ptr,
+                                 unsigned int leftover_size, int rev,
+                                 unsigned int *current_size)
+{
+       struct microcode_header_amd *mc_hdr;
+       unsigned int actual_size;
+       u16 equiv_cpu_id;
+
+       /* size of the current patch we're staring at */
+       *current_size = *(u32 *)(ucode_ptr + 4) + SECTION_HDR_SIZE;
+
+       equiv_cpu_id = find_equiv_id();
        if (!equiv_cpu_id)
                return 0;
 
+       /*
+        * let's look at the patch header itself now
+        */
+       mc_hdr = (struct microcode_header_amd *)(ucode_ptr + SECTION_HDR_SIZE);
+
        if (mc_hdr->processor_rev_id != equiv_cpu_id)
                return 0;
 
@@ -120,7 +176,20 @@ static int get_matching_microcode(int cpu, struct microcode_header_amd *mc_hdr,
        if (mc_hdr->patch_id <= rev)
                return 0;
 
-       return 1;
+       /*
+        * now that the header looks sane, verify its size
+        */
+       actual_size = verify_ucode_size(cpu, *current_size, leftover_size);
+       if (!actual_size)
+               return 0;
+
+       /* clear the patch buffer */
+       memset(patch, 0, PAGE_SIZE);
+
+       /* all looks ok, get the binary patch */
+       get_ucode_data(patch, ucode_ptr + SECTION_HDR_SIZE, actual_size);
+
+       return actual_size;
 }
 
 static int apply_microcode_amd(int cpu)
@@ -155,63 +224,6 @@ static int apply_microcode_amd(int cpu)
        return 0;
 }
 
-static unsigned int verify_ucode_size(int cpu, const u8 *buf, unsigned int size)
-{
-       struct cpuinfo_x86 *c = &cpu_data(cpu);
-       u32 max_size, actual_size;
-
-#define F1XH_MPB_MAX_SIZE 2048
-#define F14H_MPB_MAX_SIZE 1824
-#define F15H_MPB_MAX_SIZE 4096
-
-       switch (c->x86) {
-       case 0x14:
-               max_size = F14H_MPB_MAX_SIZE;
-               break;
-       case 0x15:
-               max_size = F15H_MPB_MAX_SIZE;
-               break;
-       default:
-               max_size = F1XH_MPB_MAX_SIZE;
-               break;
-       }
-
-       actual_size = *(u32 *)(buf + 4);
-
-       if (actual_size + SECTION_HDR_SIZE > size || actual_size > max_size) {
-               pr_err("section size mismatch\n");
-               return 0;
-       }
-
-       return actual_size;
-}
-
-static struct microcode_header_amd *
-get_next_ucode(int cpu, const u8 *buf, unsigned int size, unsigned int *mc_size)
-{
-       struct microcode_header_amd *mc = NULL;
-       unsigned int actual_size = 0;
-
-       if (*(u32 *)buf != UCODE_UCODE_TYPE) {
-               pr_err("invalid type field in container file section header\n");
-               goto out;
-       }
-
-       actual_size = verify_ucode_size(cpu, buf, size);
-       if (!actual_size)
-               goto out;
-
-       mc = vzalloc(actual_size);
-       if (!mc)
-               goto out;
-
-       get_ucode_data(mc, buf + SECTION_HDR_SIZE, actual_size);
-       *mc_size = actual_size + SECTION_HDR_SIZE;
-
-out:
-       return mc;
-}
-
 static int install_equiv_cpu_table(const u8 *buf)
 {
        unsigned int *ibuf = (unsigned int *)buf;
@@ -247,36 +259,38 @@ generic_load_microcode(int cpu, const u8 *data, size_t size)
 {
        struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
        struct microcode_header_amd *mc_hdr = NULL;
-       unsigned int mc_size, leftover;
+       unsigned int mc_size, leftover, current_size = 0;
        int offset;
        const u8 *ucode_ptr = data;
        void *new_mc = NULL;
        unsigned int new_rev = uci->cpu_sig.rev;
-       enum ucode_state state = UCODE_OK;
+       enum ucode_state state = UCODE_ERROR;
 
        offset = install_equiv_cpu_table(ucode_ptr);
        if (offset < 0) {
                pr_err("failed to create equivalent cpu table\n");
-               return UCODE_ERROR;
+               goto out;
        }
-
        ucode_ptr += offset;
        leftover = size - offset;
 
-       while (leftover) {
-               mc_hdr = get_next_ucode(cpu, ucode_ptr, leftover, &mc_size);
-               if (!mc_hdr)
-                       break;
+       if (*(u32 *)ucode_ptr != UCODE_UCODE_TYPE) {
+               pr_err("invalid type field in container file section header\n");
+               goto free_table;
+       }
 
-               if (get_matching_microcode(cpu, mc_hdr, new_rev)) {
-                       vfree(new_mc);
+       while (leftover) {
+               mc_size = get_matching_microcode(cpu, ucode_ptr, leftover,
+                                                new_rev, &current_size);
+               if (mc_size) {
+                       mc_hdr  = patch;
+                       new_mc  = patch;
                        new_rev = mc_hdr->patch_id;
-                       new_mc  = mc_hdr;
-               } else
-                       vfree(mc_hdr);
+                       goto out_ok;
+               }
 
-               ucode_ptr += mc_size;
-               leftover  -= mc_size;
+               ucode_ptr += current_size;
+               leftover  -= current_size;
        }
 
        if (!new_mc) {
@@ -284,19 +298,16 @@ generic_load_microcode(int cpu, const u8 *data, size_t size)
                goto free_table;
        }
 
-       if (!leftover) {
-               vfree(uci->mc);
-               uci->mc = new_mc;
-               pr_debug("CPU%d update ucode (0x%08x -> 0x%08x)\n",
-                        cpu, uci->cpu_sig.rev, new_rev);
-       } else {
-               vfree(new_mc);
-               state = UCODE_ERROR;
-       }
+out_ok:
+       uci->mc = new_mc;
+       state = UCODE_OK;
+       pr_debug("CPU%d update ucode (0x%08x -> 0x%08x)\n",
+                cpu, uci->cpu_sig.rev, new_rev);
 
 free_table:
        free_equiv_cpu_table();
 
+out:
        return state;
 }
 
@@ -337,7 +348,6 @@ static void microcode_fini_cpu_amd(int cpu)
 {
        struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
 
-       vfree(uci->mc);
        uci->mc = NULL;
 }
 
@@ -351,5 +361,14 @@ static struct microcode_ops microcode_amd_ops = {
 
 struct microcode_ops * __init init_amd_microcode(void)
 {
+       patch = (void *)get_zeroed_page(GFP_KERNEL);
+       if (!patch)
+               return NULL;
+
        return &microcode_amd_ops;
 }
+
+void __exit exit_amd_microcode(void)
+{
+       free_page((unsigned long)patch);
+}
index 9d46f5e43b51f0dd2b02ad0575c1e90470df862a..9302e2d0eb4b5dcf5175976553486d50767aa0a8 100644 (file)
@@ -563,6 +563,8 @@ module_init(microcode_init);
 
 static void __exit microcode_exit(void)
 {
+       struct cpuinfo_x86 *c = &cpu_data(0);
+
        microcode_dev_exit();
 
        unregister_hotcpu_notifier(&mc_cpu_notifier);
@@ -580,6 +582,9 @@ static void __exit microcode_exit(void)
 
        microcode_ops = NULL;
 
+       if (c->x86_vendor == X86_VENDOR_AMD)
+               exit_amd_microcode();
+
        pr_info("Microcode Update Driver: v" MICROCODE_VERSION " removed.\n");
 }
 module_exit(microcode_exit);