aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm64/kernel
diff options
context:
space:
mode:
authorMarc Zyngier <marc.zyngier@arm.com>2015-03-27 09:09:22 -0400
committerWill Deacon <will.deacon@arm.com>2015-03-30 06:03:43 -0400
commitfef7f2b2010381c795ae43743ad31931cc58f5ad (patch)
treeb8f144d8646b22385f3e5ff603749cd26eb09703 /arch/arm64/kernel
parent0978fb25f86b7595821cee6955679250d47c6438 (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.c55
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
29extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; 30extern 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 */
42static 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
50static 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
36static int __apply_alternatives(void *alt_region) 79static 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 }