linux/arch/powerpc/lib/feature-fixups.c
<<
>>
Prefs
   1/*
   2 *  Copyright (C) 2001 Ben. Herrenschmidt (benh@kernel.crashing.org)
   3 *
   4 *  Modifications for ppc64:
   5 *      Copyright (C) 2003 Dave Engebretsen <engebret@us.ibm.com>
   6 *
   7 *  Copyright 2008 Michael Ellerman, IBM Corporation.
   8 *
   9 *  This program is free software; you can redistribute it and/or
  10 *  modify it under the terms of the GNU General Public License
  11 *  as published by the Free Software Foundation; either version
  12 *  2 of the License, or (at your option) any later version.
  13 */
  14
  15#include <linux/kernel.h>
  16#include <linux/string.h>
  17#include <linux/init.h>
  18#include <asm/cputable.h>
  19#include <asm/code-patching.h>
  20
  21
  22struct fixup_entry {
  23        unsigned long   mask;
  24        unsigned long   value;
  25        long            start_off;
  26        long            end_off;
  27        long            alt_start_off;
  28        long            alt_end_off;
  29};
  30
  31static unsigned int *calc_addr(struct fixup_entry *fcur, long offset)
  32{
  33        /*
  34         * We store the offset to the code as a negative offset from
  35         * the start of the alt_entry, to support the VDSO. This
  36         * routine converts that back into an actual address.
  37         */
  38        return (unsigned int *)((unsigned long)fcur + offset);
  39}
  40
  41static int patch_alt_instruction(unsigned int *src, unsigned int *dest,
  42                                 unsigned int *alt_start, unsigned int *alt_end)
  43{
  44        unsigned int instr;
  45
  46        instr = *src;
  47
  48        if (instr_is_relative_branch(*src)) {
  49                unsigned int *target = (unsigned int *)branch_target(src);
  50
  51                /* Branch within the section doesn't need translating */
  52                if (target < alt_start || target >= alt_end) {
  53                        instr = translate_branch(dest, src);
  54                        if (!instr)
  55                                return 1;
  56                }
  57        }
  58
  59        patch_instruction(dest, instr);
  60
  61        return 0;
  62}
  63
  64static int patch_feature_section(unsigned long value, struct fixup_entry *fcur)
  65{
  66        unsigned int *start, *end, *alt_start, *alt_end, *src, *dest;
  67
  68        start = calc_addr(fcur, fcur->start_off);
  69        end = calc_addr(fcur, fcur->end_off);
  70        alt_start = calc_addr(fcur, fcur->alt_start_off);
  71        alt_end = calc_addr(fcur, fcur->alt_end_off);
  72
  73        if ((alt_end - alt_start) > (end - start))
  74                return 1;
  75
  76        if ((value & fcur->mask) == fcur->value)
  77                return 0;
  78
  79        src = alt_start;
  80        dest = start;
  81
  82        for (; src < alt_end; src++, dest++) {
  83                if (patch_alt_instruction(src, dest, alt_start, alt_end))
  84                        return 1;
  85        }
  86
  87        for (; dest < end; dest++)
  88                patch_instruction(dest, PPC_NOP_INSTR);
  89
  90        return 0;
  91}
  92
  93void do_feature_fixups(unsigned long value, void *fixup_start, void *fixup_end)
  94{
  95        struct fixup_entry *fcur, *fend;
  96
  97        fcur = fixup_start;
  98        fend = fixup_end;
  99
 100        for (; fcur < fend; fcur++) {
 101                if (patch_feature_section(value, fcur)) {
 102                        WARN_ON(1);
 103                        printk("Unable to patch feature section at %p - %p" \
 104                                " with %p - %p\n",
 105                                calc_addr(fcur, fcur->start_off),
 106                                calc_addr(fcur, fcur->end_off),
 107                                calc_addr(fcur, fcur->alt_start_off),
 108                                calc_addr(fcur, fcur->alt_end_off));
 109                }
 110        }
 111}
 112
 113void do_lwsync_fixups(unsigned long value, void *fixup_start, void *fixup_end)
 114{
 115        unsigned int *start, *end, *dest;
 116
 117        if (!(value & CPU_FTR_LWSYNC))
 118                return ;
 119
 120        start = fixup_start;
 121        end = fixup_end;
 122
 123        for (; start < end; start++) {
 124                dest = (void *)start + *start;
 125                patch_instruction(dest, PPC_LWSYNC_INSTR);
 126        }
 127}
 128
 129#ifdef CONFIG_FTR_FIXUP_SELFTEST
 130
 131#define check(x)        \
 132        if (!(x)) printk("feature-fixups: test failed at line %d\n", __LINE__);
 133
 134/* This must be after the text it fixes up, vmlinux.lds.S enforces that atm */
 135static struct fixup_entry fixup;
 136
 137static long calc_offset(struct fixup_entry *entry, unsigned int *p)
 138{
 139        return (unsigned long)p - (unsigned long)entry;
 140}
 141
 142void test_basic_patching(void)
 143{
 144        extern unsigned int ftr_fixup_test1;
 145        extern unsigned int end_ftr_fixup_test1;
 146        extern unsigned int ftr_fixup_test1_orig;
 147        extern unsigned int ftr_fixup_test1_expected;
 148        int size = &end_ftr_fixup_test1 - &ftr_fixup_test1;
 149
 150        fixup.value = fixup.mask = 8;
 151        fixup.start_off = calc_offset(&fixup, &ftr_fixup_test1 + 1);
 152        fixup.end_off = calc_offset(&fixup, &ftr_fixup_test1 + 2);
 153        fixup.alt_start_off = fixup.alt_end_off = 0;
 154
 155        /* Sanity check */
 156        check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);
 157
 158        /* Check we don't patch if the value matches */
 159        patch_feature_section(8, &fixup);
 160        check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);
 161
 162        /* Check we do patch if the value doesn't match */
 163        patch_feature_section(0, &fixup);
 164        check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);
 165
 166        /* Check we do patch if the mask doesn't match */
 167        memcpy(&ftr_fixup_test1, &ftr_fixup_test1_orig, size);
 168        check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);
 169        patch_feature_section(~8, &fixup);
 170        check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);
 171}
 172
 173static void test_alternative_patching(void)
 174{
 175        extern unsigned int ftr_fixup_test2;
 176        extern unsigned int end_ftr_fixup_test2;
 177        extern unsigned int ftr_fixup_test2_orig;
 178        extern unsigned int ftr_fixup_test2_alt;
 179        extern unsigned int ftr_fixup_test2_expected;
 180        int size = &end_ftr_fixup_test2 - &ftr_fixup_test2;
 181
 182        fixup.value = fixup.mask = 0xF;
 183        fixup.start_off = calc_offset(&fixup, &ftr_fixup_test2 + 1);
 184        fixup.end_off = calc_offset(&fixup, &ftr_fixup_test2 + 2);
 185        fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test2_alt);
 186        fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test2_alt + 1);
 187
 188        /* Sanity check */
 189        check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);
 190
 191        /* Check we don't patch if the value matches */
 192        patch_feature_section(0xF, &fixup);
 193        check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);
 194
 195        /* Check we do patch if the value doesn't match */
 196        patch_feature_section(0, &fixup);
 197        check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);
 198
 199        /* Check we do patch if the mask doesn't match */
 200        memcpy(&ftr_fixup_test2, &ftr_fixup_test2_orig, size);
 201        check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);
 202        patch_feature_section(~0xF, &fixup);
 203        check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);
 204}
 205
 206static void test_alternative_case_too_big(void)
 207{
 208        extern unsigned int ftr_fixup_test3;
 209        extern unsigned int end_ftr_fixup_test3;
 210        extern unsigned int ftr_fixup_test3_orig;
 211        extern unsigned int ftr_fixup_test3_alt;
 212        int size = &end_ftr_fixup_test3 - &ftr_fixup_test3;
 213
 214        fixup.value = fixup.mask = 0xC;
 215        fixup.start_off = calc_offset(&fixup, &ftr_fixup_test3 + 1);
 216        fixup.end_off = calc_offset(&fixup, &ftr_fixup_test3 + 2);
 217        fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test3_alt);
 218        fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test3_alt + 2);
 219
 220        /* Sanity check */
 221        check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
 222
 223        /* Expect nothing to be patched, and the error returned to us */
 224        check(patch_feature_section(0xF, &fixup) == 1);
 225        check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
 226        check(patch_feature_section(0, &fixup) == 1);
 227        check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
 228        check(patch_feature_section(~0xF, &fixup) == 1);
 229        check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
 230}
 231
 232static void test_alternative_case_too_small(void)
 233{
 234        extern unsigned int ftr_fixup_test4;
 235        extern unsigned int end_ftr_fixup_test4;
 236        extern unsigned int ftr_fixup_test4_orig;
 237        extern unsigned int ftr_fixup_test4_alt;
 238        extern unsigned int ftr_fixup_test4_expected;
 239        int size = &end_ftr_fixup_test4 - &ftr_fixup_test4;
 240        unsigned long flag;
 241
 242        /* Check a high-bit flag */
 243        flag = 1UL << ((sizeof(unsigned long) - 1) * 8);
 244        fixup.value = fixup.mask = flag;
 245        fixup.start_off = calc_offset(&fixup, &ftr_fixup_test4 + 1);
 246        fixup.end_off = calc_offset(&fixup, &ftr_fixup_test4 + 5);
 247        fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test4_alt);
 248        fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test4_alt + 2);
 249
 250        /* Sanity check */
 251        check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);
 252
 253        /* Check we don't patch if the value matches */
 254        patch_feature_section(flag, &fixup);
 255        check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);
 256
 257        /* Check we do patch if the value doesn't match */
 258        patch_feature_section(0, &fixup);
 259        check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);
 260
 261        /* Check we do patch if the mask doesn't match */
 262        memcpy(&ftr_fixup_test4, &ftr_fixup_test4_orig, size);
 263        check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);
 264        patch_feature_section(~flag, &fixup);
 265        check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);
 266}
 267
 268static void test_alternative_case_with_branch(void)
 269{
 270        extern unsigned int ftr_fixup_test5;
 271        extern unsigned int end_ftr_fixup_test5;
 272        extern unsigned int ftr_fixup_test5_expected;
 273        int size = &end_ftr_fixup_test5 - &ftr_fixup_test5;
 274
 275        check(memcmp(&ftr_fixup_test5, &ftr_fixup_test5_expected, size) == 0);
 276}
 277
 278static void test_alternative_case_with_external_branch(void)
 279{
 280        extern unsigned int ftr_fixup_test6;
 281        extern unsigned int end_ftr_fixup_test6;
 282        extern unsigned int ftr_fixup_test6_expected;
 283        int size = &end_ftr_fixup_test6 - &ftr_fixup_test6;
 284
 285        check(memcmp(&ftr_fixup_test6, &ftr_fixup_test6_expected, size) == 0);
 286}
 287
 288static void test_cpu_macros(void)
 289{
 290        extern void ftr_fixup_test_FTR_macros;
 291        extern void ftr_fixup_test_FTR_macros_expected;
 292        unsigned long size = &ftr_fixup_test_FTR_macros_expected -
 293                             &ftr_fixup_test_FTR_macros;
 294
 295        /* The fixups have already been done for us during boot */
 296        check(memcmp(&ftr_fixup_test_FTR_macros,
 297                     &ftr_fixup_test_FTR_macros_expected, size) == 0);
 298}
 299
 300static void test_fw_macros(void)
 301{
 302#ifdef CONFIG_PPC64
 303        extern void ftr_fixup_test_FW_FTR_macros;
 304        extern void ftr_fixup_test_FW_FTR_macros_expected;
 305        unsigned long size = &ftr_fixup_test_FW_FTR_macros_expected -
 306                             &ftr_fixup_test_FW_FTR_macros;
 307
 308        /* The fixups have already been done for us during boot */
 309        check(memcmp(&ftr_fixup_test_FW_FTR_macros,
 310                     &ftr_fixup_test_FW_FTR_macros_expected, size) == 0);
 311#endif
 312}
 313
 314static void test_lwsync_macros(void)
 315{
 316        extern void lwsync_fixup_test;
 317        extern void end_lwsync_fixup_test;
 318        extern void lwsync_fixup_test_expected_LWSYNC;
 319        extern void lwsync_fixup_test_expected_SYNC;
 320        unsigned long size = &end_lwsync_fixup_test -
 321                             &lwsync_fixup_test;
 322
 323        /* The fixups have already been done for us during boot */
 324        if (cur_cpu_spec->cpu_features & CPU_FTR_LWSYNC) {
 325                check(memcmp(&lwsync_fixup_test,
 326                             &lwsync_fixup_test_expected_LWSYNC, size) == 0);
 327        } else {
 328                check(memcmp(&lwsync_fixup_test,
 329                             &lwsync_fixup_test_expected_SYNC, size) == 0);
 330        }
 331}
 332
 333static int __init test_feature_fixups(void)
 334{
 335        printk(KERN_DEBUG "Running feature fixup self-tests ...\n");
 336
 337        test_basic_patching();
 338        test_alternative_patching();
 339        test_alternative_case_too_big();
 340        test_alternative_case_too_small();
 341        test_alternative_case_with_branch();
 342        test_alternative_case_with_external_branch();
 343        test_cpu_macros();
 344        test_fw_macros();
 345        test_lwsync_macros();
 346
 347        return 0;
 348}
 349late_initcall(test_feature_fixups);
 350
 351#endif /* CONFIG_FTR_FIXUP_SELFTEST */
 352
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.