Path: blob/master/arch/powerpc/lib/feature-fixups.c
10817 views
/*1* Copyright (C) 2001 Ben. Herrenschmidt ([email protected])2*3* Modifications for ppc64:4* Copyright (C) 2003 Dave Engebretsen <[email protected]>5*6* Copyright 2008 Michael Ellerman, IBM Corporation.7*8* This program is free software; you can redistribute it and/or9* modify it under the terms of the GNU General Public License10* as published by the Free Software Foundation; either version11* 2 of the License, or (at your option) any later version.12*/1314#include <linux/types.h>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>202122struct fixup_entry {23unsigned long mask;24unsigned long value;25long start_off;26long end_off;27long alt_start_off;28long alt_end_off;29};3031static unsigned int *calc_addr(struct fixup_entry *fcur, long offset)32{33/*34* We store the offset to the code as a negative offset from35* the start of the alt_entry, to support the VDSO. This36* routine converts that back into an actual address.37*/38return (unsigned int *)((unsigned long)fcur + offset);39}4041static int patch_alt_instruction(unsigned int *src, unsigned int *dest,42unsigned int *alt_start, unsigned int *alt_end)43{44unsigned int instr;4546instr = *src;4748if (instr_is_relative_branch(*src)) {49unsigned int *target = (unsigned int *)branch_target(src);5051/* Branch within the section doesn't need translating */52if (target < alt_start || target >= alt_end) {53instr = translate_branch(dest, src);54if (!instr)55return 1;56}57}5859patch_instruction(dest, instr);6061return 0;62}6364static int patch_feature_section(unsigned long value, struct fixup_entry *fcur)65{66unsigned int *start, *end, *alt_start, *alt_end, *src, *dest;6768start = calc_addr(fcur, fcur->start_off);69end = calc_addr(fcur, fcur->end_off);70alt_start = calc_addr(fcur, fcur->alt_start_off);71alt_end = calc_addr(fcur, fcur->alt_end_off);7273if ((alt_end - alt_start) > (end - start))74return 1;7576if ((value & fcur->mask) == fcur->value)77return 0;7879src = alt_start;80dest = start;8182for (; src < alt_end; src++, dest++) {83if (patch_alt_instruction(src, dest, alt_start, alt_end))84return 1;85}8687for (; dest < end; dest++)88patch_instruction(dest, PPC_INST_NOP);8990return 0;91}9293void do_feature_fixups(unsigned long value, void *fixup_start, void *fixup_end)94{95struct fixup_entry *fcur, *fend;9697fcur = fixup_start;98fend = fixup_end;99100for (; fcur < fend; fcur++) {101if (patch_feature_section(value, fcur)) {102WARN_ON(1);103printk("Unable to patch feature section at %p - %p" \104" with %p - %p\n",105calc_addr(fcur, fcur->start_off),106calc_addr(fcur, fcur->end_off),107calc_addr(fcur, fcur->alt_start_off),108calc_addr(fcur, fcur->alt_end_off));109}110}111}112113void do_lwsync_fixups(unsigned long value, void *fixup_start, void *fixup_end)114{115long *start, *end;116unsigned int *dest;117118if (!(value & CPU_FTR_LWSYNC))119return ;120121start = fixup_start;122end = fixup_end;123124for (; start < end; start++) {125dest = (void *)start + *start;126patch_instruction(dest, PPC_INST_LWSYNC);127}128}129130#ifdef CONFIG_FTR_FIXUP_SELFTEST131132#define check(x) \133if (!(x)) printk("feature-fixups: test failed at line %d\n", __LINE__);134135/* This must be after the text it fixes up, vmlinux.lds.S enforces that atm */136static struct fixup_entry fixup;137138static long calc_offset(struct fixup_entry *entry, unsigned int *p)139{140return (unsigned long)p - (unsigned long)entry;141}142143void test_basic_patching(void)144{145extern unsigned int ftr_fixup_test1;146extern unsigned int end_ftr_fixup_test1;147extern unsigned int ftr_fixup_test1_orig;148extern unsigned int ftr_fixup_test1_expected;149int size = &end_ftr_fixup_test1 - &ftr_fixup_test1;150151fixup.value = fixup.mask = 8;152fixup.start_off = calc_offset(&fixup, &ftr_fixup_test1 + 1);153fixup.end_off = calc_offset(&fixup, &ftr_fixup_test1 + 2);154fixup.alt_start_off = fixup.alt_end_off = 0;155156/* Sanity check */157check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);158159/* Check we don't patch if the value matches */160patch_feature_section(8, &fixup);161check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);162163/* Check we do patch if the value doesn't match */164patch_feature_section(0, &fixup);165check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);166167/* Check we do patch if the mask doesn't match */168memcpy(&ftr_fixup_test1, &ftr_fixup_test1_orig, size);169check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);170patch_feature_section(~8, &fixup);171check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);172}173174static void test_alternative_patching(void)175{176extern unsigned int ftr_fixup_test2;177extern unsigned int end_ftr_fixup_test2;178extern unsigned int ftr_fixup_test2_orig;179extern unsigned int ftr_fixup_test2_alt;180extern unsigned int ftr_fixup_test2_expected;181int size = &end_ftr_fixup_test2 - &ftr_fixup_test2;182183fixup.value = fixup.mask = 0xF;184fixup.start_off = calc_offset(&fixup, &ftr_fixup_test2 + 1);185fixup.end_off = calc_offset(&fixup, &ftr_fixup_test2 + 2);186fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test2_alt);187fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test2_alt + 1);188189/* Sanity check */190check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);191192/* Check we don't patch if the value matches */193patch_feature_section(0xF, &fixup);194check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);195196/* Check we do patch if the value doesn't match */197patch_feature_section(0, &fixup);198check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);199200/* Check we do patch if the mask doesn't match */201memcpy(&ftr_fixup_test2, &ftr_fixup_test2_orig, size);202check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);203patch_feature_section(~0xF, &fixup);204check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);205}206207static void test_alternative_case_too_big(void)208{209extern unsigned int ftr_fixup_test3;210extern unsigned int end_ftr_fixup_test3;211extern unsigned int ftr_fixup_test3_orig;212extern unsigned int ftr_fixup_test3_alt;213int size = &end_ftr_fixup_test3 - &ftr_fixup_test3;214215fixup.value = fixup.mask = 0xC;216fixup.start_off = calc_offset(&fixup, &ftr_fixup_test3 + 1);217fixup.end_off = calc_offset(&fixup, &ftr_fixup_test3 + 2);218fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test3_alt);219fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test3_alt + 2);220221/* Sanity check */222check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);223224/* Expect nothing to be patched, and the error returned to us */225check(patch_feature_section(0xF, &fixup) == 1);226check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);227check(patch_feature_section(0, &fixup) == 1);228check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);229check(patch_feature_section(~0xF, &fixup) == 1);230check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);231}232233static void test_alternative_case_too_small(void)234{235extern unsigned int ftr_fixup_test4;236extern unsigned int end_ftr_fixup_test4;237extern unsigned int ftr_fixup_test4_orig;238extern unsigned int ftr_fixup_test4_alt;239extern unsigned int ftr_fixup_test4_expected;240int size = &end_ftr_fixup_test4 - &ftr_fixup_test4;241unsigned long flag;242243/* Check a high-bit flag */244flag = 1UL << ((sizeof(unsigned long) - 1) * 8);245fixup.value = fixup.mask = flag;246fixup.start_off = calc_offset(&fixup, &ftr_fixup_test4 + 1);247fixup.end_off = calc_offset(&fixup, &ftr_fixup_test4 + 5);248fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test4_alt);249fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test4_alt + 2);250251/* Sanity check */252check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);253254/* Check we don't patch if the value matches */255patch_feature_section(flag, &fixup);256check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);257258/* Check we do patch if the value doesn't match */259patch_feature_section(0, &fixup);260check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);261262/* Check we do patch if the mask doesn't match */263memcpy(&ftr_fixup_test4, &ftr_fixup_test4_orig, size);264check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);265patch_feature_section(~flag, &fixup);266check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);267}268269static void test_alternative_case_with_branch(void)270{271extern unsigned int ftr_fixup_test5;272extern unsigned int end_ftr_fixup_test5;273extern unsigned int ftr_fixup_test5_expected;274int size = &end_ftr_fixup_test5 - &ftr_fixup_test5;275276check(memcmp(&ftr_fixup_test5, &ftr_fixup_test5_expected, size) == 0);277}278279static void test_alternative_case_with_external_branch(void)280{281extern unsigned int ftr_fixup_test6;282extern unsigned int end_ftr_fixup_test6;283extern unsigned int ftr_fixup_test6_expected;284int size = &end_ftr_fixup_test6 - &ftr_fixup_test6;285286check(memcmp(&ftr_fixup_test6, &ftr_fixup_test6_expected, size) == 0);287}288289static void test_cpu_macros(void)290{291extern u8 ftr_fixup_test_FTR_macros;292extern u8 ftr_fixup_test_FTR_macros_expected;293unsigned long size = &ftr_fixup_test_FTR_macros_expected -294&ftr_fixup_test_FTR_macros;295296/* The fixups have already been done for us during boot */297check(memcmp(&ftr_fixup_test_FTR_macros,298&ftr_fixup_test_FTR_macros_expected, size) == 0);299}300301static void test_fw_macros(void)302{303#ifdef CONFIG_PPC64304extern u8 ftr_fixup_test_FW_FTR_macros;305extern u8 ftr_fixup_test_FW_FTR_macros_expected;306unsigned long size = &ftr_fixup_test_FW_FTR_macros_expected -307&ftr_fixup_test_FW_FTR_macros;308309/* The fixups have already been done for us during boot */310check(memcmp(&ftr_fixup_test_FW_FTR_macros,311&ftr_fixup_test_FW_FTR_macros_expected, size) == 0);312#endif313}314315static void test_lwsync_macros(void)316{317extern u8 lwsync_fixup_test;318extern u8 end_lwsync_fixup_test;319extern u8 lwsync_fixup_test_expected_LWSYNC;320extern u8 lwsync_fixup_test_expected_SYNC;321unsigned long size = &end_lwsync_fixup_test -322&lwsync_fixup_test;323324/* The fixups have already been done for us during boot */325if (cur_cpu_spec->cpu_features & CPU_FTR_LWSYNC) {326check(memcmp(&lwsync_fixup_test,327&lwsync_fixup_test_expected_LWSYNC, size) == 0);328} else {329check(memcmp(&lwsync_fixup_test,330&lwsync_fixup_test_expected_SYNC, size) == 0);331}332}333334static int __init test_feature_fixups(void)335{336printk(KERN_DEBUG "Running feature fixup self-tests ...\n");337338test_basic_patching();339test_alternative_patching();340test_alternative_case_too_big();341test_alternative_case_too_small();342test_alternative_case_with_branch();343test_alternative_case_with_external_branch();344test_cpu_macros();345test_fw_macros();346test_lwsync_macros();347348return 0;349}350late_initcall(test_feature_fixups);351352#endif /* CONFIG_FTR_FIXUP_SELFTEST */353354355