| /* | |
| * Copyright (C) 2013 Huawei Ltd. | |
| * Author: Jiang Liu <liuj97@gmail.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #include <linux/bitops.h> | |
| #include <linux/compiler.h> | |
| #include <linux/kernel.h> | |
| #include <linux/smp.h> | |
| #include <linux/stop_machine.h> | |
| #include <linux/uaccess.h> | |
| #include <asm/cacheflush.h> | |
| #include <asm/insn.h> | |
| static int aarch64_insn_encoding_class[] = { | |
| AARCH64_INSN_CLS_UNKNOWN, | |
| AARCH64_INSN_CLS_UNKNOWN, | |
| AARCH64_INSN_CLS_UNKNOWN, | |
| AARCH64_INSN_CLS_UNKNOWN, | |
| AARCH64_INSN_CLS_LDST, | |
| AARCH64_INSN_CLS_DP_REG, | |
| AARCH64_INSN_CLS_LDST, | |
| AARCH64_INSN_CLS_DP_FPSIMD, | |
| AARCH64_INSN_CLS_DP_IMM, | |
| AARCH64_INSN_CLS_DP_IMM, | |
| AARCH64_INSN_CLS_BR_SYS, | |
| AARCH64_INSN_CLS_BR_SYS, | |
| AARCH64_INSN_CLS_LDST, | |
| AARCH64_INSN_CLS_DP_REG, | |
| AARCH64_INSN_CLS_LDST, | |
| AARCH64_INSN_CLS_DP_FPSIMD, | |
| }; | |
| enum aarch64_insn_encoding_class __kprobes aarch64_get_insn_class(u32 insn) | |
| { | |
| return aarch64_insn_encoding_class[(insn >> 25) & 0xf]; | |
| } | |
| /* NOP is an alias of HINT */ | |
| bool __kprobes aarch64_insn_is_nop(u32 insn) | |
| { | |
| if (!aarch64_insn_is_hint(insn)) | |
| return false; | |
| switch (insn & 0xFE0) { | |
| case AARCH64_INSN_HINT_YIELD: | |
| case AARCH64_INSN_HINT_WFE: | |
| case AARCH64_INSN_HINT_WFI: | |
| case AARCH64_INSN_HINT_SEV: | |
| case AARCH64_INSN_HINT_SEVL: | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } | |
| /* | |
| * In ARMv8-A, A64 instructions have a fixed length of 32 bits and are always | |
| * little-endian. | |
| */ | |
| int __kprobes aarch64_insn_read(void *addr, u32 *insnp) | |
| { | |
| int ret; | |
| u32 val; | |
| ret = probe_kernel_read(&val, addr, AARCH64_INSN_SIZE); | |
| if (!ret) | |
| *insnp = le32_to_cpu(val); | |
| return ret; | |
| } | |
| int __kprobes aarch64_insn_write(void *addr, u32 insn) | |
| { | |
| insn = cpu_to_le32(insn); | |
| return probe_kernel_write(addr, &insn, AARCH64_INSN_SIZE); | |
| } | |
| static bool __kprobes __aarch64_insn_hotpatch_safe(u32 insn) | |
| { | |
| if (aarch64_get_insn_class(insn) != AARCH64_INSN_CLS_BR_SYS) | |
| return false; | |
| return aarch64_insn_is_b(insn) || | |
| aarch64_insn_is_bl(insn) || | |
| aarch64_insn_is_svc(insn) || | |
| aarch64_insn_is_hvc(insn) || | |
| aarch64_insn_is_smc(insn) || | |
| aarch64_insn_is_brk(insn) || | |
| aarch64_insn_is_nop(insn); | |
| } | |
| /* | |
| * ARM Architecture Reference Manual for ARMv8 Profile-A, Issue A.a | |
| * Section B2.6.5 "Concurrent modification and execution of instructions": | |
| * Concurrent modification and execution of instructions can lead to the | |
| * resulting instruction performing any behavior that can be achieved by | |
| * executing any sequence of instructions that can be executed from the | |
| * same Exception level, except where the instruction before modification | |
| * and the instruction after modification is a B, BL, NOP, BKPT, SVC, HVC, | |
| * or SMC instruction. | |
| */ | |
| bool __kprobes aarch64_insn_hotpatch_safe(u32 old_insn, u32 new_insn) | |
| { | |
| return __aarch64_insn_hotpatch_safe(old_insn) && | |
| __aarch64_insn_hotpatch_safe(new_insn); | |
| } | |
| int __kprobes aarch64_insn_patch_text_nosync(void *addr, u32 insn) | |
| { | |
| u32 *tp = addr; | |
| int ret; | |
| /* A64 instructions must be word aligned */ | |
| if ((uintptr_t)tp & 0x3) | |
| return -EINVAL; | |
| ret = aarch64_insn_write(tp, insn); | |
| if (ret == 0) | |
| flush_icache_range((uintptr_t)tp, | |
| (uintptr_t)tp + AARCH64_INSN_SIZE); | |
| return ret; | |
| } | |
| struct aarch64_insn_patch { | |
| void **text_addrs; | |
| u32 *new_insns; | |
| int insn_cnt; | |
| atomic_t cpu_count; | |
| }; | |
| static int __kprobes aarch64_insn_patch_text_cb(void *arg) | |
| { | |
| int i, ret = 0; | |
| struct aarch64_insn_patch *pp = arg; | |
| /* The first CPU becomes master */ | |
| if (atomic_inc_return(&pp->cpu_count) == 1) { | |
| for (i = 0; ret == 0 && i < pp->insn_cnt; i++) | |
| ret = aarch64_insn_patch_text_nosync(pp->text_addrs[i], | |
| pp->new_insns[i]); | |
| /* | |
| * aarch64_insn_patch_text_nosync() calls flush_icache_range(), | |
| * which ends with "dsb; isb" pair guaranteeing global | |
| * visibility. | |
| */ | |
| atomic_set(&pp->cpu_count, -1); | |
| } else { | |
| while (atomic_read(&pp->cpu_count) != -1) | |
| cpu_relax(); | |
| isb(); | |
| } | |
| return ret; | |
| } | |
| int __kprobes aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt) | |
| { | |
| struct aarch64_insn_patch patch = { | |
| .text_addrs = addrs, | |
| .new_insns = insns, | |
| .insn_cnt = cnt, | |
| .cpu_count = ATOMIC_INIT(0), | |
| }; | |
| if (cnt <= 0) | |
| return -EINVAL; | |
| return stop_machine(aarch64_insn_patch_text_cb, &patch, | |
| cpu_online_mask); | |
| } | |
| int __kprobes aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt) | |
| { | |
| int ret; | |
| u32 insn; | |
| /* Unsafe to patch multiple instructions without synchronizaiton */ | |
| if (cnt == 1) { | |
| ret = aarch64_insn_read(addrs[0], &insn); | |
| if (ret) | |
| return ret; | |
| if (aarch64_insn_hotpatch_safe(insn, insns[0])) { | |
| /* | |
| * ARMv8 architecture doesn't guarantee all CPUs see | |
| * the new instruction after returning from function | |
| * aarch64_insn_patch_text_nosync(). So send IPIs to | |
| * all other CPUs to achieve instruction | |
| * synchronization. | |
| */ | |
| ret = aarch64_insn_patch_text_nosync(addrs[0], insns[0]); | |
| kick_all_cpus_sync(); | |
| return ret; | |
| } | |
| } | |
| return aarch64_insn_patch_text_sync(addrs, insns, cnt); | |
| } | |
| u32 __kprobes aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type, | |
| u32 insn, u64 imm) | |
| { | |
| u32 immlo, immhi, lomask, himask, mask; | |
| int shift; | |
| switch (type) { | |
| case AARCH64_INSN_IMM_ADR: | |
| lomask = 0x3; | |
| himask = 0x7ffff; | |
| immlo = imm & lomask; | |
| imm >>= 2; | |
| immhi = imm & himask; | |
| imm = (immlo << 24) | (immhi); | |
| mask = (lomask << 24) | (himask); | |
| shift = 5; | |
| break; | |
| case AARCH64_INSN_IMM_26: | |
| mask = BIT(26) - 1; | |
| shift = 0; | |
| break; | |
| case AARCH64_INSN_IMM_19: | |
| mask = BIT(19) - 1; | |
| shift = 5; | |
| break; | |
| case AARCH64_INSN_IMM_16: | |
| mask = BIT(16) - 1; | |
| shift = 5; | |
| break; | |
| case AARCH64_INSN_IMM_14: | |
| mask = BIT(14) - 1; | |
| shift = 5; | |
| break; | |
| case AARCH64_INSN_IMM_12: | |
| mask = BIT(12) - 1; | |
| shift = 10; | |
| break; | |
| case AARCH64_INSN_IMM_9: | |
| mask = BIT(9) - 1; | |
| shift = 12; | |
| break; | |
| default: | |
| pr_err("aarch64_insn_encode_immediate: unknown immediate encoding %d\n", | |
| type); | |
| return 0; | |
| } | |
| /* Update the immediate field. */ | |
| insn &= ~(mask << shift); | |
| insn |= (imm & mask) << shift; | |
| return insn; | |
| } | |
| u32 __kprobes aarch64_insn_gen_branch_imm(unsigned long pc, unsigned long addr, | |
| enum aarch64_insn_branch_type type) | |
| { | |
| u32 insn; | |
| long offset; | |
| /* | |
| * PC: A 64-bit Program Counter holding the address of the current | |
| * instruction. A64 instructions must be word-aligned. | |
| */ | |
| BUG_ON((pc & 0x3) || (addr & 0x3)); | |
| /* | |
| * B/BL support [-128M, 128M) offset | |
| * ARM64 virtual address arrangement guarantees all kernel and module | |
| * texts are within +/-128M. | |
| */ | |
| offset = ((long)addr - (long)pc); | |
| BUG_ON(offset < -SZ_128M || offset >= SZ_128M); | |
| if (type == AARCH64_INSN_BRANCH_LINK) | |
| insn = aarch64_insn_get_bl_value(); | |
| else | |
| insn = aarch64_insn_get_b_value(); | |
| return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_26, insn, | |
| offset >> 2); | |
| } | |
| u32 __kprobes aarch64_insn_gen_hint(enum aarch64_insn_hint_op op) | |
| { | |
| return aarch64_insn_get_hint_value() | op; | |
| } | |
| u32 __kprobes aarch64_insn_gen_nop(void) | |
| { | |
| return aarch64_insn_gen_hint(AARCH64_INSN_HINT_NOP); | |
| } |