blob: c85598acb8fd5854394dd4aa8e16877bd5c01848 [file] [log] [blame]
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -08001#include <linux/module.h>
Al Virof6a57032006-10-18 01:47:25 -04002#include <linux/sched.h>
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -08003#include <linux/spinlock.h>
4#include <linux/list.h>
Andi Kleen19d36cc2007-07-22 11:12:31 +02005#include <linux/kprobes.h>
6#include <linux/mm.h>
7#include <linux/vmalloc.h>
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -08008#include <asm/alternative.h>
9#include <asm/sections.h>
Andi Kleen19d36cc2007-07-22 11:12:31 +020010#include <asm/pgtable.h>
Andi Kleen8f4e9562007-07-22 11:12:32 +020011#include <asm/mce.h>
12#include <asm/nmi.h>
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080013
Jan Beulich09488162007-07-21 17:10:25 +020014#ifdef CONFIG_HOTPLUG_CPU
15static int smp_alt_once;
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080016
Gerd Hoffmannd167a512006-06-26 13:56:16 +020017static int __init bootonly(char *str)
18{
19 smp_alt_once = 1;
20 return 1;
21}
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020022__setup("smp-alt-boot", bootonly);
Jan Beulich09488162007-07-21 17:10:25 +020023#else
24#define smp_alt_once 1
25#endif
26
27static int debug_alternative;
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020028
Gerd Hoffmannd167a512006-06-26 13:56:16 +020029static int __init debug_alt(char *str)
30{
31 debug_alternative = 1;
32 return 1;
33}
Gerd Hoffmannd167a512006-06-26 13:56:16 +020034__setup("debug-alternative", debug_alt);
35
Jan Beulich09488162007-07-21 17:10:25 +020036static int noreplace_smp;
37
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020038static int __init setup_noreplace_smp(char *str)
39{
40 noreplace_smp = 1;
41 return 1;
42}
43__setup("noreplace-smp", setup_noreplace_smp);
44
Jeremy Fitzhardinge959b4fd2007-05-02 19:27:16 +020045#ifdef CONFIG_PARAVIRT
46static int noreplace_paravirt = 0;
47
48static int __init setup_noreplace_paravirt(char *str)
49{
50 noreplace_paravirt = 1;
51 return 1;
52}
53__setup("noreplace-paravirt", setup_noreplace_paravirt);
54#endif
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020055
Gerd Hoffmannd167a512006-06-26 13:56:16 +020056#define DPRINTK(fmt, args...) if (debug_alternative) \
57 printk(KERN_DEBUG fmt, args)
58
59#ifdef GENERIC_NOP1
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080060/* Use inline assembly to define this because the nops are defined
61 as inline assembly strings in the include files and we cannot
62 get them easily into strings. */
63asm("\t.data\nintelnops: "
64 GENERIC_NOP1 GENERIC_NOP2 GENERIC_NOP3 GENERIC_NOP4 GENERIC_NOP5 GENERIC_NOP6
65 GENERIC_NOP7 GENERIC_NOP8);
Gerd Hoffmannd167a512006-06-26 13:56:16 +020066extern unsigned char intelnops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080067static unsigned char *intel_nops[ASM_NOP_MAX+1] = {
68 NULL,
69 intelnops,
70 intelnops + 1,
71 intelnops + 1 + 2,
72 intelnops + 1 + 2 + 3,
73 intelnops + 1 + 2 + 3 + 4,
74 intelnops + 1 + 2 + 3 + 4 + 5,
75 intelnops + 1 + 2 + 3 + 4 + 5 + 6,
76 intelnops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
77};
Gerd Hoffmannd167a512006-06-26 13:56:16 +020078#endif
79
80#ifdef K8_NOP1
81asm("\t.data\nk8nops: "
82 K8_NOP1 K8_NOP2 K8_NOP3 K8_NOP4 K8_NOP5 K8_NOP6
83 K8_NOP7 K8_NOP8);
84extern unsigned char k8nops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080085static unsigned char *k8_nops[ASM_NOP_MAX+1] = {
86 NULL,
87 k8nops,
88 k8nops + 1,
89 k8nops + 1 + 2,
90 k8nops + 1 + 2 + 3,
91 k8nops + 1 + 2 + 3 + 4,
92 k8nops + 1 + 2 + 3 + 4 + 5,
93 k8nops + 1 + 2 + 3 + 4 + 5 + 6,
94 k8nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
95};
Gerd Hoffmannd167a512006-06-26 13:56:16 +020096#endif
97
98#ifdef K7_NOP1
99asm("\t.data\nk7nops: "
100 K7_NOP1 K7_NOP2 K7_NOP3 K7_NOP4 K7_NOP5 K7_NOP6
101 K7_NOP7 K7_NOP8);
102extern unsigned char k7nops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800103static unsigned char *k7_nops[ASM_NOP_MAX+1] = {
104 NULL,
105 k7nops,
106 k7nops + 1,
107 k7nops + 1 + 2,
108 k7nops + 1 + 2 + 3,
109 k7nops + 1 + 2 + 3 + 4,
110 k7nops + 1 + 2 + 3 + 4 + 5,
111 k7nops + 1 + 2 + 3 + 4 + 5 + 6,
112 k7nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
113};
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200114#endif
115
116#ifdef CONFIG_X86_64
117
118extern char __vsyscall_0;
119static inline unsigned char** find_nop_table(void)
120{
121 return k8_nops;
122}
123
124#else /* CONFIG_X86_64 */
125
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800126static struct nop {
127 int cpuid;
128 unsigned char **noptable;
129} noptypes[] = {
130 { X86_FEATURE_K8, k8_nops },
131 { X86_FEATURE_K7, k7_nops },
132 { -1, NULL }
133};
134
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800135static unsigned char** find_nop_table(void)
136{
137 unsigned char **noptable = intel_nops;
138 int i;
139
140 for (i = 0; noptypes[i].cpuid >= 0; i++) {
141 if (boot_cpu_has(noptypes[i].cpuid)) {
142 noptable = noptypes[i].noptable;
143 break;
144 }
145 }
146 return noptable;
147}
148
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200149#endif /* CONFIG_X86_64 */
150
Rusty Russell139ec7c2006-12-07 02:14:08 +0100151static void nop_out(void *insns, unsigned int len)
152{
153 unsigned char **noptable = find_nop_table();
154
155 while (len > 0) {
156 unsigned int noplen = len;
157 if (noplen > ASM_NOP_MAX)
158 noplen = ASM_NOP_MAX;
Andi Kleen19d36cc2007-07-22 11:12:31 +0200159 text_poke(insns, noptable[noplen], noplen);
Rusty Russell139ec7c2006-12-07 02:14:08 +0100160 insns += noplen;
161 len -= noplen;
162 }
163}
164
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200165extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200166extern u8 *__smp_locks[], *__smp_locks_end[];
167
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800168/* Replace instructions with better alternatives for this CPU type.
169 This runs before SMP is initialized to avoid SMP problems with
170 self modifying code. This implies that assymetric systems where
171 APs have less capabilities than the boot processor are not handled.
172 Tough. Make sure you disable such features by hand. */
173
174void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
175{
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800176 struct alt_instr *a;
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200177 u8 *instr;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100178 int diff;
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800179
180 DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end);
181 for (a = start; a < end; a++) {
182 BUG_ON(a->replacementlen > a->instrlen);
183 if (!boot_cpu_has(a->cpuid))
184 continue;
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200185 instr = a->instr;
186#ifdef CONFIG_X86_64
187 /* vsyscall code is not mapped yet. resolve it manually. */
188 if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) {
189 instr = __va(instr - (u8*)VSYSCALL_START + (u8*)__pa_symbol(&__vsyscall_0));
190 DPRINTK("%s: vsyscall fixup: %p => %p\n",
191 __FUNCTION__, a->instr, instr);
192 }
193#endif
194 memcpy(instr, a->replacement, a->replacementlen);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800195 diff = a->instrlen - a->replacementlen;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100196 nop_out(instr + a->replacementlen, diff);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800197 }
198}
199
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700200#ifdef CONFIG_SMP
201
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800202static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end)
203{
204 u8 **ptr;
205
206 for (ptr = start; ptr < end; ptr++) {
207 if (*ptr < text)
208 continue;
209 if (*ptr > text_end)
210 continue;
Andi Kleen19d36cc2007-07-22 11:12:31 +0200211 text_poke(*ptr, ((unsigned char []){0xf0}), 1); /* add lock prefix */
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800212 };
213}
214
215static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end)
216{
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800217 u8 **ptr;
218
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200219 if (noreplace_smp)
220 return;
221
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800222 for (ptr = start; ptr < end; ptr++) {
223 if (*ptr < text)
224 continue;
225 if (*ptr > text_end)
226 continue;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100227 nop_out(*ptr, 1);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800228 };
229}
230
231struct smp_alt_module {
232 /* what is this ??? */
233 struct module *mod;
234 char *name;
235
236 /* ptrs to lock prefixes */
237 u8 **locks;
238 u8 **locks_end;
239
240 /* .text segment, needed to avoid patching init code ;) */
241 u8 *text;
242 u8 *text_end;
243
244 struct list_head next;
245};
246static LIST_HEAD(smp_alt_modules);
247static DEFINE_SPINLOCK(smp_alt);
248
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800249void alternatives_smp_module_add(struct module *mod, char *name,
250 void *locks, void *locks_end,
251 void *text, void *text_end)
252{
253 struct smp_alt_module *smp;
254 unsigned long flags;
255
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200256 if (noreplace_smp)
257 return;
258
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800259 if (smp_alt_once) {
260 if (boot_cpu_has(X86_FEATURE_UP))
261 alternatives_smp_unlock(locks, locks_end,
262 text, text_end);
263 return;
264 }
265
266 smp = kzalloc(sizeof(*smp), GFP_KERNEL);
267 if (NULL == smp)
268 return; /* we'll run the (safe but slow) SMP code then ... */
269
270 smp->mod = mod;
271 smp->name = name;
272 smp->locks = locks;
273 smp->locks_end = locks_end;
274 smp->text = text;
275 smp->text_end = text_end;
276 DPRINTK("%s: locks %p -> %p, text %p -> %p, name %s\n",
277 __FUNCTION__, smp->locks, smp->locks_end,
278 smp->text, smp->text_end, smp->name);
279
280 spin_lock_irqsave(&smp_alt, flags);
281 list_add_tail(&smp->next, &smp_alt_modules);
282 if (boot_cpu_has(X86_FEATURE_UP))
283 alternatives_smp_unlock(smp->locks, smp->locks_end,
284 smp->text, smp->text_end);
285 spin_unlock_irqrestore(&smp_alt, flags);
286}
287
288void alternatives_smp_module_del(struct module *mod)
289{
290 struct smp_alt_module *item;
291 unsigned long flags;
292
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200293 if (smp_alt_once || noreplace_smp)
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800294 return;
295
296 spin_lock_irqsave(&smp_alt, flags);
297 list_for_each_entry(item, &smp_alt_modules, next) {
298 if (mod != item->mod)
299 continue;
300 list_del(&item->next);
301 spin_unlock_irqrestore(&smp_alt, flags);
302 DPRINTK("%s: %s\n", __FUNCTION__, item->name);
303 kfree(item);
304 return;
305 }
306 spin_unlock_irqrestore(&smp_alt, flags);
307}
308
309void alternatives_smp_switch(int smp)
310{
311 struct smp_alt_module *mod;
312 unsigned long flags;
313
Ingo Molnar3047e992006-07-03 00:24:57 -0700314#ifdef CONFIG_LOCKDEP
315 /*
316 * A not yet fixed binutils section handling bug prevents
317 * alternatives-replacement from working reliably, so turn
318 * it off:
319 */
320 printk("lockdep: not fixing up alternatives.\n");
321 return;
322#endif
323
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200324 if (noreplace_smp || smp_alt_once)
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800325 return;
326 BUG_ON(!smp && (num_online_cpus() > 1));
327
328 spin_lock_irqsave(&smp_alt, flags);
329 if (smp) {
330 printk(KERN_INFO "SMP alternatives: switching to SMP code\n");
331 clear_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
332 clear_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800333 list_for_each_entry(mod, &smp_alt_modules, next)
334 alternatives_smp_lock(mod->locks, mod->locks_end,
335 mod->text, mod->text_end);
336 } else {
337 printk(KERN_INFO "SMP alternatives: switching to UP code\n");
338 set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
339 set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800340 list_for_each_entry(mod, &smp_alt_modules, next)
341 alternatives_smp_unlock(mod->locks, mod->locks_end,
342 mod->text, mod->text_end);
343 }
344 spin_unlock_irqrestore(&smp_alt, flags);
345}
346
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700347#endif
348
Rusty Russell139ec7c2006-12-07 02:14:08 +0100349#ifdef CONFIG_PARAVIRT
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200350void apply_paravirt(struct paravirt_patch_site *start,
351 struct paravirt_patch_site *end)
Rusty Russell139ec7c2006-12-07 02:14:08 +0100352{
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200353 struct paravirt_patch_site *p;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100354
Jeremy Fitzhardinge959b4fd2007-05-02 19:27:16 +0200355 if (noreplace_paravirt)
356 return;
357
Rusty Russell139ec7c2006-12-07 02:14:08 +0100358 for (p = start; p < end; p++) {
359 unsigned int used;
360
361 used = paravirt_ops.patch(p->instrtype, p->clobbers, p->instr,
362 p->len);
Jeremy Fitzhardinge7f63c412007-05-02 19:27:13 +0200363
Jeremy Fitzhardinge63f70272007-05-02 19:27:14 +0200364 BUG_ON(used > p->len);
365
Rusty Russell139ec7c2006-12-07 02:14:08 +0100366 /* Pad the rest with nops */
367 nop_out(p->instr + used, p->len - used);
368 }
Rusty Russell139ec7c2006-12-07 02:14:08 +0100369}
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200370extern struct paravirt_patch_site __start_parainstructions[],
Rusty Russell139ec7c2006-12-07 02:14:08 +0100371 __stop_parainstructions[];
372#endif /* CONFIG_PARAVIRT */
373
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800374void __init alternative_instructions(void)
375{
Zachary Amsdene51959f2006-10-19 23:29:04 -0700376 unsigned long flags;
Zachary Amsdene51959f2006-10-19 23:29:04 -0700377
Andi Kleen8f4e9562007-07-22 11:12:32 +0200378 /* The patching is not fully atomic, so try to avoid local interruptions
379 that might execute the to be patched code.
380 Other CPUs are not running. */
381 stop_nmi();
382#ifdef CONFIG_MCE
383 stop_mce();
384#endif
385
Zachary Amsdene51959f2006-10-19 23:29:04 -0700386 local_irq_save(flags);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800387 apply_alternatives(__alt_instructions, __alt_instructions_end);
388
389 /* switch to patch-once-at-boottime-only mode and free the
390 * tables in case we know the number of CPUs will never ever
391 * change */
392#ifdef CONFIG_HOTPLUG_CPU
393 if (num_possible_cpus() < 2)
394 smp_alt_once = 1;
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800395#endif
396
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700397#ifdef CONFIG_SMP
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800398 if (smp_alt_once) {
399 if (1 == num_possible_cpus()) {
400 printk(KERN_INFO "SMP alternatives: switching to UP code\n");
401 set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
402 set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800403 alternatives_smp_unlock(__smp_locks, __smp_locks_end,
404 _text, _etext);
405 }
406 free_init_pages("SMP alternatives",
Linus Torvaldse3ebadd2007-05-07 08:44:24 -0700407 (unsigned long)__smp_locks,
408 (unsigned long)__smp_locks_end);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800409 } else {
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800410 alternatives_smp_module_add(NULL, "core kernel",
411 __smp_locks, __smp_locks_end,
412 _text, _etext);
413 alternatives_smp_switch(0);
414 }
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700415#endif
Jeremy Fitzhardinge441d40d2007-05-02 19:27:16 +0200416 apply_paravirt(__parainstructions, __parainstructions_end);
Zachary Amsdene51959f2006-10-19 23:29:04 -0700417 local_irq_restore(flags);
Andi Kleen8f4e9562007-07-22 11:12:32 +0200418
419 restart_nmi();
420#ifdef CONFIG_MCE
421 restart_mce();
422#endif
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800423}
Andi Kleen19d36cc2007-07-22 11:12:31 +0200424
425/*
426 * Warning:
427 * When you use this code to patch more than one byte of an instruction
428 * you need to make sure that other CPUs cannot execute this code in parallel.
429 * Also no thread must be currently preempted in the middle of these instructions.
430 * And on the local CPU you need to be protected again NMI or MCE handlers
431 * seeing an inconsistent instruction while you patch.
432 */
Linus Torvalds602033e2007-07-26 12:07:21 -0700433void __kprobes text_poke(void *addr, unsigned char *opcode, int len)
Andi Kleen19d36cc2007-07-22 11:12:31 +0200434{
Andi Kleen19d36cc2007-07-22 11:12:31 +0200435 memcpy(addr, opcode, len);
436 sync_core();
437 /* Not strictly needed, but can speed CPU recovery up. Ignore cross cacheline
438 case. */
439 if (cpu_has_clflush)
Linus Torvalds602033e2007-07-26 12:07:21 -0700440 asm("clflush (%0) " :: "r" (addr) : "memory");
Andi Kleen19d36cc2007-07-22 11:12:31 +0200441}