Re: [tip:x86/microcode] x86/microcode_intel_early.c: Early updateucode on Intel's CPU

From: Yinghai Lu
Date: Fri Nov 30 2012 - 19:55:18 EST


On Fri, Nov 30, 2012 at 4:25 PM, tip-bot for Fenghua Yu
<fenghua.yu@xxxxxxxxx> wrote:
> Commit-ID: 474355fe313391de2429ae225e0fb02f67ec6c31
> Gitweb: http://git.kernel.org/tip/474355fe313391de2429ae225e0fb02f67ec6c31
> Author: Fenghua Yu <fenghua.yu@xxxxxxxxx>
> AuthorDate: Thu, 29 Nov 2012 17:47:43 -0800
> Committer: H. Peter Anvin <hpa@xxxxxxxxxxxxxxx>
> CommitDate: Fri, 30 Nov 2012 15:18:16 -0800
>
> x86/microcode_intel_early.c: Early update ucode on Intel's CPU
>
> Implementation of early update ucode on Intel's CPU.
>
> load_ucode_intel_bsp() scans ucode in initrd image file which is a cpio format
> ucode followed by ordinary initrd image file. The binary ucode file is stored
> in kernel/x86/microcode/GenuineIntel/microcode.bin in the cpio data. All ucode
> patches with the same model as BSP are saved in memory. A matching ucode patch
> is updated on BSP.
>
> load_ucode_intel_ap() reads saved ucoded patches and updates ucode on AP.
>
> Signed-off-by: Fenghua Yu <fenghua.yu@xxxxxxxxx>
> Link: http://lkml.kernel.org/r/1354240068-9821-6-git-send-email-fenghua.yu@xxxxxxxxx
> Signed-off-by: H. Peter Anvin <hpa@xxxxxxxxxxxxxxx>
> ---
> arch/x86/kernel/microcode_intel_early.c | 438 ++++++++++++++++++++++++++++++++
> 1 file changed, 438 insertions(+)
>
> diff --git a/arch/x86/kernel/microcode_intel_early.c b/arch/x86/kernel/microcode_intel_early.c
> new file mode 100644
> index 0000000..36b1df1
> --- /dev/null
> +++ b/arch/x86/kernel/microcode_intel_early.c
> @@ -0,0 +1,438 @@
> +/*
> + * Intel CPU Microcode Update Driver for Linux
> + *
> + * Copyright (C) 2012 Fenghua Yu <fenghua.yu@xxxxxxxxx>
> + * H Peter Anvin" <hpa@xxxxxxxxx>
> + *
> + * This driver allows to early upgrade microcode on Intel processors
> + * belonging to IA-32 family - PentiumPro, Pentium II,
> + * Pentium III, Xeon, Pentium 4, etc.
> + *
> + * Reference: Section 9.11 of Volume 3, IA-32 Intel Architecture
> + * Software Developer's Manual.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version
> + * 2 of the License, or (at your option) any later version.
> + */
> +#include <linux/module.h>
> +#include <linux/vmalloc.h>
> +#include <linux/mm.h>
> +#include <linux/earlycpio.h>
> +#include <asm/msr.h>
> +#include <asm/microcode_intel.h>
> +#include <asm/processor.h>
> +
> +struct microcode_intel __initdata *mc_saved_in_initrd[MAX_UCODE_COUNT];
> +struct mc_saved_data mc_saved_data;
> +
> +enum ucode_state
> +generic_load_microcode_early(int cpu, struct microcode_intel **mc_saved_p,
> + unsigned int mc_saved_count,
> + struct ucode_cpu_info *uci)
> +{
> + struct microcode_intel *ucode_ptr, *new_mc = NULL;
> + int new_rev = uci->cpu_sig.rev;
> + enum ucode_state state = UCODE_OK;
> + unsigned int mc_size;
> + struct microcode_header_intel *mc_header;
> + unsigned int csig = uci->cpu_sig.sig;
> + unsigned int cpf = uci->cpu_sig.pf;
> + int i;
> +
> + for (i = 0; i < mc_saved_count; i++) {
> + ucode_ptr = mc_saved_p[i];
> + mc_header = (struct microcode_header_intel *)ucode_ptr;
> + mc_size = get_totalsize(mc_header);
> + if (get_matching_microcode(csig, cpf, ucode_ptr, new_rev)) {
> + new_rev = mc_header->rev;
> + new_mc = ucode_ptr;
> + }
> + }
> +
> + if (!new_mc) {
> + state = UCODE_NFOUND;
> + goto out;
> + }
> +
> + uci->mc = (struct microcode_intel *)new_mc;
> +out:
> + return state;
> +}
> +EXPORT_SYMBOL_GPL(generic_load_microcode_early);
> +
> +static enum ucode_state __init
> +load_microcode(struct mc_saved_data *mc_saved_data, int cpu)
> +{
> + struct ucode_cpu_info *uci = mc_saved_data->ucode_cpu_info + cpu;
> +
> + return generic_load_microcode_early(cpu, mc_saved_data->mc_saved,
> + mc_saved_data->mc_saved_count, uci);
> +}
> +
> +static u8 get_x86_family(unsigned long sig)
> +{
> + u8 x86;
> +
> + x86 = (sig >> 8) & 0xf;
> +
> + if (x86 == 0xf)
> + x86 += (sig >> 20) & 0xff;
> +
> + return x86;
> +}
> +
> +static u8 get_x86_model(unsigned long sig)
> +{
> + u8 x86, x86_model;
> +
> + x86 = get_x86_family(sig);
> + x86_model = (sig >> 4) & 0xf;
> +
> + if (x86 == 0x6 || x86 == 0xf)
> + x86_model += ((sig >> 16) & 0xf) << 4;
> +
> + return x86_model;
> +}
> +
> +static enum ucode_state
> +matching_model_microcode(struct microcode_header_intel *mc_header,
> + unsigned long sig)
> +{
> + u8 x86, x86_model;
> + u8 x86_ucode, x86_model_ucode;
> +
> + x86 = get_x86_family(sig);
> + x86_model = get_x86_model(sig);
> +
> + x86_ucode = get_x86_family(mc_header->sig);
> + x86_model_ucode = get_x86_model(mc_header->sig);
> +
> + if (x86 != x86_ucode || x86_model != x86_model_ucode)
> + return UCODE_ERROR;
> +
> + return UCODE_OK;
> +}
> +
> +static void
> +save_microcode(struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_src,
> + unsigned int mc_saved_count)
> +{
> + int i;
> + struct microcode_intel **mc_saved_p;
> +
> + if (!mc_saved_count)
> + return;
> +
> + mc_saved_p = vmalloc(mc_saved_count*sizeof(struct microcode_intel *));
> + if (!mc_saved_p)
> + return;
> +
> + for (i = 0; i < mc_saved_count; i++) {
> + struct microcode_intel *mc = mc_saved_src[i];
> + struct microcode_header_intel *mc_header = &mc->hdr;
> + unsigned long mc_size = get_totalsize(mc_header);
> + mc_saved_p[i] = vmalloc(mc_size);
> + if (mc_saved_src[i])
> + memcpy(mc_saved_p[i], mc, mc_size);
> + }
> +
> + mc_saved_data->mc_saved = mc_saved_p;
> +}
> +
> +/*
> + * Get microcode matching with BSP's model. Only CPU's with the same model as
> + * BSP can stay in the platform.
> + */
> +enum ucode_state
> +get_matching_model_microcode(int cpu, void *data, size_t size,
> + struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd,
> + enum system_states system_state)
> +{
> + u8 *ucode_ptr = data;
> + unsigned int leftover = size;
> + enum ucode_state state = UCODE_OK;
> + unsigned int mc_size;
> + struct microcode_header_intel *mc_header;
> + struct microcode_intel *mc_saved_tmp[MAX_UCODE_COUNT];
> + size_t mc_saved_size;
> + size_t mem_size;
> + unsigned int mc_saved_count = mc_saved_data->mc_saved_count;
> + struct ucode_cpu_info *uci = mc_saved_data->ucode_cpu_info + cpu;
> + int found = 0;
> + int i;
> +
> + if (mc_saved_count) {
> + mem_size = mc_saved_count * sizeof(struct microcode_intel *);
> + memcpy(mc_saved_tmp, mc_saved_data->mc_saved, mem_size);
> + }
> +
> + while (leftover) {
> + mc_header = (struct microcode_header_intel *)ucode_ptr;
> +
> + mc_size = get_totalsize(mc_header);
> + if (!mc_size || mc_size > leftover ||
> + microcode_sanity_check(ucode_ptr, 0) < 0)
> + break;
> +
> + leftover -= mc_size;
> + if (matching_model_microcode(mc_header, uci->cpu_sig.sig) !=
> + UCODE_OK) {
> + ucode_ptr += mc_size;
> + continue;
> + }
> +
> + found = 0;
> + for (i = 0; i < mc_saved_count; i++) {
> + unsigned int sig, pf;
> + unsigned int new_rev;
> + struct microcode_header_intel *mc_saved_header =
> + (struct microcode_header_intel *)mc_saved_tmp[i];
> + sig = mc_saved_header->sig;
> + pf = mc_saved_header->pf;
> + new_rev = mc_header->rev;
> +
> + if (get_matching_sig(sig, pf, ucode_ptr, new_rev)) {
> + found = 1;
> + if (update_match_revision(mc_header, new_rev)) {
> + /*
> + * Found an older ucode saved before.
> + * Replace the older one with this newer
> + * one.
> + */
> + mc_saved_tmp[i] =
> + (struct microcode_intel *)ucode_ptr;
> + break;
> + }
> + }
> + }
> + if (i >= mc_saved_count && !found)
> + /*
> + * This ucode is first time discovered in ucode file.
> + * Save it to memory.
> + */
> + mc_saved_tmp[mc_saved_count++] =
> + (struct microcode_intel *)ucode_ptr;
> +
> + ucode_ptr += mc_size;
> + }
> +
> + if (leftover) {
> + state = UCODE_ERROR;
> + goto out;
> + }
> +
> + if (mc_saved_count == 0) {
> + state = UCODE_NFOUND;
> + goto out;
> + }
> +
> + if (system_state == SYSTEM_RUNNING) {
> + vfree(mc_saved_data->mc_saved);
> + save_microcode(mc_saved_data, mc_saved_tmp, mc_saved_count);
> + } else {
> + mc_saved_size = sizeof(struct microcode_intel *) *
> + mc_saved_count;
> + memcpy(mc_saved_in_initrd, mc_saved_tmp, mc_saved_size);
> + mc_saved_data->mc_saved = mc_saved_in_initrd;
> + }
> +
> + mc_saved_data->mc_saved_count = mc_saved_count;
> +out:
> + return state;
> +}
> +EXPORT_SYMBOL_GPL(get_matching_model_microcode);
> +
> +#define native_rdmsr(msr, val1, val2) \
> +do { \
> + u64 __val = native_read_msr((msr)); \
> + (void)((val1) = (u32)__val); \
> + (void)((val2) = (u32)(__val >> 32)); \
> +} while (0)
> +
> +#define native_wrmsr(msr, low, high) \
> + native_write_msr(msr, low, high);
> +
> +static int __cpuinit collect_cpu_info_early(struct ucode_cpu_info *uci)
> +{
> + unsigned int val[2];
> + u8 x86, x86_model;
> + struct cpu_signature csig = {0, 0, 0};
> + unsigned int eax, ebx, ecx, edx;
> +
> + memset(uci, 0, sizeof(*uci));
> +
> + eax = 0x00000001;
> + ecx = 0;
> + native_cpuid(&eax, &ebx, &ecx, &edx);
> + csig.sig = eax;
> +
> + x86 = get_x86_family(csig.sig);
> + x86_model = get_x86_model(csig.sig);
> +
> + if ((x86_model >= 5) || (x86 > 6)) {
> + /* get processor flags from MSR 0x17 */
> + native_rdmsr(MSR_IA32_PLATFORM_ID, val[0], val[1]);
> + csig.pf = 1 << ((val[1] >> 18) & 7);
> + }
> +
> + /* get the current revision from MSR 0x8B */
> + native_rdmsr(MSR_IA32_UCODE_REV, val[0], val[1]);
> +
> + csig.rev = val[1];
> +
> + uci->cpu_sig = csig;
> + uci->valid = 1;
> +
> + return 0;
> +}
> +
> +static __init enum ucode_state
> +scan_microcode(unsigned long start, unsigned long end,
> + struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd)
> +{
> + unsigned int size = end - start + 1;
> + struct cpio_data cd = { 0, 0 };
> + char ucode_name[] = "kernel/x86/microcode/GenuineIntel.bin";
> + long offset = 0;
> +
> + cd = find_cpio_data(ucode_name, (void *)start, size, &offset);
> + if (!cd.data)
> + return UCODE_ERROR;
> +
> + return get_matching_model_microcode(0, cd.data, cd.size, mc_saved_data,
> + mc_saved_in_initrd, SYSTEM_BOOTING);
> +}
> +
> +static int __init
> +apply_microcode_early(struct mc_saved_data *mc_saved_data, int cpu)
> +{
> + struct ucode_cpu_info *uci = mc_saved_data->ucode_cpu_info + cpu;
> + struct microcode_intel *mc_intel;
> + unsigned int val[2];
> +
> + /* We should bind the task to the CPU */
> + mc_intel = uci->mc;
> + if (mc_intel == NULL)
> + return 0;
> +
> + /* write microcode via MSR 0x79 */
> + native_wrmsr(MSR_IA32_UCODE_WRITE,
> + (unsigned long) mc_intel->bits,
> + (unsigned long) mc_intel->bits >> 16 >> 16);
> + native_wrmsr(MSR_IA32_UCODE_REV, 0, 0);
> +
> + /* As documented in the SDM: Do a CPUID 1 here */
> + sync_core();
> +
> + /* get the current revision from MSR 0x8B */
> + native_rdmsr(MSR_IA32_UCODE_REV, val[0], val[1]);
> + if (val[1] != mc_intel->hdr.rev)
> + return -1;
> +
> + uci->cpu_sig.rev = val[1];
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_X86_32
> +static void __init map_mc_saved(struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd)
> +{
> + int i;
> +
> + if (mc_saved_data->mc_saved) {
> + for (i = 0; i < mc_saved_data->mc_saved_count; i++)
> + mc_saved_data->mc_saved[i] =
> + __va(mc_saved_data->mc_saved[i]);
> +
> + mc_saved_data->mc_saved = __va(mc_saved_data->mc_saved);
> + }
> +
> + if (mc_saved_data->ucode_cpu_info->mc)
> + mc_saved_data->ucode_cpu_info->mc =
> + __va(mc_saved_data->ucode_cpu_info->mc);
> + mc_saved_data->ucode_cpu_info = __va(mc_saved_data->ucode_cpu_info);
> +}
> +#else
> +static inline void __init map_mc_saved(struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd)
> +{
> +}
> +#endif
> +
> +void __init save_microcode_in_initrd(struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd)
> +{
> + unsigned int count = mc_saved_data->mc_saved_count;
> +
> + save_microcode(mc_saved_data, mc_saved_in_initrd, count);
> +}
> +
> +static void __init
> +_load_ucode_intel_bsp(struct mc_saved_data *mc_saved_data,
> + struct microcode_intel **mc_saved_in_initrd,
> + unsigned long initrd_start, unsigned long initrd_end)
> +{
> + int cpu = 0;
> +
> +#ifdef CONFIG_X86_64
> + mc_saved_data->ucode_cpu_info = ucode_cpu_info_early;
> +#else
> + mc_saved_data->ucode_cpu_info =
> + (struct ucode_cpu_info *)__pa(ucode_cpu_info_early);
> +#endif
> + collect_cpu_info_early(mc_saved_data->ucode_cpu_info + cpu);
> + scan_microcode(initrd_start, initrd_end, mc_saved_data,
> + mc_saved_in_initrd);
> + load_microcode(mc_saved_data, cpu);
> + apply_microcode_early(mc_saved_data, cpu);
> + map_mc_saved(mc_saved_data, mc_saved_in_initrd);
> +}
> +
> +void __init
> +load_ucode_intel_bsp(char *real_mode_data)
> +{
> + u64 ramdisk_image, ramdisk_size, ramdisk_end;
> + unsigned long initrd_start, initrd_end;
> + struct boot_params *boot_params;
> +
> + boot_params = (struct boot_params *)real_mode_data;
> + ramdisk_image = boot_params->hdr.ramdisk_image;
> + ramdisk_size = boot_params->hdr.ramdisk_size;
> +
> +#ifdef CONFIG_X86_64
> + ramdisk_end = PAGE_ALIGN(ramdisk_image + ramdisk_size);
> + initrd_start = ramdisk_image + PAGE_OFFSET;
> + initrd_end = initrd_start + ramdisk_size;
> + _load_ucode_intel_bsp(&mc_saved_data, mc_saved_in_initrd,
> + initrd_start, initrd_end);
> +#else
> + ramdisk_end = ramdisk_image + ramdisk_size;
> + initrd_start = ramdisk_image;
> + initrd_end = initrd_start + ramdisk_size;
> + _load_ucode_intel_bsp((struct mc_saved_data *)__pa(&mc_saved_data),
> + (struct microcode_intel **)__pa(mc_saved_in_initrd),
> + initrd_start, initrd_end);
> +#endif
> +}
> +
> +void __cpuinit load_ucode_intel_ap(void)
> +{
> + int cpu = smp_processor_id();
> +
> + /*
> + * If BSP doesn't find valid ucode and save it in memory, no need to
> + * update ucode on this AP.
> + */
> + if (!mc_saved_data.mc_saved)
> + return;
> +
> + collect_cpu_info_early(mc_saved_data.ucode_cpu_info + cpu);
> + load_microcode(&mc_saved_data, cpu);
> + apply_microcode_early(&mc_saved_data, cpu);
> +}

looks like this patches do not consider that initrd could be relocated
even 64bit.

may need to copy the ucode.bin to BRK at first. that will make code
much simple, and later does not need to
copy them back in free_bootmem_initrd.

aka, this patchset is not ready for 3.8 even.

Thanks

Yinghai
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/