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