feature-fixups.c 10.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 *  Copyright (C) 2001 Ben. Herrenschmidt (benh@kernel.crashing.org)
 *
 *  Modifications for ppc64:
 *      Copyright (C) 2003 Dave Engebretsen <engebret@us.ibm.com>
 *
 *  Copyright 2008 Michael Ellerman, IBM Corporation.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 */

#include <linux/kernel.h>
16 17
#include <linux/string.h>
#include <linux/init.h>
18 19 20 21 22 23 24 25 26
#include <asm/cputable.h>
#include <asm/code-patching.h>


struct fixup_entry {
	unsigned long	mask;
	unsigned long	value;
	long		start_off;
	long		end_off;
27 28
	long		alt_start_off;
	long		alt_end_off;
29 30
};

31
static unsigned int *calc_addr(struct fixup_entry *fcur, long offset)
32
{
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
	/*
	 * We store the offset to the code as a negative offset from
	 * the start of the alt_entry, to support the VDSO. This
	 * routine converts that back into an actual address.
	 */
	return (unsigned int *)((unsigned long)fcur + offset);
}

static int patch_alt_instruction(unsigned int *src, unsigned int *dest,
				 unsigned int *alt_start, unsigned int *alt_end)
{
	unsigned int instr;

	instr = *src;

	if (instr_is_relative_branch(*src)) {
		unsigned int *target = (unsigned int *)branch_target(src);

		/* Branch within the section doesn't need translating */
		if (target < alt_start || target >= alt_end) {
			instr = translate_branch(dest, src);
			if (!instr)
				return 1;
		}
	}

	patch_instruction(dest, instr);

	return 0;
}

static int patch_feature_section(unsigned long value, struct fixup_entry *fcur)
{
	unsigned int *start, *end, *alt_start, *alt_end, *src, *dest;

	start = calc_addr(fcur, fcur->start_off);
	end = calc_addr(fcur, fcur->end_off);
	alt_start = calc_addr(fcur, fcur->alt_start_off);
	alt_end = calc_addr(fcur, fcur->alt_end_off);

	if ((alt_end - alt_start) > (end - start))
		return 1;
75 76

	if ((value & fcur->mask) == fcur->value)
77
		return 0;
78

79 80
	src = alt_start;
	dest = start;
81

82 83 84
	for (; src < alt_end; src++, dest++) {
		if (patch_alt_instruction(src, dest, alt_start, alt_end))
			return 1;
85
	}
86 87 88 89 90

	for (; dest < end; dest++)
		patch_instruction(dest, PPC_NOP_INSTR);

	return 0;
91 92 93 94 95 96 97 98 99
}

void do_feature_fixups(unsigned long value, void *fixup_start, void *fixup_end)
{
	struct fixup_entry *fcur, *fend;

	fcur = fixup_start;
	fend = fixup_end;

100 101
	for (; fcur < fend; fcur++) {
		if (patch_feature_section(value, fcur)) {
102
			WARN_ON(1);
103 104 105 106 107 108 109 110
			printk("Unable to patch feature section at %p - %p" \
				" with %p - %p\n",
				calc_addr(fcur, fcur->start_off),
				calc_addr(fcur, fcur->end_off),
				calc_addr(fcur, fcur->alt_start_off),
				calc_addr(fcur, fcur->alt_end_off));
		}
	}
111
}
112

K
Kumar Gala 已提交
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
void do_lwsync_fixups(unsigned long value, void *fixup_start, void *fixup_end)
{
	unsigned int *start, *end, *dest;

	if (!(value & CPU_FTR_LWSYNC))
		return ;

	start = fixup_start;
	end = fixup_end;

	for (; start < end; start++) {
		dest = (void *)start + *start;
		patch_instruction(dest, PPC_LWSYNC_INSTR);
	}
}

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
#ifdef CONFIG_FTR_FIXUP_SELFTEST

#define check(x)	\
	if (!(x)) printk("feature-fixups: test failed at line %d\n", __LINE__);

/* This must be after the text it fixes up, vmlinux.lds.S enforces that atm */
static struct fixup_entry fixup;

static long calc_offset(struct fixup_entry *entry, unsigned int *p)
{
	return (unsigned long)p - (unsigned long)entry;
}

