linux/arch/i386/kernel/alternative.c
<<
>>
Prefs
   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
   8static int noreplace_smp     = 0;
   9static int smp_alt_once      = 0;
  10static int debug_alternative = 0;
  11
  12static int __init bootonly(char *str)
  13{
  14        smp_alt_once = 1;
  15        return 1;
  16}
  17__setup("smp-alt-boot", bootonly);
  18
  19static int __init debug_alt(char *str)
  20{
  21        debug_alternative = 1;
  22        return 1;
  23}
  24__setup("debug-alternative", debug_alt);
  25
  26static int __init setup_noreplace_smp(char *str)
  27{
  28        noreplace_smp = 1;
  29        return 1;
  30}
  31__setup("noreplace-smp", setup_noreplace_smp);
  32
  33#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
  43
  44#define DPRINTK(fmt, args...) if (debug_alternative) \
  45        printk(KERN_DEBUG fmt, args)
  46
  47#ifdef GENERIC_NOP1
  48/* 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);
  54extern unsigned char intelnops[];
  55static 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};
  66#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[];
  73static 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};
  84#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[];
  91static 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};
 102#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
 114static 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
 123static 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
 137#endif /* CONFIG_X86_64 */
 138
 139static 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
 153extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
 154extern u8 *__smp_locks[], *__smp_locks_end[];
 155
 156/* 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{
 164        struct alt_instr *a;
 165        u8 *instr;
 166        int diff;
 167
 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;
 173                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);
 183                diff = a->instrlen - a->replacementlen;
 184                nop_out(instr + a->replacementlen, diff);
 185        }
 186}
 187
 188#ifdef CONFIG_SMP
 189
 190static 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{
 205        u8 **ptr;
 206
 207        if (noreplace_smp)
 208                return;
 209
 210        for (ptr = start; ptr < end; ptr++) {
 211                if (*ptr < text)
 212                        continue;
 213                if (*ptr > text_end)
 214                        continue;
 215                nop_out(*ptr, 1);
 216        };
 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
 237void 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
 244        if (noreplace_smp)
 245                return;
 246
 247        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
 281        if (smp_alt_once || noreplace_smp)
 282                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
 302#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
 312        if (noreplace_smp || smp_alt_once)
 313                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);
 321                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);
 328                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
 335#endif
 336
 337#ifdef CONFIG_PARAVIRT
 338void apply_paravirt(struct paravirt_patch_site *start,
 339                    struct paravirt_patch_site *end)
 340{
 341        struct paravirt_patch_site *p;
 342
 343        if (noreplace_paravirt)
 344                return;
 345
 346        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);
 351
 352                BUG_ON(used > p->len);
 353
 354                /* Pad the rest with nops */
 355                nop_out(p->instr + used, p->len - used);
 356        }
 357
 358        /* Sync to be conservative, in case we patched following
 359         * instructions */
 360        sync_core();
 361}
 362extern struct paravirt_patch_site __start_parainstructions[],
 363        __stop_parainstructions[];
 364#endif  /* CONFIG_PARAVIRT */
 365
 366void __init alternative_instructions(void)
 367{
 368        unsigned long flags;
 369
 370        local_irq_save(flags);
 371        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
 383#ifdef CONFIG_SMP
 384        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);
 389                        alternatives_smp_unlock(__smp_locks, __smp_locks_end,
 390                                                _text, _etext);
 391                }
 392                free_init_pages("SMP alternatives",
 393                                (unsigned long)__smp_locks,
 394                                (unsigned long)__smp_locks_end);
 395        } else {
 396                alternatives_smp_module_add(NULL, "core kernel",
 397                                            __smp_locks, __smp_locks_end,
 398                                            _text, _etext);
 399                alternatives_smp_switch(0);
 400        }
 401#endif
 402        apply_paravirt(__parainstructions, __parainstructions_end);
 403        local_irq_restore(flags);
 404}
 405
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.