Merge to Fedora kernel-2.6.18-1.2224_FC5 patched with stable patch-2.6.18.1-vs2.0...
[linux-2.6.git] / arch / i386 / kernel / alternative.c
1 #include <linux/module.h>
2 #include <linux/spinlock.h>
3 #include <linux/list.h>
4 #include <asm/alternative.h>
5 #include <asm/sections.h>
6
7 #ifdef CONFIG_X86_64_XEN
8 static int no_replacement    = 1;
9 #else
10 static int no_replacement    = 0;
11 #endif
12 static int smp_alt_once      = 0;
13 static int debug_alternative = 0;
14
15 static int __init noreplacement_setup(char *s)
16 {
17         no_replacement = 1;
18         return 1;
19 }
20 static int __init bootonly(char *str)
21 {
22         smp_alt_once = 1;
23         return 1;
24 }
25 static int __init debug_alt(char *str)
26 {
27         debug_alternative = 1;
28         return 1;
29 }
30
31 __setup("noreplacement", noreplacement_setup);
32 __setup("smp-alt-boot", bootonly);
33 __setup("debug-alternative", debug_alt);
34
35 #define DPRINTK(fmt, args...) if (debug_alternative) \
36         printk(KERN_DEBUG fmt, args)
37
38 #ifdef GENERIC_NOP1
39 /* Use inline assembly to define this because the nops are defined
40    as inline assembly strings in the include files and we cannot
41    get them easily into strings. */
42 asm("\t.data\nintelnops: "
43         GENERIC_NOP1 GENERIC_NOP2 GENERIC_NOP3 GENERIC_NOP4 GENERIC_NOP5 GENERIC_NOP6
44         GENERIC_NOP7 GENERIC_NOP8);
45 extern unsigned char intelnops[];
46 static unsigned char *intel_nops[ASM_NOP_MAX+1] = {
47         NULL,
48         intelnops,
49         intelnops + 1,
50         intelnops + 1 + 2,
51         intelnops + 1 + 2 + 3,
52         intelnops + 1 + 2 + 3 + 4,
53         intelnops + 1 + 2 + 3 + 4 + 5,
54         intelnops + 1 + 2 + 3 + 4 + 5 + 6,
55         intelnops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
56 };
57 #endif
58
59 #ifdef K8_NOP1
60 asm("\t.data\nk8nops: "
61         K8_NOP1 K8_NOP2 K8_NOP3 K8_NOP4 K8_NOP5 K8_NOP6
62         K8_NOP7 K8_NOP8);
63 extern unsigned char k8nops[];
64 static unsigned char *k8_nops[ASM_NOP_MAX+1] = {
65         NULL,
66         k8nops,
67         k8nops + 1,
68         k8nops + 1 + 2,
69         k8nops + 1 + 2 + 3,
70         k8nops + 1 + 2 + 3 + 4,
71         k8nops + 1 + 2 + 3 + 4 + 5,
72         k8nops + 1 + 2 + 3 + 4 + 5 + 6,
73         k8nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
74 };
75 #endif
76
77 #ifdef K7_NOP1
78 asm("\t.data\nk7nops: "
79         K7_NOP1 K7_NOP2 K7_NOP3 K7_NOP4 K7_NOP5 K7_NOP6
80         K7_NOP7 K7_NOP8);
81 extern unsigned char k7nops[];
82 static unsigned char *k7_nops[ASM_NOP_MAX+1] = {
83         NULL,
84         k7nops,
85         k7nops + 1,
86         k7nops + 1 + 2,
87         k7nops + 1 + 2 + 3,
88         k7nops + 1 + 2 + 3 + 4,
89         k7nops + 1 + 2 + 3 + 4 + 5,
90         k7nops + 1 + 2 + 3 + 4 + 5 + 6,
91         k7nops + 1 + 2 + 3 + 4 + 5 + 6 + 7,
92 };
93 #endif
94
95 #ifdef CONFIG_X86_64
96
97 extern char __vsyscall_0;
98 static inline unsigned char** find_nop_table(void)
99 {
100         return k8_nops;
101 }
102
103 #else /* CONFIG_X86_64 */
104
105 static struct nop {
106         int cpuid;
107         unsigned char **noptable;
108 } noptypes[] = {
109         { X86_FEATURE_K8, k8_nops },
110         { X86_FEATURE_K7, k7_nops },
111         { -1, NULL }
112 };
113
114 static unsigned char** find_nop_table(void)
115 {
116         unsigned char **noptable = intel_nops;
117         int i;
118
119         for (i = 0; noptypes[i].cpuid >= 0; i++) {
120                 if (boot_cpu_has(noptypes[i].cpuid)) {
121                         noptable = noptypes[i].noptable;
122                         break;
123                 }
124         }
125         return noptable;
126 }
127
128 #endif /* CONFIG_X86_64 */
129
130 extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
131 extern struct alt_instr __smp_alt_instructions[], __smp_alt_instructions_end[];
132 extern u8 *__smp_locks[], *__smp_locks_end[];
133
134 extern u8 __smp_alt_begin[], __smp_alt_end[];
135
136 /* Replace instructions with better alternatives for this CPU type.
137    This runs before SMP is initialized to avoid SMP problems with
138    self modifying code. This implies that assymetric systems where
139    APs have less capabilities than the boot processor are not handled.
140    Tough. Make sure you disable such features by hand. */
141
142 void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
143 {
144         unsigned char **noptable = find_nop_table();
145         struct alt_instr *a;
146         u8 *instr;
147         int diff, i, k;
148
149         DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end);
150         for (a = start; a < end; a++) {
151                 BUG_ON(a->replacementlen > a->instrlen);
152                 if (!boot_cpu_has(a->cpuid))
153                         continue;
154                 instr = a->instr;
155 #ifdef CONFIG_X86_64
156                 /* vsyscall code is not mapped yet. resolve it manually. */
157                 if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) {
158 #ifdef CONFIG_XEN
159                         instr = __va(instr - (u8*)VSYSCALL_START + (u8*)phys_to_machine(__pa_symbol(&__vsyscall_0)));
160 #else
161                         instr = __va(instr - (u8*)VSYSCALL_START + (u8*)__pa_symbol(&__vsyscall_0));
162 #endif
163                         DPRINTK("%s: vsyscall fixup: %p => %p\n",
164                                 __FUNCTION__, a->instr, instr);
165                 }
166 #endif
167                 memcpy(instr, a->replacement, a->replacementlen);
168                 diff = a->instrlen - a->replacementlen;
169                 /* Pad the rest with nops */
170                 for (i = a->replacementlen; diff > 0; diff -= k, i += k) {
171                         k = diff;
172                         if (k > ASM_NOP_MAX)
173                                 k = ASM_NOP_MAX;
174                         memcpy(a->instr + i, noptable[k], k);
175                 }
176         }
177 }
178
179 #ifdef CONFIG_SMP
180
181 static void alternatives_smp_save(struct alt_instr *start, struct alt_instr *end)
182 {
183         struct alt_instr *a;
184
185         DPRINTK("%s: alt table %p-%p\n", __FUNCTION__, start, end);
186         for (a = start; a < end; a++) {
187                 memcpy(a->replacement + a->replacementlen,
188                        a->instr,
189                        a->instrlen);
190         }
191 }
192
193 static void alternatives_smp_apply(struct alt_instr *start, struct alt_instr *end)
194 {
195         struct alt_instr *a;
196
197         for (a = start; a < end; a++) {
198                 memcpy(a->instr,
199                        a->replacement + a->replacementlen,
200                        a->instrlen);
201         }
202 }
203
204 static void alternatives_smp_lock(u8 **start, u8 **end, u8 *text, u8 *text_end)
205 {
206         u8 **ptr;
207
208         for (ptr = start; ptr < end; ptr++) {
209                 if (*ptr < text)
210                         continue;
211                 if (*ptr > text_end)
212                         continue;
213                 **ptr = 0xf0; /* lock prefix */
214         };
215 }
216
217 static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8 *text_end)
218 {
219         unsigned char **noptable = find_nop_table();
220         u8 **ptr;
221
222         for (ptr = start; ptr < end; ptr++) {
223                 if (*ptr < text)
224                         continue;
225                 if (*ptr > text_end)
226                         continue;
227                 **ptr = noptable[1][0];
228         };
229 }
230
231 struct 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 };
246 static LIST_HEAD(smp_alt_modules);
247 static DEFINE_SPINLOCK(smp_alt);
248
249 void 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
256         if (no_replacement)
257                 return;
258
259         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
288 void alternatives_smp_module_del(struct module *mod)
289 {
290         struct smp_alt_module *item;
291         unsigned long flags;
292
293         if (no_replacement || smp_alt_once)
294                 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
309 void alternatives_smp_switch(int smp)
310 {
311         struct smp_alt_module *mod;
312         unsigned long flags;
313
314 #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
324         if (no_replacement || smp_alt_once)
325                 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);
333                 alternatives_smp_apply(__smp_alt_instructions,
334                                        __smp_alt_instructions_end);
335                 list_for_each_entry(mod, &smp_alt_modules, next)
336                         alternatives_smp_lock(mod->locks, mod->locks_end,
337                                               mod->text, mod->text_end);
338         } else {
339                 printk(KERN_INFO "SMP alternatives: switching to UP code\n");
340                 set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
341                 set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
342                 apply_alternatives(__smp_alt_instructions,
343                                    __smp_alt_instructions_end);
344                 list_for_each_entry(mod, &smp_alt_modules, next)
345                         alternatives_smp_unlock(mod->locks, mod->locks_end,
346                                                 mod->text, mod->text_end);
347         }
348         spin_unlock_irqrestore(&smp_alt, flags);
349 }
350
351 #endif
352
353 void __init alternative_instructions(void)
354 {
355         unsigned long flags;
356         if (no_replacement) {
357                 printk(KERN_INFO "(SMP-)alternatives turned off\n");
358                 free_init_pages("SMP alternatives",
359                                 (unsigned long)__smp_alt_begin,
360                                 (unsigned long)__smp_alt_end);
361                 return;
362         }
363
364         local_irq_save(flags);
365         apply_alternatives(__alt_instructions, __alt_instructions_end);
366
367         /* switch to patch-once-at-boottime-only mode and free the
368          * tables in case we know the number of CPUs will never ever
369          * change */
370 #ifdef CONFIG_HOTPLUG_CPU
371         if (num_possible_cpus() < 2)
372                 smp_alt_once = 1;
373 #else
374         smp_alt_once = 1;
375 #endif
376
377 #ifdef CONFIG_SMP
378         if (smp_alt_once) {
379                 if (1 == num_possible_cpus()) {
380                         printk(KERN_INFO "SMP alternatives: switching to UP code\n");
381                         set_bit(X86_FEATURE_UP, boot_cpu_data.x86_capability);
382                         set_bit(X86_FEATURE_UP, cpu_data[0].x86_capability);
383                         apply_alternatives(__smp_alt_instructions,
384                                            __smp_alt_instructions_end);
385                         alternatives_smp_unlock(__smp_locks, __smp_locks_end,
386                                                 _text, _etext);
387                 }
388                 free_init_pages("SMP alternatives",
389                                 (unsigned long)__smp_alt_begin,
390                                 (unsigned long)__smp_alt_end);
391         } else {
392                 alternatives_smp_save(__smp_alt_instructions,
393                                       __smp_alt_instructions_end);
394                 alternatives_smp_module_add(NULL, "core kernel",
395                                             __smp_locks, __smp_locks_end,
396                                             _text, _etext);
397                 alternatives_smp_switch(0);
398         }
399 #endif
400         local_irq_restore(flags);
401 }