diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 44948e016f3213b4a86c19a0d7c2118e7fa30b35..257dbc0ce341fc55465566a421bc085bebe230b6 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -124,6 +124,7 @@ config ARM
select RTC_LIB
select SET_FS
select SYS_SUPPORTS_APM_EMULATION
+ select HAVE_LIVEPATCH_WO_FTRACE
# Above selects are sorted alphabetically; please add new ones
# according to that. Thanks.
help
@@ -2077,3 +2078,5 @@ source "arch/arm/crypto/Kconfig"
endif
source "arch/arm/Kconfig.assembler"
+
+source "kernel/livepatch/Kconfig"
diff --git a/arch/arm/include/asm/livepatch.h b/arch/arm/include/asm/livepatch.h
new file mode 100644
index 0000000000000000000000000000000000000000..216078d8c2b0a23a39da9690e934fd04bb9a631e
--- /dev/null
+++ b/arch/arm/include/asm/livepatch.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * livepatch.h - arm-specific Kernel Live Patching Core
+ *
+ * Copyright (C) 2018 Huawei Technologies Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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 .
+ */
+
+#ifndef _ASM_ARM_LIVEPATCH_H
+#define _ASM_ARM_LIVEPATCH_H
+
+#include
+
+struct klp_patch;
+struct klp_func;
+
+/* kernel livepatch instruction barrier */
+#define klp_smp_isb() isb()
+
+int arch_klp_patch_func(struct klp_func *func);
+void arch_klp_unpatch_func(struct klp_func *func);
+
+#ifdef CONFIG_LIVEPATCH_STOP_MACHINE_CONSISTENCY
+int klp_check_calltrace(struct klp_patch *patch, int enable);
+#endif
+
+#endif /* _ASM_ARM_LIVEPATCH_H */
diff --git a/arch/arm/include/asm/thread_info.h b/arch/arm/include/asm/thread_info.h
index 56fae7861fd38528808dd199373209652e25b29a..2e4733a2e73788923ba60f8c0703686bb96b449f 100644
--- a/arch/arm/include/asm/thread_info.h
+++ b/arch/arm/include/asm/thread_info.h
@@ -143,6 +143,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp *,
#define TIF_SYSCALL_AUDIT 5 /* syscall auditing active */
#define TIF_SYSCALL_TRACEPOINT 6 /* syscall tracepoint instrumentation */
#define TIF_SECCOMP 7 /* seccomp syscall filtering active */
+#define TIF_PATCH_PENDING 8 /* pending live patching update */
#define TIF_USING_IWMMXT 17
#define TIF_MEMDIE 18 /* is terminating due to OOM killer */
@@ -157,6 +158,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp *,
#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT)
#define _TIF_SECCOMP (1 << TIF_SECCOMP)
#define _TIF_USING_IWMMXT (1 << TIF_USING_IWMMXT)
+#define _TIF_PATCH_PENDING (1 << TIF_PATCH_PENDING)
/* Checks for any syscall work in entry-common.S */
#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index c38c8b019d24c6103a06ccaebd441929d3ac6c69..20900568c5685cd235a767f37fe48d4d9ba5cfa8 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_ARM_ARCH_TIMER) += arch_timer.o
obj-$(CONFIG_FUNCTION_TRACER) += entry-ftrace.o
obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o insn.o patch.o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o patch.o
+obj-$(CONFIG_LIVEPATCH) += livepatch.o insn.o patch.o
obj-$(CONFIG_JUMP_LABEL) += jump_label.o insn.o patch.o
obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o
# Main staffs in KPROBES are in arch/arm/probes/ .
diff --git a/arch/arm/kernel/livepatch.c b/arch/arm/kernel/livepatch.c
new file mode 100644
index 0000000000000000000000000000000000000000..5f87d98a9e99f9a7583fa991c8b2c33740dab987
--- /dev/null
+++ b/arch/arm/kernel/livepatch.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * livepatch.c - arm-specific Kernel Live Patching Core
+ *
+ * Copyright (C) 2018 Huawei Technologies Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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 .
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef CONFIG_LIVEPATCH_STOP_MACHINE_CONSISTENCY
+struct walk_stackframe_args {
+ struct klp_patch *patch;
+ int enable;
+ int ret;
+};
+
+static inline int klp_compare_address(unsigned long pc, unsigned long func_addr,
+ unsigned long func_size, const char *func_name)
+{
+ if (pc >= func_addr && pc < func_addr + func_size) {
+ pr_err("func %s is in use!\n", func_name);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int klp_check_activeness_func(struct stackframe *frame, void *data)
+{
+ struct walk_stackframe_args *args = data;
+ struct klp_patch *patch = args->patch;
+ struct klp_object *obj;
+ struct klp_func *func;
+ unsigned long func_addr, func_size;
+ const char *func_name;
+
+ if (args->ret)
+ return args->ret;
+
+ for (obj = patch->objs; obj->funcs; obj++) {
+ for (func = obj->funcs; func->old_name; func++) {
+ if (args->enable) {
+ func_addr = (unsigned long)func->old_func;
+ func_size = func->old_size;
+ } else {
+ func_addr = (unsigned long)func->new_func;
+ func_size = func->new_size;
+ }
+ func_name = func->old_name;
+ args->ret = klp_compare_address(frame->pc, func_addr,
+ func_size, func_name);
+ if (args->ret)
+ return args->ret;
+ }
+ }
+
+ return args->ret;
+}
+
+int klp_check_calltrace(struct klp_patch *patch, int enable)
+{
+ struct task_struct *g, *t;
+ struct stackframe frame;
+ int ret = 0;
+
+ struct walk_stackframe_args args = {
+ .patch = patch,
+ .enable = enable,
+ .ret = 0
+ };
+
+ for_each_process_thread(g, t) {
+ frame.fp = thread_saved_fp(t);
+ frame.sp = thread_saved_sp(t);
+ frame.pc = thread_saved_pc(t);
+ walk_stackframe(&frame, klp_check_activeness_func, &args);
+ if (args.ret) {
+ ret = args.ret;
+ pr_info("PID: %d Comm: %.20s\n", t->pid, t->comm);
+ show_stack(t, NULL, KERN_INFO);
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+#endif
+
+#define LJMP_INSN_SIZE 4
+
+struct klp_func_node {
+ struct list_head node;
+ struct list_head func_stack;
+ void *old_func;
+ u32 old_insn;
+};
+
+static LIST_HEAD(klp_func_list);
+
+static struct klp_func_node *klp_find_func_node(void *old_func)
+{
+ struct klp_func_node *func_node;
+
+ list_for_each_entry(func_node, &klp_func_list, node) {
+ if (func_node->old_func == old_func)
+ return func_node;
+ }
+
+ return NULL;
+}
+
+long arm_insn_read(void *addr, u32 *insnp)
+{
+ long ret;
+ u32 val;
+
+ ret = copy_from_kernel_nofault(&val, addr, LJMP_INSN_SIZE);
+ if (!ret)
+ *insnp = le32_to_cpu(val);
+
+ return ret;
+}
+
+int arch_klp_patch_func(struct klp_func *func)
+{
+ struct klp_func_node *func_node;
+ unsigned long pc, new_addr;
+ u32 insn;
+ long ret;
+
+ func_node = klp_find_func_node(func->old_func);
+ if (!func_node) {
+ func_node = kzalloc(sizeof(*func_node), GFP_ATOMIC);
+ if (!func_node)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&func_node->func_stack);
+ func_node->old_func = func->old_func;
+ ret = arm_insn_read(func->old_func, &func_node->old_insn);
+ if (ret) {
+ kfree(func_node);
+ return -EPERM;
+ }
+ list_add_rcu(&func_node->node, &klp_func_list);
+ }
+
+ list_add_rcu(&func->stack_node, &func_node->func_stack);
+
+ pc = (unsigned long)func->old_func;
+ new_addr = (unsigned long)func->new_func;
+ insn = arm_gen_branch(pc, new_addr);
+
+ __patch_text((void *)pc, insn);
+
+ return 0;
+}
+
+void arch_klp_unpatch_func(struct klp_func *func)
+{
+ struct klp_func_node *func_node;
+ struct klp_func *next_func;
+ unsigned long pc, new_addr;
+ u32 insn;
+
+ func_node = klp_find_func_node(func->old_func);
+ pc = (unsigned long)func_node->old_func;
+ if (list_is_singular(&func_node->func_stack)) {
+ insn = func_node->old_insn;
+ list_del_rcu(&func->stack_node);
+ list_del_rcu(&func_node->node);
+ kfree(func_node);
+
+ __patch_text((void *)pc, insn);
+ } else {
+ list_del_rcu(&func->stack_node);
+ next_func = list_first_or_null_rcu(&func_node->func_stack,
+ struct klp_func, stack_node);
+
+ new_addr = (unsigned long)next_func->new_func;
+ insn = arm_gen_branch(pc, new_addr);
+
+ __patch_text((void *)pc, insn);
+ }
+}