void test_basic_patching(void)
{
	extern unsigned int ftr_fixup_test1;
	extern unsigned int end_ftr_fixup_test1;
	extern unsigned int ftr_fixup_test1_orig;
	extern unsigned int ftr_fixup_test1_expected;
	int size = &end_ftr_fixup_test1 - &ftr_fixup_test1;

	fixup.value = fixup.mask = 8;
	fixup.start_off = calc_offset(&fixup, &ftr_fixup_test1 + 1);
	fixup.end_off = calc_offset(&fixup, &ftr_fixup_test1 + 2);
	fixup.alt_start_off = fixup.alt_end_off = 0;

	/* Sanity check */
	check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);

	/* Check we don't patch if the value matches */
	patch_feature_section(8, &fixup);
	check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);

	/* Check we do patch if the value doesn't match */
	patch_feature_section(0, &fixup);
	check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);

	/* Check we do patch if the mask doesn't match */
	memcpy(&ftr_fixup_test1, &ftr_fixup_test1_orig, size);
	check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_orig, size) == 0);
	patch_feature_section(~8, &fixup);
	check(memcmp(&ftr_fixup_test1, &ftr_fixup_test1_expected, size) == 0);
}

static void test_alternative_patching(void)
{
	extern unsigned int ftr_fixup_test2;
	extern unsigned int end_ftr_fixup_test2;
	extern unsigned int ftr_fixup_test2_orig;
	extern unsigned int ftr_fixup_test2_alt;
	extern unsigned int ftr_fixup_test2_expected;
	int size = &end_ftr_fixup_test2 - &ftr_fixup_test2;

	fixup.value = fixup.mask = 0xF;
	fixup.start_off = calc_offset(&fixup, &ftr_fixup_test2 + 1);
	fixup.end_off = calc_offset(&fixup, &ftr_fixup_test2 + 2);
	fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test2_alt);
	fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test2_alt + 1);

	/* Sanity check */
	check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);

	/* Check we don't patch if the value matches */
	patch_feature_section(0xF, &fixup);
	check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);

	/* Check we do patch if the value doesn't match */
	patch_feature_section(0, &fixup);
	check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);

	/* Check we do patch if the mask doesn't match */
	memcpy(&ftr_fixup_test2, &ftr_fixup_test2_orig, size);
	check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_orig, size) == 0);
	patch_feature_section(~0xF, &fixup);
	check(memcmp(&ftr_fixup_test2, &ftr_fixup_test2_expected, size) == 0);
}

static void test_alternative_case_too_big(void)
{
	extern unsigned int ftr_fixup_test3;
	extern unsigned int end_ftr_fixup_test3;
	extern unsigned int ftr_fixup_test3_orig;
	extern unsigned int ftr_fixup_test3_alt;
	int size = &end_ftr_fixup_test3 - &ftr_fixup_test3;

	fixup.value = fixup.mask = 0xC;
	fixup.start_off = calc_offset(&fixup, &ftr_fixup_test3 + 1);
	fixup.end_off = calc_offset(&fixup, &ftr_fixup_test3 + 2);
	fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test3_alt);
	fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test3_alt + 2);

	/* Sanity check */
	check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);

	/* Expect nothing to be patched, and the error returned to us */
	check(patch_feature_section(0xF, &fixup) == 1);
	check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
	check(patch_feature_section(0, &fixup) == 1);
	check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
	check(patch_feature_section(~0xF, &fixup) == 1);
	check(memcmp(&ftr_fixup_test3, &ftr_fixup_test3_orig, size) == 0);
}

