blob: 080a59d56ea69f2aa0e7c72ff1a02526b2044a1a [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>
5#include <asm/alternative.h>
6#include <asm/sections.h>
7
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +02008static int noreplace_smp = 0;
Gerd Hoffmannd167a512006-06-26 13:56:16 +02009static int smp_alt_once = 0;
10static int debug_alternative = 0;
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080011
Gerd Hoffmannd167a512006-06-26 13:56:16 +020012static int __init bootonly(char *str)
13{
14 smp_alt_once = 1;
15 return 1;
16}
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020017__setup("smp-alt-boot", bootonly);
18
Gerd Hoffmannd167a512006-06-26 13:56:16 +020019static int __init debug_alt(char *str)
20{
21 debug_alternative = 1;
22 return 1;
23}
Gerd Hoffmannd167a512006-06-26 13:56:16 +020024__setup("debug-alternative", debug_alt);
25
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020026static int __init setup_noreplace_smp(char *str)
27{
28 noreplace_smp = 1;
29 return 1;
30}
31__setup("noreplace-smp", setup_noreplace_smp);
32
Jeremy Fitzhardinge959b4fd2007-05-02 19:27:16 +020033#ifdef CONFIG_PARAVIRT
34static int noreplace_paravirt = 0;
35
36static int __init setup_noreplace_paravirt(char *str)
37{
38 noreplace_paravirt = 1;
39 return 1;
40}
41__setup("noreplace-paravirt", setup_noreplace_paravirt);
42#endif
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +020043
Gerd Hoffmannd167a512006-06-26 13:56:16 +020044#define DPRINTK(fmt, args...) if (debug_alternative) \
45 printk(KERN_DEBUG fmt, args)
46
47#ifdef GENERIC_NOP1
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080048/* Use inline assembly to define this because the nops are defined
49 as inline assembly strings in the include files and we cannot
50 get them easily into strings. */
51asm("\t.data\nintelnops: "
52 GENERIC_NOP1 GENERIC_NOP2 GENERIC_NOP3 GENERIC_NOP4 GENERIC_NOP5 GENERIC_NOP6
53 GENERIC_NOP7 GENERIC_NOP8);
Gerd Hoffmannd167a512006-06-26 13:56:16 +020054extern unsigned char intelnops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080055static unsigned char *intel_nops[ASM_NOP_MAX+1] = {
56 NULL,
57 intelnops,
58 intelnops + 1,
59 intelnops + 1 + 2,
60 intelnops + 1 + 2 + 3,
61 intelnops + 1 + 2 + 3 + 4,
62 intelnops + 1 + 2 + 3 + 4 + 5,
63 intelnops + 1 + 2 + 3 + 4 + 5 + 6,
64 intelnops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
65};
Gerd Hoffmannd167a512006-06-26 13:56:16 +020066#endif
67
68#ifdef K8_NOP1
69asm("\t.data\nk8nops: "
70 K8_NOP1 K8_NOP2 K8_NOP3 K8_NOP4 K8_NOP5 K8_NOP6
71 K8_NOP7 K8_NOP8);
72extern unsigned char k8nops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080073static unsigned char *k8_nops[ASM_NOP_MAX+1] = {
74 NULL,
75 k8nops,
76 k8nops + 1,
77 k8nops + 1 + 2,
78 k8nops + 1 + 2 + 3,
79 k8nops + 1 + 2 + 3 + 4,
80 k8nops + 1 + 2 + 3 + 4 + 5,
81 k8nops + 1 + 2 + 3 + 4 + 5 + 6,
82 k8nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
83};
Gerd Hoffmannd167a512006-06-26 13:56:16 +020084#endif
85
86#ifdef K7_NOP1
87asm("\t.data\nk7nops: "
88 K7_NOP1 K7_NOP2 K7_NOP3 K7_NOP4 K7_NOP5 K7_NOP6
89 K7_NOP7 K7_NOP8);
90extern unsigned char k7nops[];
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -080091static unsigned char *k7_nops[ASM_NOP_MAX+1] = {
92 NULL,
93 k7nops,
94 k7nops + 1,
95 k7nops + 1 + 2,
96 k7nops + 1 + 2 + 3,
97 k7nops + 1 + 2 + 3 + 4,
98 k7nops + 1 + 2 + 3 + 4 + 5,
99 k7nops + 1 + 2 + 3 + 4 + 5 + 6,
100 k7nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
101};
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200102#endif
103
104#ifdef CONFIG_X86_64
105
106extern char __vsyscall_0;
107static inline unsigned char** find_nop_table(void)
108{
109 return k8_nops;
110}
111
112#else /* CONFIG_X86_64 */
113
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800114static struct nop {
115 int cpuid;
116 unsigned char **noptable;
117} noptypes[] = {
118 { X86_FEATURE_K8, k8_nops },
119 { X86_FEATURE_K7, k7_nops },
120 { -1, NULL }
121};
122
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800123static unsigned char** find_nop_table(void)
124{
125 unsigned char **noptable = intel_nops;
126 int i;
127
128 for (i = 0; noptypes[i].cpuid >= 0; i++) {
129 if (boot_cpu_has(noptypes[i].cpuid)) {
130 noptable = noptypes[i].noptable;
131 break;
132 }
133 }
134 return noptable;
135}
136
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200137#endif /* CONFIG_X86_64 */
138
Rusty Russell139ec7c2006-12-07 02:14:08 +0100139static void nop_out(void *insns, unsigned int len)
140{
141 unsigned char **noptable = find_nop_table();
142
143 while (len > 0) {
144 unsigned int noplen = len;
145 if (noplen > ASM_NOP_MAX)
146 noplen = ASM_NOP_MAX;
147 memcpy(insns, noptable[noplen], noplen);
148 insns += noplen;
149 len -= noplen;
150 }
151}
152
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200153extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200154extern u8 *__smp_locks[], *__smp_locks_end[];
155
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800156/* Replace instructions with better alternatives for this CPU type.
157 This runs before SMP is initialized to avoid SMP problems with
158 self modifying code. This implies that assymetric systems where
159 APs have less capabilities than the boot processor are not handled.
160 Tough. Make sure you disable such features by hand. */
161
162void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
163{
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800164 struct alt_instr *a;
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200165 u8 *instr;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100166 int diff;
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800167
168 DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end);
169 for (a = start; a < end; a++) {
170 BUG_ON(a->replacementlen > a->instrlen);
171 if (!boot_cpu_has(a->cpuid))
172 continue;
Gerd Hoffmannd167a512006-06-26 13:56:16 +0200173 instr = a->instr;
174#ifdef CONFIG_X86_64
175 /* vsyscall code is not mapped yet. resolve it manually. */
176 if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) {
177 instr = __va(instr - (u8*)VSYSCALL_START + (u8*)__pa_symbol(&__vsyscall_0));
178 DPRINTK("%s: vsyscall fixup: %p => %p\n",
179 __FUNCTION__, a->instr, instr);
180 }
181#endif
182 memcpy(instr, a->replacement, a->replacementlen);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800183 diff = a->instrlen - a->replacementlen;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100184 nop_out(instr + a->replacementlen, diff);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800185 }
186}
187
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700188#ifdef CONFIG_SMP
189
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800190static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end)
191{
192 u8 **ptr;
193
194 for (ptr = start; ptr < end; ptr++) {
195 if (*ptr < text)
196 continue;
197 if (*ptr > text_end)
198 continue;
199 **ptr = 0xf0; /* lock prefix */
200 };
201}
202
203static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end)
204{
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800205 u8 **ptr;
206
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200207 if (noreplace_smp)
208 return;
209
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800210 for (ptr = start; ptr < end; ptr++) {
211 if (*ptr < text)
212 continue;
213 if (*ptr > text_end)
214 continue;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100215 nop_out(*ptr, 1);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800216 };
217}
218
219struct smp_alt_module {
220 /* what is this ??? */
221 struct module *mod;
222 char *name;
223
224 /* ptrs to lock prefixes */
225 u8 **locks;
226 u8 **locks_end;
227
228 /* .text segment, needed to avoid patching init code ;) */
229 u8 *text;
230 u8 *text_end;
231
232 struct list_head next;
233};
234static LIST_HEAD(smp_alt_modules);
235static DEFINE_SPINLOCK(smp_alt);
236
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800237void alternatives_smp_module_add(struct module *mod, char *name,
238 void *locks, void *locks_end,
239 void *text, void *text_end)
240{
241 struct smp_alt_module *smp;
242 unsigned long flags;
243
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200244 if (noreplace_smp)
245 return;
246
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800247 if (smp_alt_once) {
248 if (boot_cpu_has(X86_FEATURE_UP))
249 alternatives_smp_unlock(locks, locks_end,
250 text, text_end);
251 return;
252 }
253
254 smp = kzalloc(sizeof(*smp), GFP_KERNEL);
255 if (NULL == smp)
256 return; /* we'll run the (safe but slow) SMP code then ... */
257
258 smp->mod = mod;
259 smp->name = name;
260 smp->locks = locks;
261 smp->locks_end = locks_end;
262 smp->text = text;
263 smp->text_end = text_end;
264 DPRINTK("%s: locks %p -> %p, text %p -> %p, name %s\n",
265 __FUNCTION__, smp->locks, smp->locks_end,
266 smp->text, smp->text_end, smp->name);
267
268 spin_lock_irqsave(&smp_alt, flags);
269 list_add_tail(&smp->next, &smp_alt_modules);
270 if (boot_cpu_has(X86_FEATURE_UP))
271 alternatives_smp_unlock(smp->locks, smp->locks_end,
272 smp->text, smp->text_end);
273 spin_unlock_irqrestore(&smp_alt, flags);
274}
275
276void alternatives_smp_module_del(struct module *mod)
277{
278 struct smp_alt_module *item;
279 unsigned long flags;
280
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200281 if (smp_alt_once || noreplace_smp)
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800282 return;
283
284 spin_lock_irqsave(&smp_alt, flags);
285 list_for_each_entry(item, &smp_alt_modules, next) {
286 if (mod != item->mod)
287 continue;
288 list_del(&item->next);
289 spin_unlock_irqrestore(&smp_alt, flags);
290 DPRINTK("%s: %s\n", __FUNCTION__, item->name);
291 kfree(item);
292 return;
293 }
294 spin_unlock_irqrestore(&smp_alt, flags);
295}
296
297void alternatives_smp_switch(int smp)
298{
299 struct smp_alt_module *mod;
300 unsigned long flags;
301
Ingo Molnar3047e992006-07-03 00:24:57 -0700302#ifdef CONFIG_LOCKDEP
303 /*
304 * A not yet fixed binutils section handling bug prevents
305 * alternatives-replacement from working reliably, so turn
306 * it off:
307 */
308 printk("lockdep: not fixing up alternatives.\n");
309 return;
310#endif
311
Jeremy Fitzhardingeb7fb4af2007-05-02 19:27:13 +0200312 if (noreplace_smp || smp_alt_once)
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800313 return;
314 BUG_ON(!smp && (num_online_cpus() > 1));
315
316 spin_lock_irqsave(&smp_alt, flags);
317 if (smp) {
318 printk(KERN_INFO "SMP alternatives: switching to SMP code\n");
319 clear_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
320 clear_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800321 list_for_each_entry(mod, &smp_alt_modules, next)
322 alternatives_smp_lock(mod->locks, mod->locks_end,
323 mod->text, mod->text_end);
324 } else {
325 printk(KERN_INFO "SMP alternatives: switching to UP code\n");
326 set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
327 set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800328 list_for_each_entry(mod, &smp_alt_modules, next)
329 alternatives_smp_unlock(mod->locks, mod->locks_end,
330 mod->text, mod->text_end);
331 }
332 spin_unlock_irqrestore(&smp_alt, flags);
333}
334
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700335#endif
336
Rusty Russell139ec7c2006-12-07 02:14:08 +0100337#ifdef CONFIG_PARAVIRT
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200338void apply_paravirt(struct paravirt_patch_site *start,
339 struct paravirt_patch_site *end)
Rusty Russell139ec7c2006-12-07 02:14:08 +0100340{
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200341 struct paravirt_patch_site *p;
Rusty Russell139ec7c2006-12-07 02:14:08 +0100342
Jeremy Fitzhardinge959b4fd2007-05-02 19:27:16 +0200343 if (noreplace_paravirt)
344 return;
345
Rusty Russell139ec7c2006-12-07 02:14:08 +0100346 for (p = start; p < end; p++) {
347 unsigned int used;
348
349 used = paravirt_ops.patch(p->instrtype, p->clobbers, p->instr,
350 p->len);
Jeremy Fitzhardinge7f63c412007-05-02 19:27:13 +0200351
Jeremy Fitzhardinge63f70272007-05-02 19:27:14 +0200352 BUG_ON(used > p->len);
353
Rusty Russell139ec7c2006-12-07 02:14:08 +0100354 /* Pad the rest with nops */
355 nop_out(p->instr + used, p->len - used);
356 }
357
Jeremy Fitzhardinge63f70272007-05-02 19:27:14 +0200358 /* Sync to be conservative, in case we patched following
359 * instructions */
Rusty Russell139ec7c2006-12-07 02:14:08 +0100360 sync_core();
361}
Jeremy Fitzhardinge98de0322007-05-02 19:27:14 +0200362extern struct paravirt_patch_site __start_parainstructions[],
Rusty Russell139ec7c2006-12-07 02:14:08 +0100363 __stop_parainstructions[];
364#endif /* CONFIG_PARAVIRT */
365
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800366void __init alternative_instructions(void)
367{
Zachary Amsdene51959f2006-10-19 23:29:04 -0700368 unsigned long flags;
Zachary Amsdene51959f2006-10-19 23:29:04 -0700369
370 local_irq_save(flags);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800371 apply_alternatives(__alt_instructions, __alt_instructions_end);
372
373 /* switch to patch-once-at-boottime-only mode and free the
374 * tables in case we know the number of CPUs will never ever
375 * change */
376#ifdef CONFIG_HOTPLUG_CPU
377 if (num_possible_cpus() < 2)
378 smp_alt_once = 1;
379#else
380 smp_alt_once = 1;
381#endif
382
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700383#ifdef CONFIG_SMP
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800384 if (smp_alt_once) {
385 if (1 == num_possible_cpus()) {
386 printk(KERN_INFO "SMP alternatives: switching to UP code\n");
387 set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
388 set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800389 alternatives_smp_unlock(__smp_locks, __smp_locks_end,
390 _text, _etext);
391 }
392 free_init_pages("SMP alternatives",
Jeremy Fitzhardinged0175ab2007-05-02 19:27:13 +0200393 __pa_symbol(&__smp_locks),
394 __pa_symbol(&__smp_locks_end));
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800395 } else {
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800396 alternatives_smp_module_add(NULL, "core kernel",
397 __smp_locks, __smp_locks_end,
398 _text, _etext);
399 alternatives_smp_switch(0);
400 }
Gerd Hoffmann8ec4d412006-07-01 04:36:18 -0700401#endif
Rusty Russell139ec7c2006-12-07 02:14:08 +0100402 apply_paravirt(__start_parainstructions, __stop_parainstructions);
Zachary Amsdene51959f2006-10-19 23:29:04 -0700403 local_irq_restore(flags);
Gerd Hoffmann9a0b5812006-03-23 02:59:32 -0800404}