[PATCH] x86: Early boot alternative instructions

From: Pekka Enberg
Date: Thu Mar 24 2011 - 13:59:35 EST


Commit 8a5ec0ba42c4919e2d8f4c3138cc8b987fdb0b79 ("Lockless (and preemptless)
fastpaths for slub") added use of cmpxchg16b in kmem_cache_init() which happens
early on during the boot. As cmpxchg16b is not supported on some older AMD
CPUs, there's a alternative_io() fallback for cmpxchg16b emulation.

Unfortunately alternative_instructions() happens late in the boot sequence so
the fallback code is not patched into kernel text which causes the following oops:

BUG: unable to handle kernel paging request at ffff87ffc147e020
IP: [<ffffffff811aa762>] this_cpu_cmpxchg16b_emu+0x2/0x1c

[<ffffffff810d9cbc>] ? kmem_cache_alloc+0x4c/0x110
[<ffffffff8151cf06>] kmem_cache_init+0xeb/0x2b0
[<ffffffff81504a06>] start_kernel+0x1de/0x49b
[<ffffffff8150432b>] x86_64_start_reservations+0x132/0x136
[<ffffffff81504140>] ? early_idt_handlers+0x140/0x140

This patch reorganizes the code so that alternative_io() tagged fallbacks are
patched to the kernel before kmem_cache_init() is called.

Reported-by: Ingo Molnar <mingo@xxxxxxx>
Signed-off-by: Pekka Enberg <penberg@xxxxxxxxxx>
---
arch/x86/include/asm/alternative.h | 1 +
arch/x86/kernel/alternative.c | 12 +++++++++++-
arch/x86/kernel/cpu/bugs.c | 2 --
arch/x86/kernel/cpu/bugs_64.c | 2 --
include/asm-generic/alternative.h | 19 +++++++++++++++++++
init/main.c | 3 +++
6 files changed, 34 insertions(+), 5 deletions(-)
create mode 100644 include/asm-generic/alternative.h

diff --git a/arch/x86/include/asm/alternative.h b/arch/x86/include/asm/alternative.h
index 13009d1..ca819c6 100644
--- a/arch/x86/include/asm/alternative.h
+++ b/arch/x86/include/asm/alternative.h
@@ -54,6 +54,7 @@ struct alt_instr {
#endif
};

+extern void alternative_instructions_early(void);
extern void alternative_instructions(void);
extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);

diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 4a23467..32e96dd 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -453,7 +453,7 @@ extern struct paravirt_patch_site __start_parainstructions[],
__stop_parainstructions[];
#endif /* CONFIG_PARAVIRT */

-void __init alternative_instructions(void)
+void __init alternative_instructions_early(void)
{
/* The patching is not fully atomic, so try to avoid local interruptions
that might execute the to be patched code.
@@ -473,6 +473,16 @@ void __init alternative_instructions(void)

apply_alternatives(__alt_instructions, __alt_instructions_end);

+ restart_nmi();
+}
+
+void __init alternative_instructions(void)
+{
+ /* The patching is not fully atomic, so try to avoid local interruptions
+ that might execute the to be patched code.
+ Other CPUs are not running. */
+ stop_nmi();
+
/* switch to patch-once-at-boottime-only mode and free the
* tables in case we know the number of CPUs will never ever
* change */
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index c39576c..3854e58 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -15,7 +15,6 @@
#include <asm/i387.h>
#include <asm/msr.h>
#include <asm/paravirt.h>
-#include <asm/alternative.h>

static int __init no_halt(char *s)
{
@@ -165,5 +164,4 @@ void __init check_bugs(void)
check_popad();
init_utsname()->machine[1] =
'0' + (boot_cpu_data.x86 > 6 ? 6 : boot_cpu_data.x86);
- alternative_instructions();
}
diff --git a/arch/x86/kernel/cpu/bugs_64.c b/arch/x86/kernel/cpu/bugs_64.c
index 04f0fe5..1064db5 100644
--- a/arch/x86/kernel/cpu/bugs_64.c
+++ b/arch/x86/kernel/cpu/bugs_64.c
@@ -5,7 +5,6 @@

#include <linux/kernel.h>
#include <linux/init.h>
-#include <asm/alternative.h>
#include <asm/bugs.h>
#include <asm/processor.h>
#include <asm/mtrr.h>
@@ -18,7 +17,6 @@ void __init check_bugs(void)
printk(KERN_INFO "CPU: ");
print_cpu_info(&boot_cpu_data);
#endif
- alternative_instructions();

/*
* Make sure the first 2MB area is not mapped by huge pages
diff --git a/include/asm-generic/alternative.h b/include/asm-generic/alternative.h
new file mode 100644
index 0000000..893a070
--- /dev/null
+++ b/include/asm-generic/alternative.h
@@ -0,0 +1,19 @@
+/*
+ * linux/include/asm-generic/alternative.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef __ASM_GENERIC_ALTERNATIVE_H__
+#define __ASM_GENERIC_ALTERNATIVE_H__
+
+static inline void alternative_instructions_early(void)
+{
+}
+
+static inline void alternative_instructions(void)
+{
+}
+
+#endif /* __ASM_GENERIC_ALTERNATIVE_H__ */
diff --git a/init/main.c b/init/main.c
index 4a9479e..0bb3c52 100644
--- a/init/main.c
+++ b/init/main.c
@@ -74,6 +74,7 @@
#include <asm/setup.h>
#include <asm/sections.h>
#include <asm/cacheflush.h>
+#include <asm/alternative.h>

#ifdef CONFIG_X86_LOCAL_APIC
#include <asm/smp.h>
@@ -508,6 +509,7 @@ asmlinkage void __init start_kernel(void)
vfs_caches_init_early();
sort_main_extable();
trap_init();
+ alternative_instructions_early();
mm_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
@@ -614,6 +616,7 @@ asmlinkage void __init start_kernel(void)
taskstats_init_early();
delayacct_init();

+ alternative_instructions();
check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */
--
1.7.0.4

--8323329-1280347503-1300990398=:4990--
--
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/