static void test_alternative_case_too_small(void)
{
	extern unsigned int ftr_fixup_test4;
	extern unsigned int end_ftr_fixup_test4;
	extern unsigned int ftr_fixup_test4_orig;
	extern unsigned int ftr_fixup_test4_alt;
	extern unsigned int ftr_fixup_test4_expected;
	int size = &end_ftr_fixup_test4 - &ftr_fixup_test4;
	unsigned long flag;

	/* Check a high-bit flag */
	flag = 1UL << ((sizeof(unsigned long) - 1) * 8);
	fixup.value = fixup.mask = flag;
	fixup.start_off = calc_offset(&fixup, &ftr_fixup_test4 + 1);
	fixup.end_off = calc_offset(&fixup, &ftr_fixup_test4 + 5);
	fixup.alt_start_off = calc_offset(&fixup, &ftr_fixup_test4_alt);
	fixup.alt_end_off = calc_offset(&fixup, &ftr_fixup_test4_alt + 2);

	/* Sanity check */
	check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);

	/* Check we don't patch if the value matches */
	patch_feature_section(flag, &fixup);
	check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);

	/* Check we do patch if the value doesn't match */
	patch_feature_section(0, &fixup);
	check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);

	/* Check we do patch if the mask doesn't match */
	memcpy(&ftr_fixup_test4, &ftr_fixup_test4_orig, size);
	check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_orig, size) == 0);
	patch_feature_section(~flag, &fixup);
	check(memcmp(&ftr_fixup_test4, &ftr_fixup_test4_expected, size) == 0);
}

static void test_alternative_case_with_branch(void)
{
	extern unsigned int ftr_fixup_test5;
	extern unsigned int end_ftr_fixup_test5;
	extern unsigned int ftr_fixup_test5_expected;
	int size = &end_ftr_fixup_test5 - &ftr_fixup_test5;

	check(memcmp(&ftr_fixup_test5, &ftr_fixup_test5_expected, size) == 0);
}

static void test_alternative_case_with_external_branch(void)
{
	extern unsigned int ftr_fixup_test6;
	extern unsigned int end_ftr_fixup_test6;
	extern unsigned int ftr_fixup_test6_expected;
	int size = &end_ftr_fixup_test6 - &ftr_fixup_test6;

	check(memcmp(&ftr_fixup_test6, &ftr_fixup_test6_expected, size) == 0);
}

static void test_cpu_macros(void)
{
	extern void ftr_fixup_test_FTR_macros;
	extern void ftr_fixup_test_FTR_macros_expected;
	unsigned long size = &ftr_fixup_test_FTR_macros_expected -
			     &ftr_fixup_test_FTR_macros;

	/* The fixups have already been done for us during boot */
	check(memcmp(&ftr_fixup_test_FTR_macros,
		     &ftr_fixup_test_FTR_macros_expected, size) == 0);
}

static void test_fw_macros(void)
{
#ifdef CONFIG_PPC64
	extern void ftr_fixup_test_FW_FTR_macros;
	extern void ftr_fixup_test_FW_FTR_macros_expected;
	unsigned long size = &ftr_fixup_test_FW_FTR_macros_expected -
			     &ftr_fixup_test_FW_FTR_macros;

	/* The fixups have already been done for us during boot */
	check(memcmp(&ftr_fixup_test_FW_FTR_macros,
		     &ftr_fixup_test_FW_FTR_macros_expected, size) == 0);
#endif
}

K
Kumar Gala 已提交
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
static void test_lwsync_macros(void)
{
	extern void lwsync_fixup_test;
	extern void end_lwsync_fixup_test;
	extern void lwsync_fixup_test_expected_LWSYNC;
	extern void lwsync_fixup_test_expected_SYNC;
	unsigned long size = &end_lwsync_fixup_test -
			     &lwsync_fixup_test;

	/* The fixups have already been done for us during boot */
	if (cur_cpu_spec->cpu_features & CPU_FTR_LWSYNC) {
		check(memcmp(&lwsync_fixup_test,
			     &lwsync_fixup_test_expected_LWSYNC, size) == 0);
	} else {
		check(memcmp(&lwsync_fixup_test,
			     &lwsync_fixup_test_expected_SYNC, size) == 0);
	}
}

333 334 335 336 337 338 339 340 341 342 343 344
static int __init test_feature_fixups(void)
{
	printk(KERN_DEBUG "Running feature fixup self-tests ...\n");

	test_basic_patching();
	test_alternative_patching();
	test_alternative_case_too_big();
	test_alternative_case_too_small();
	test_alternative_case_with_branch();
	test_alternative_case_with_external_branch();
	test_cpu_macros();
	test_fw_macros();
K
Kumar Gala 已提交
345
	test_lwsync_macros();
346 347 348 349 350 351

	return 0;
}
late_initcall(test_feature_fixups);

#endif /* CONFIG_FTR_FIXUP_SELFTEST */