| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/kernel.h> | |
| #include <linux/mm.h> | |
| #include <linux/smp.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/stop_machine.h> | |
| #include <linux/uaccess.h> | |
| #include <asm/cacheflush.h> | |
| #include <asm/fixmap.h> | |
| #include <asm/insn.h> | |
| #include <asm/kprobes.h> | |
| #include <asm/patching.h> | |
| #include <asm/sections.h> | |
| static DEFINE_RAW_SPINLOCK(patch_lock); | |
| static bool is_exit_text(unsigned long addr) | |
| { | |
| /* discarded with init text/data */ | |
| return system_state < SYSTEM_RUNNING && | |
| addr >= (unsigned long)__exittext_begin && | |
| addr < (unsigned long)__exittext_end; | |
| } | |
| static bool is_image_text(unsigned long addr) | |
| { | |
| return core_kernel_text(addr) || is_exit_text(addr); | |
| } | |
| static void __kprobes *patch_map(void *addr, int fixmap) | |
| { | |
| unsigned long uintaddr = (uintptr_t) addr; | |
| bool image = is_image_text(uintaddr); | |
| struct page *page; | |
| if (image) | |
| page = phys_to_page(__pa_symbol(addr)); | |
| else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) | |
| page = vmalloc_to_page(addr); | |
| else | |
| return addr; | |
| BUG_ON(!page); | |
| return (void *)set_fixmap_offset(fixmap, page_to_phys(page) + | |
| (uintaddr & ~PAGE_MASK)); | |
| } | |
| static void __kprobes patch_unmap(int fixmap) | |
| { | |
| clear_fixmap(fixmap); | |
| } | |
| /* | |
| * 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; | |
| __le32 val; | |
| ret = copy_from_kernel_nofault(&val, addr, AARCH64_INSN_SIZE); | |
| if (!ret) | |
| *insnp = le32_to_cpu(val); | |
| return ret; | |
| } | |
| static int __kprobes __aarch64_insn_write(void *addr, __le32 insn) | |
| { | |
| void *waddr = addr; | |
| unsigned long flags = 0; | |
| int ret; | |
| raw_spin_lock_irqsave(&patch_lock, flags); | |
| waddr = patch_map(addr, FIX_TEXT_POKE0); | |
| ret = copy_to_kernel_nofault(waddr, &insn, AARCH64_INSN_SIZE); | |
| patch_unmap(FIX_TEXT_POKE0); | |
| raw_spin_unlock_irqrestore(&patch_lock, flags); | |
| return ret; | |
| } | |
| int __kprobes aarch64_insn_write(void *addr, u32 insn) | |
| { | |
| return __aarch64_insn_write(addr, cpu_to_le32(insn)); | |
| } | |
| noinstr int aarch64_insn_write_literal_u64(void *addr, u64 val) | |
| { | |
| u64 *waddr; | |
| unsigned long flags; | |
| int ret; | |
| raw_spin_lock_irqsave(&patch_lock, flags); | |
| waddr = patch_map(addr, FIX_TEXT_POKE0); | |
| ret = copy_to_kernel_nofault(waddr, &val, sizeof(val)); | |
| patch_unmap(FIX_TEXT_POKE0); | |
| raw_spin_unlock_irqrestore(&patch_lock, flags); | |
| return ret; | |
| } | |
| 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) | |
| caches_clean_inval_pou((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 last CPU becomes master */ | |
| if (atomic_inc_return(&pp->cpu_count) == num_online_cpus()) { | |
| for (i = 0; ret == 0 && i < pp->insn_cnt; i++) | |
| ret = aarch64_insn_patch_text_nosync(pp->text_addrs[i], | |
| pp->new_insns[i]); | |
| /* Notify other processors with an additional increment. */ | |
| atomic_inc(&pp->cpu_count); | |
| } else { | |
| while (atomic_read(&pp->cpu_count) <= num_online_cpus()) | |
| cpu_relax(); | |
| isb(); | |
| } | |
| return ret; | |
| } | |
| int __kprobes aarch64_insn_patch_text(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_cpuslocked(aarch64_insn_patch_text_cb, &patch, | |
| cpu_online_mask); | |
| } |