1 // SPDX-License-Identifier: GPL-2.0-only 2 /* Copyright (C) 2020 SiFive 3 * Copyright (C) 2025 Chen Miao 4 */ 5 6 #include <linux/mm.h> 7 #include <linux/kernel.h> 8 #include <linux/spinlock.h> 9 #include <linux/uaccess.h> 10 11 #include <asm/insn-def.h> 12 #include <asm/cacheflush.h> 13 #include <asm/page.h> 14 #include <asm/fixmap.h> 15 #include <asm/text-patching.h> 16 #include <asm/sections.h> 17 18 static DEFINE_RAW_SPINLOCK(patch_lock); 19 20 static __always_inline void *patch_map(void *addr, int fixmap) 21 { 22 uintptr_t uaddr = (uintptr_t) addr; 23 phys_addr_t phys; 24 25 if (core_kernel_text(uaddr)) { 26 phys = __pa_symbol(addr); 27 } else { 28 struct page *page = vmalloc_to_page(addr); 29 BUG_ON(!page); 30 phys = page_to_phys(page) + offset_in_page(addr); 31 } 32 33 return (void *)set_fixmap_offset(fixmap, phys); 34 } 35 36 static void patch_unmap(int fixmap) 37 { 38 clear_fixmap(fixmap); 39 } 40 41 static int __patch_insn_write(void *addr, u32 insn) 42 { 43 void *waddr = addr; 44 unsigned long flags = 0; 45 int ret; 46 47 raw_spin_lock_irqsave(&patch_lock, flags); 48 49 waddr = patch_map(addr, FIX_TEXT_POKE0); 50 51 ret = copy_to_kernel_nofault(waddr, &insn, OPENRISC_INSN_SIZE); 52 local_icache_range_inv((unsigned long)waddr, 53 (unsigned long)waddr + OPENRISC_INSN_SIZE); 54 55 patch_unmap(FIX_TEXT_POKE0); 56 57 raw_spin_unlock_irqrestore(&patch_lock, flags); 58 59 return ret; 60 } 61 62 /* 63 * patch_insn_write - Write a single instruction to a specified memory location 64 * This API provides a single-instruction patching, primarily used for runtime 65 * code modification. 66 * By the way, the insn size must be 4 bytes. 67 */ 68 int patch_insn_write(void *addr, u32 insn) 69 { 70 u32 *tp = addr; 71 int ret; 72 73 if ((uintptr_t) tp & 0x3) 74 return -EINVAL; 75 76 ret = __patch_insn_write(tp, insn); 77 78 return ret; 79 } 80