blob: 174c1eba9ac87c4eb61b6418577d5e446ccbc3d7 [file] [log] [blame]
Michael Ellerman51c52e82008-06-24 11:32:36 +10001/*
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 <asm/cputable.h>
17#include <asm/code-patching.h>
18
19
20struct fixup_entry {
21 unsigned long mask;
22 unsigned long value;
23 long start_off;
24 long end_off;
Michael Ellermanfac23fe2008-06-24 11:32:54 +100025 long alt_start_off;
26 long alt_end_off;
Michael Ellerman51c52e82008-06-24 11:32:36 +100027};
28
Michael Ellerman9b1a7352008-06-24 11:33:02 +100029static unsigned int *calc_addr(struct fixup_entry *fcur, long offset)
Michael Ellerman51c52e82008-06-24 11:32:36 +100030{
Michael Ellerman9b1a7352008-06-24 11:33:02 +100031 /*
32 * We store the offset to the code as a negative offset from
33 * the start of the alt_entry, to support the VDSO. This
34 * routine converts that back into an actual address.
35 */
36 return (unsigned int *)((unsigned long)fcur + offset);
37}
38
39static int patch_alt_instruction(unsigned int *src, unsigned int *dest,
40 unsigned int *alt_start, unsigned int *alt_end)
41{
42 unsigned int instr;
43
44 instr = *src;
45
46 if (instr_is_relative_branch(*src)) {
47 unsigned int *target = (unsigned int *)branch_target(src);
48
49 /* Branch within the section doesn't need translating */
50 if (target < alt_start || target >= alt_end) {
51 instr = translate_branch(dest, src);
52 if (!instr)
53 return 1;
54 }
55 }
56
57 patch_instruction(dest, instr);
58
59 return 0;
60}
61
62static int patch_feature_section(unsigned long value, struct fixup_entry *fcur)
63{
64 unsigned int *start, *end, *alt_start, *alt_end, *src, *dest;
65
66 start = calc_addr(fcur, fcur->start_off);
67 end = calc_addr(fcur, fcur->end_off);
68 alt_start = calc_addr(fcur, fcur->alt_start_off);
69 alt_end = calc_addr(fcur, fcur->alt_end_off);
70
71 if ((alt_end - alt_start) > (end - start))
72 return 1;
Michael Ellerman51c52e82008-06-24 11:32:36 +100073
74 if ((value & fcur->mask) == fcur->value)
Michael Ellerman9b1a7352008-06-24 11:33:02 +100075 return 0;
Michael Ellerman51c52e82008-06-24 11:32:36 +100076
Michael Ellerman9b1a7352008-06-24 11:33:02 +100077 src = alt_start;
78 dest = start;
Michael Ellerman51c52e82008-06-24 11:32:36 +100079
Michael Ellerman9b1a7352008-06-24 11:33:02 +100080 for (; src < alt_end; src++, dest++) {
81 if (patch_alt_instruction(src, dest, alt_start, alt_end))
82 return 1;
Michael Ellerman51c52e82008-06-24 11:32:36 +100083 }
Michael Ellerman9b1a7352008-06-24 11:33:02 +100084
85 for (; dest < end; dest++)
86 patch_instruction(dest, PPC_NOP_INSTR);
87
88 return 0;
Michael Ellerman51c52e82008-06-24 11:32:36 +100089}
90
91void do_feature_fixups(unsigned long value, void *fixup_start, void *fixup_end)
92{
93 struct fixup_entry *fcur, *fend;
94
95 fcur = fixup_start;
96 fend = fixup_end;
97
Michael Ellerman9b1a7352008-06-24 11:33:02 +100098 for (; fcur < fend; fcur++) {
99 if (patch_feature_section(value, fcur)) {
100 __WARN();
101 printk("Unable to patch feature section at %p - %p" \
102 " with %p - %p\n",
103 calc_addr(fcur, fcur->start_off),
104 calc_addr(fcur, fcur->end_off),
105 calc_addr(fcur, fcur->alt_start_off),
106 calc_addr(fcur, fcur->alt_end_off));
107 }
108 }
Michael Ellerman51c52e82008-06-24 11:32:36 +1000109}