From: chenmiao <chenmiao...@gmail.com>

We need a text patching mechanism to ensure that in the subsequent
implementation of jump_label, the code can be modified to the correct
location. Therefore, FIX_TEXT_POKE0 has been added as a mapping area.

Among these changes, we implement patch_map and support the
patch_insn_write API for single instruction writing.

Link: https://lore.kernel.org/openrisc/aJIC8o1WmVHol9RY@antec/T/#t

Signed-off-by: chenmiao <chenmiao...@gmail.com>

---
Changes in V3:
  - Removed the unimplemented and unsupported is_exit_text, added
    comments for the set_fixmap modification explaining why __init was
    removed, and added new comments for patch_insn_write.

Changes in V2:
  - We modify the patch_insn_write(void *addr, const void *insn) API to
    patch_insn_write(void *addr, u32 insn), derectly support a single u32
    instruction write to map memory.
  - Create a new file named insn-def.h to define the or1k insn macro
    size and more define in the future.

Signed-off-by: chenmiao <chenmiao...@gmail.com>
---
 arch/openrisc/include/asm/Kbuild          |  1 -
 arch/openrisc/include/asm/fixmap.h        |  1 +
 arch/openrisc/include/asm/insn-def.h      | 12 ++++
 arch/openrisc/include/asm/text-patching.h | 13 ++++
 arch/openrisc/kernel/Makefile             |  1 +
 arch/openrisc/kernel/patching.c           | 78 +++++++++++++++++++++++
 arch/openrisc/mm/init.c                   | 10 ++-
 7 files changed, 114 insertions(+), 2 deletions(-)
 create mode 100644 arch/openrisc/include/asm/insn-def.h
 create mode 100644 arch/openrisc/include/asm/text-patching.h
 create mode 100644 arch/openrisc/kernel/patching.c

diff --git a/arch/openrisc/include/asm/Kbuild b/arch/openrisc/include/asm/Kbuild
index 2b1a6b00cdac..cef49d60d74c 100644
--- a/arch/openrisc/include/asm/Kbuild
+++ b/arch/openrisc/include/asm/Kbuild
@@ -9,4 +9,3 @@ generic-y += spinlock.h
 generic-y += qrwlock_types.h
 generic-y += qrwlock.h
 generic-y += user.h
-generic-y += text-patching.h
diff --git a/arch/openrisc/include/asm/fixmap.h 
b/arch/openrisc/include/asm/fixmap.h
index aaa6a26a3e92..74000215064d 100644
--- a/arch/openrisc/include/asm/fixmap.h
+++ b/arch/openrisc/include/asm/fixmap.h
@@ -28,6 +28,7 @@
 
 enum fixed_addresses {
        FIX_EARLYCON_MEM_BASE,
+       FIX_TEXT_POKE0,
        __end_of_fixed_addresses
 };
 
diff --git a/arch/openrisc/include/asm/insn-def.h 
b/arch/openrisc/include/asm/insn-def.h
new file mode 100644
index 000000000000..dc8d16db1579
--- /dev/null
+++ b/arch/openrisc/include/asm/insn-def.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Chen Miao
+ */
+
+#ifndef __ASM_INSN_DEF_H
+#define __ASM_INSN_DEF_H
+
+/* or1k instructions are always 32 bits. */
+#define        OPENRISC_INSN_SIZE              4
+
+#endif /* __ASM_INSN_DEF_H */
diff --git a/arch/openrisc/include/asm/text-patching.h 
b/arch/openrisc/include/asm/text-patching.h
new file mode 100644
index 000000000000..bffe828288c3
--- /dev/null
+++ b/arch/openrisc/include/asm/text-patching.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Chen Miao
+ */
+
+#ifndef _ASM_PATCHING_H_
+#define _ASM_PATCHING_H_
+
+#include <linux/types.h>
+
+int patch_insn_write(void *addr, u32 insn);
+
+#endif /* _ASM_PATCHING_H_ */
diff --git a/arch/openrisc/kernel/Makefile b/arch/openrisc/kernel/Makefile
index 58e6a1b525b7..f0957ce16d6b 100644
--- a/arch/openrisc/kernel/Makefile
+++ b/arch/openrisc/kernel/Makefile
@@ -13,5 +13,6 @@ obj-$(CONFIG_SMP)             += smp.o sync-timer.o
 obj-$(CONFIG_STACKTRACE)       += stacktrace.o
 obj-$(CONFIG_MODULES)          += module.o
 obj-$(CONFIG_OF)               += prom.o
