diff options
author | Marc Zyngier <marc.zyngier@arm.com> | 2015-03-27 09:09:22 -0400 |
---|---|---|
committer | Will Deacon <will.deacon@arm.com> | 2015-03-30 06:03:43 -0400 |
commit | fef7f2b2010381c795ae43743ad31931cc58f5ad (patch) | |
tree | b8f144d8646b22385f3e5ff603749cd26eb09703 /arch/arm64/kernel | |
parent | 0978fb25f86b7595821cee6955679250d47c6438 (diff) |
arm64: alternative: Allow immediate branch as alternative instruction
Since all immediate branches are PC-relative on Aarch64, these
instructions cannot be used as an alternative with the simplistic
approach we currently have (the immediate has been computed from
the .altinstr_replacement section, and end-up being completely off
if we insert it directly).
This patch handles the b and bl instructions in a different way,
using the insn framework to recompute the immediate, and generate
the right displacement.
Reviewed-by: Andre Przywara <andre.przywara@arm.com>
Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Diffstat (limited to 'arch/arm64/kernel')
-rw-r--r-- | arch/arm64/kernel/alternative.c | 55 |
1 files changed, 53 insertions, 2 deletions
diff --git a/arch/arm64/kernel/alternative.c b/arch/arm64/kernel/alternative.c index ad7821d64a1d..21033bba9390 100644 --- a/arch/arm64/kernel/alternative.c +++ b/arch/arm64/kernel/alternative.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include <asm/cacheflush.h> | 24 | #include <asm/cacheflush.h> |
25 | #include <asm/alternative.h> | 25 | #include <asm/alternative.h> |
26 | #include <asm/cpufeature.h> | 26 | #include <asm/cpufeature.h> |
27 | #include <asm/insn.h> | ||
27 | #include <linux/stop_machine.h> | 28 | #include <linux/stop_machine.h> |
28 | 29 | ||
29 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; | 30 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; |
@@ -33,6 +34,48 @@ struct alt_region { | |||
33 | struct alt_instr *end; | 34 | struct alt_instr *end; |
34 | }; | 35 | }; |
35 | 36 | ||
37 | /* | ||
38 | * Decode the imm field of a b/bl instruction, and return the byte | ||
39 | * offset as a signed value (so it can be used when computing a new | ||
40 | * branch target). | ||
41 | */ | ||
42 | static s32 get_branch_offset(u32 insn) | ||
43 | { | ||
44 | s32 imm = aarch64_insn_decode_immediate(AARCH64_INSN_IMM_26, insn); | ||
45 | |||
46 | /* sign-extend the immediate before turning it into a byte offset */ | ||
47 | return (imm << 6) >> 4; | ||
48 | } | ||
49 | |||
50 | static u32 get_alt_insn(u8 *insnptr, u8 *altinsnptr) | ||
51 | { | ||
52 | u32 insn; | ||
53 | |||
54 | aarch64_insn_read(altinsnptr, &insn); | ||
55 | |||
56 | /* Stop the world on instructions we don't support... */ | ||
57 | BUG_ON(aarch64_insn_is_cbz(insn)); | ||
58 | BUG_ON(aarch64_insn_is_cbnz(insn)); | ||
59 | BUG_ON(aarch64_insn_is_bcond(insn)); | ||
60 | /* ... and there is probably more. */ | ||
61 | |||
62 | if (aarch64_insn_is_b(insn) || aarch64_insn_is_bl(insn)) { | ||
63 | enum aarch64_insn_branch_type type; | ||
64 | unsigned long target; | ||
65 | |||
66 | if (aarch64_insn_is_b(insn)) | ||
67 | type = AARCH64_INSN_BRANCH_NOLINK; | ||
68 | else | ||
69 | type = AARCH64_INSN_BRANCH_LINK; | ||
70 | |||
71 | target = (unsigned long)altinsnptr + get_branch_offset(insn); | ||
72 | insn = aarch64_insn_gen_branch_imm((unsigned long)insnptr, | ||
73 | target, type); | ||
74 | } | ||
75 | |||
76 | return insn; | ||
77 | } | ||
78 | |||
36 | static int __apply_alternatives(void *alt_region) | 79 | static int __apply_alternatives(void *alt_region) |
37 | { | 80 | { |
38 | struct alt_instr *alt; | 81 | struct alt_instr *alt; |
@@ -40,16 +83,24 @@ static int __apply_alternatives(void *alt_region) | |||
40 | u8 *origptr, *replptr; | 83 | u8 *origptr, *replptr; |
41 | 84 | ||
42 | for (alt = region->begin; alt < region->end; alt++) { | 85 | for (alt = region->begin; alt < region->end; alt++) { |
86 | u32 insn; | ||
87 | int i; | ||
88 | |||
43 | if (!cpus_have_cap(alt->cpufeature)) | 89 | if (!cpus_have_cap(alt->cpufeature)) |
44 | continue; | 90 | continue; |
45 | 91 | ||
46 | BUG_ON(alt->alt_len > alt->orig_len); | 92 | BUG_ON(alt->alt_len != alt->orig_len); |
47 | 93 | ||
48 | pr_info_once("patching kernel code\n"); | 94 | pr_info_once("patching kernel code\n"); |
49 | 95 | ||
50 | origptr = (u8 *)&alt->orig_offset + alt->orig_offset; | 96 | origptr = (u8 *)&alt->orig_offset + alt->orig_offset; |
51 | replptr = (u8 *)&alt->alt_offset + alt->alt_offset; | 97 | replptr = (u8 *)&alt->alt_offset + alt->alt_offset; |
52 | memcpy(origptr, replptr, alt->alt_len); | 98 | |
99 | for (i = 0; i < alt->alt_len; i += sizeof(insn)) { | ||
100 | insn = get_alt_insn(origptr + i, replptr + i); | ||
101 | aarch64_insn_write(origptr + i, insn); | ||
102 | } | ||
103 | |||
53 | flush_icache_range((uintptr_t)origptr, | 104 | flush_icache_range((uintptr_t)origptr, |
54 | (uintptr_t)(origptr + alt->alt_len)); | 105 | (uintptr_t)(origptr + alt->alt_len)); |
55 | } | 106 | } |