summaryrefslogtreecommitdiff
path: root/arch/s390/include/asm/entry-percpu.h
diff options
context:
space:
mode:
Diffstat (limited to 'arch/s390/include/asm/entry-percpu.h')
-rw-r--r--arch/s390/include/asm/entry-percpu.h80
1 files changed, 80 insertions, 0 deletions
diff --git a/arch/s390/include/asm/entry-percpu.h b/arch/s390/include/asm/entry-percpu.h
new file mode 100644
index 000000000000..4ef47265cfce
--- /dev/null
+++ b/arch/s390/include/asm/entry-percpu.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef ARCH_S390_ENTRY_PERCPU_H
+#define ARCH_S390_ENTRY_PERCPU_H
+
+#include <linux/kprobes.h>
+#include <linux/percpu.h>
+#include <asm/lowcore.h>
+#include <asm/ptrace.h>
+#include <asm/asm-offsets.h>
+
+static __always_inline void percpu_entry(struct pt_regs *regs)
+{
+ struct lowcore *lc = get_lowcore();
+
+ if (user_mode(regs))
+ return;
+ regs->cpu = lc->cpu_nr;
+ regs->percpu_register = lc->percpu_register;
+ lc->percpu_register = 0;
+}
+
+static __always_inline bool percpu_code_check(struct pt_regs *regs)
+{
+ unsigned int insn, disp;
+ struct kprobe *p;
+
+ if (likely(user_mode(regs) || !regs->percpu_register))
+ return false;
+ /*
+ * Within a percpu code section - check if the percpu base register
+ * needs to be updated. This is the case if the PSW does not point to
+ * the ADD instruction within the section.
+ * - AG %rx,percpu_offset_in_lowcore(%r0,%r0)
+ * which adds the percpu offset to the percpu base register.
+ */
+ lockdep_assert_preemption_disabled();
+again:
+ insn = READ_ONCE(*(u16 *)psw_bits(regs->psw).ia);
+ if (unlikely(insn == BREAKPOINT_INSTRUCTION)) {
+ p = get_kprobe((void *)psw_bits(regs->psw).ia);
+ /*
+ * If the kprobe is concurrently removed on a different CPU
+ * it might not be found anymore. However text must have
+ * been restored - try again.
+ */
+ if (!p)
+ goto again;
+ insn = p->opcode;
+ }
+ if ((insn & 0xff0f) != 0xe300)
+ return true;
+ disp = offsetof(struct lowcore, percpu_offset);
+ if (machine_has_relocated_lowcore())
+ disp += LOWCORE_ALT_ADDRESS;
+ insn = (disp & 0xff000) >> 4 | (disp & 0x00fff) << 16 | 0x8;
+ if (*(u32 *)(psw_bits(regs->psw).ia + 2) != insn)
+ return true;
+ return false;
+}
+
+static __always_inline void percpu_exit(struct pt_regs *regs, bool needs_fixup)
+{
+ struct lowcore *lc = get_lowcore();
+ unsigned char reg;
+
+ if (user_mode(regs))
+ return;
+ reg = regs->percpu_register;
+ lc->percpu_register = reg;
+ if (likely(!needs_fixup))
+ return;
+ /* Check if process has been migrated to a different CPU. */
+ if (regs->cpu == lc->cpu_nr)
+ return;
+ /* Fixup percpu base register */
+ regs->gprs[reg] -= __per_cpu_offset[regs->cpu];
+ regs->gprs[reg] += lc->percpu_offset;
+}
+
+#endif