+obj-y  += patching.o
 
 clean:
diff --git a/arch/openrisc/kernel/patching.c b/arch/openrisc/kernel/patching.c
new file mode 100644
index 000000000000..73ae449c6c4e
--- /dev/null
+++ b/arch/openrisc/kernel/patching.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2020 SiFive
+ * Copyright (C) 2025 Chen Miao
+ */
+
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+
+#include <asm/insn-def.h>
+#include <asm/cacheflush.h>
+#include <asm/page.h>
+#include <asm/fixmap.h>
+#include <asm/text-patching.h>
+#include <asm/sections.h>
+
+static DEFINE_RAW_SPINLOCK(patch_lock);
+
+static __always_inline void *patch_map(void *addr, int fixmap)
+{
+       uintptr_t uaddr = (uintptr_t) addr;
+       phys_addr_t phys;
+
+       if (core_kernel_text(uaddr)) {
+               phys = __pa_symbol(addr);
+       } else {
+               struct page *page = vmalloc_to_page(addr);
+               BUG_ON(!page);
+               phys = page_to_phys(page) + offset_in_page(addr);
+       }
+
+       return (void *)set_fixmap_offset(fixmap, phys);
+}
+
+static void patch_unmap(int fixmap)
+{
+       clear_fixmap(fixmap);
+}
+
+static int __patch_insn_write(void *addr, u32 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, OPENRISC_INSN_SIZE);
+       local_icache_range_inv((unsigned long)waddr,
+                              (unsigned long)waddr + OPENRISC_INSN_SIZE);
+
+       patch_unmap(FIX_TEXT_POKE0);
+
+       raw_spin_unlock_irqrestore(&patch_lock, flags);
+
+       return ret;
+}
+
+/* patch_insn_write - Write a single instruction to a specified memory location
+ * This API provides a single-instruction patching, primarily used for runtime
+ * code modification.
+ * By the way, the insn size must be 4 bytes.
+ */
+int patch_insn_write(void *addr, u32 insn)
+{
+       u32 *tp = addr;
+       int ret;
+
+       if ((uintptr_t) tp & 0x3)
+               return -EINVAL;
+
+       ret = __patch_insn_write(tp, insn);
+
+       return ret;
+}
diff --git a/arch/openrisc/mm/init.c b/arch/openrisc/mm/init.c
index e4904ca6f0a0..ac256c3d9c7a 100644
--- a/arch/openrisc/mm/init.c
+++ b/arch/openrisc/mm/init.c
@@ -226,7 +226,15 @@ static int __init map_page(unsigned long va, phys_addr_t 
pa, pgprot_t prot)
        return 0;
 }
 
-void __init __set_fixmap(enum fixed_addresses idx,
+/* Removing __init is necessary. Before supporting FIX_TEXT_POKE0,
+ *  __init here indicates that it is valid during the initialization phase
+ * and is used for FIX_EARLYCON_MEM_BASE. However, attempting to support
+ * FIX_TEXT_POKE0 would introduce a bug. FIX_TEXT_POKE0 is used after the
+ * initialization phase, so __init would cause the function to become invalid.
+ * At that point, using set_fixmap would lead to accessing dirty data,
+ * which is invalid.
+ */
+void __set_fixmap(enum fixed_addresses idx,
                         phys_addr_t phys, pgprot_t prot)
 {
        unsigned long address = __fix_to_virt(idx);
-- 
2.45.2


Reply via email to