| /* 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 |