Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: insn: Add aarch64_{get,set}_branch_offset

In order to deal with branches located in alternate sequences,
but pointing to the main kernel text, it is required to extract
the relative displacement encoded in the instruction, and to be
able to update said instruction with a new offset (once it is
known).

For this, we introduce three new helpers:
- aarch64_insn_is_branch_imm is a predicate indicating if the
instruction is an immediate branch
- aarch64_get_branch_offset returns a signed value representing
the byte offset encoded in a branch instruction
- aarch64_set_branch_offset takes an instruction and an offset,
and returns the corresponding updated instruction.

Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

authored by

Marc Zyngier and committed by
Catalin Marinas
10b48f7e 9acdc2af

+63
+3
arch/arm64/include/asm/insn.h
··· 281 281 #undef __AARCH64_INSN_FUNCS 282 282 283 283 bool aarch64_insn_is_nop(u32 insn); 284 + bool aarch64_insn_is_branch_imm(u32 insn); 284 285 285 286 int aarch64_insn_read(void *addr, u32 *insnp); 286 287 int aarch64_insn_write(void *addr, u32 insn); ··· 352 351 int shift, 353 352 enum aarch64_insn_variant variant, 354 353 enum aarch64_insn_logic_type type); 354 + s32 aarch64_get_branch_offset(u32 insn); 355 + u32 aarch64_set_branch_offset(u32 insn, s32 offset); 355 356 356 357 bool aarch64_insn_hotpatch_safe(u32 old_insn, u32 new_insn); 357 358
+60
arch/arm64/kernel/insn.c
··· 77 77 } 78 78 } 79 79 80 + bool aarch64_insn_is_branch_imm(u32 insn) 81 + { 82 + return (aarch64_insn_is_b(insn) || aarch64_insn_is_bl(insn) || 83 + aarch64_insn_is_tbz(insn) || aarch64_insn_is_tbnz(insn) || 84 + aarch64_insn_is_cbz(insn) || aarch64_insn_is_cbnz(insn) || 85 + aarch64_insn_is_bcond(insn)); 86 + } 87 + 80 88 static DEFINE_SPINLOCK(patch_lock); 81 89 82 90 static void __kprobes *patch_map(void *addr, int fixmap) ··· 1063 1055 insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RM, insn, reg); 1064 1056 1065 1057 return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift); 1058 + } 1059 + 1060 + /* 1061 + * Decode the imm field of a branch, and return the byte offset as a 1062 + * signed value (so it can be used when computing a new branch 1063 + * target). 1064 + */ 1065 + s32 aarch64_get_branch_offset(u32 insn) 1066 + { 1067 + s32 imm; 1068 + 1069 + if (aarch64_insn_is_b(insn) || aarch64_insn_is_bl(insn)) { 1070 + imm = aarch64_insn_decode_immediate(AARCH64_INSN_IMM_26, insn); 1071 + return (imm << 6) >> 4; 1072 + } 1073 + 1074 + if (aarch64_insn_is_cbz(insn) || aarch64_insn_is_cbnz(insn) || 1075 + aarch64_insn_is_bcond(insn)) { 1076 + imm = aarch64_insn_decode_immediate(AARCH64_INSN_IMM_19, insn); 1077 + return (imm << 13) >> 11; 1078 + } 1079 + 1080 + if (aarch64_insn_is_tbz(insn) || aarch64_insn_is_tbnz(insn)) { 1081 + imm = aarch64_insn_decode_immediate(AARCH64_INSN_IMM_14, insn); 1082 + return (imm << 18) >> 16; 1083 + } 1084 + 1085 + /* Unhandled instruction */ 1086 + BUG(); 1087 + } 1088 + 1089 + /* 1090 + * Encode the displacement of a branch in the imm field and return the 1091 + * updated instruction. 1092 + */ 1093 + u32 aarch64_set_branch_offset(u32 insn, s32 offset) 1094 + { 1095 + if (aarch64_insn_is_b(insn) || aarch64_insn_is_bl(insn)) 1096 + return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_26, insn, 1097 + offset >> 2); 1098 + 1099 + if (aarch64_insn_is_cbz(insn) || aarch64_insn_is_cbnz(insn) || 1100 + aarch64_insn_is_bcond(insn)) 1101 + return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_19, insn, 1102 + offset >> 2); 1103 + 1104 + if (aarch64_insn_is_tbz(insn) || aarch64_insn_is_tbnz(insn)) 1105 + return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_14, insn, 1106 + offset >> 2); 1107 + 1108 + /* Unhandled instruction */ 1109 + BUG(); 1066 1110 } 1067 1111 1068 1112 bool aarch32_insn_is_wide(u32 insn)