The regular kernel stack can not be accessed in real mode in hash
guest kernels, which prevents the MMU from being disabled in general
C code. Provide a helper that can call a function pointer in real
mode using the emergency stack (accessable in real mode).

Signed-off-by: Nicholas Piggin <npig...@gmail.com>
---
 arch/powerpc/include/asm/asm-prototypes.h |  1 +
 arch/powerpc/include/asm/book3s/64/mmu.h  |  2 +
 arch/powerpc/include/asm/thread_info.h    | 16 ++++++++
 arch/powerpc/kernel/irq.c                 | 16 --------
 arch/powerpc/kernel/misc_64.S             | 22 +++++++++++
 arch/powerpc/mm/book3s64/pgtable.c        | 48 +++++++++++++++++++++++
 6 files changed, 89 insertions(+), 16 deletions(-)

diff --git a/arch/powerpc/include/asm/asm-prototypes.h 
b/arch/powerpc/include/asm/asm-prototypes.h
index d0b832cbbec8..a973023c390a 100644
--- a/arch/powerpc/include/asm/asm-prototypes.h
+++ b/arch/powerpc/include/asm/asm-prototypes.h
@@ -126,6 +126,7 @@ extern s64 __ashldi3(s64, int);
 extern s64 __ashrdi3(s64, int);
 extern int __cmpdi2(s64, s64);
 extern int __ucmpdi2(u64, u64);
+int __call_realmode(int (*fn)(void *arg), void *arg, void *sp);
 
 /* tracing */
 void _mcount(void);
diff --git a/arch/powerpc/include/asm/book3s/64/mmu.h 
b/arch/powerpc/include/asm/book3s/64/mmu.h
index 995bbcdd0ef8..80b0d24415ac 100644
--- a/arch/powerpc/include/asm/book3s/64/mmu.h
+++ b/arch/powerpc/include/asm/book3s/64/mmu.h
@@ -274,5 +274,7 @@ static inline unsigned long get_user_vsid(mm_context_t *ctx,
        return get_vsid(context, ea, ssize);
 }
 
+int call_realmode(int (*fn)(void *arg), void *arg);
+
 #endif /* __ASSEMBLY__ */
 #endif /* _ASM_POWERPC_BOOK3S_64_MMU_H_ */
diff --git a/arch/powerpc/include/asm/thread_info.h 
b/arch/powerpc/include/asm/thread_info.h
index 3d8a47af7a25..9279e472d51e 100644
--- a/arch/powerpc/include/asm/thread_info.h
+++ b/arch/powerpc/include/asm/thread_info.h
@@ -172,6 +172,22 @@ static inline bool test_thread_local_flags(unsigned int 
flags)
 #define is_elf2_task() (0)
 #endif
 
+static inline void check_stack_overflow(void)
+{
+       long sp;
+
+       if (!IS_ENABLED(CONFIG_DEBUG_STACKOVERFLOW))
+               return;
+
+       sp = current_stack_pointer & (THREAD_SIZE - 1);
+
+       /* check for stack overflow: is there less than 2KB free? */
+       if (unlikely(sp < 2048)) {
+               pr_err("do_IRQ: stack overflow: %ld\n", sp);
+               dump_stack();
+       }
+}
+
 #endif /* !__ASSEMBLY__ */
 
 #endif /* __KERNEL__ */
diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c
index 6b1eca53e36c..193b47b5b6a5 100644
--- a/arch/powerpc/kernel/irq.c
+++ b/arch/powerpc/kernel/irq.c
@@ -620,22 +620,6 @@ u64 arch_irq_stat_cpu(unsigned int cpu)
        return sum;
 }
 
-static inline void check_stack_overflow(void)
-{
-       long sp;
-
-       if (!IS_ENABLED(CONFIG_DEBUG_STACKOVERFLOW))
-               return;
-
-       sp = current_stack_pointer & (THREAD_SIZE - 1);
-
-       /* check for stack overflow: is there less than 2KB free? */
-       if (unlikely(sp < 2048)) {
-               pr_err("do_IRQ: stack overflow: %ld\n", sp);
-               dump_stack();
-       }
-}
-
 void __do_irq(struct pt_regs *regs)
 {
        unsigned int irq;
diff --git a/arch/powerpc/kernel/misc_64.S b/arch/powerpc/kernel/misc_64.S
index 070465825c21..5e911d0b0b16 100644
--- a/arch/powerpc/kernel/misc_64.S
+++ b/arch/powerpc/kernel/misc_64.S
@@ -27,6 +27,28 @@
 
        .text
 
+#ifdef CONFIG_PPC_BOOK3S_64
+_GLOBAL(__call_realmode)
+       mflr    r0
+       std     r0,16(r1)
+       stdu    r1,THREAD_SIZE-STACK_FRAME_OVERHEAD(r5)
+       mr      r1,r5
+       mtctr   r3
+       mr      r3,r4
+       mfmsr   r4
+       xori    r4,r4,(MSR_IR|MSR_DR)
+       mtmsrd  r4
+       bctrl
+       mfmsr   r4
+       xori    r4,r4,(MSR_IR|MSR_DR)
+       mtmsrd  r4
+       ld      r1,0(r1)
+       ld      r0,16(r1)
+       mtlr    r0
+       blr
+
+#endif
+
 _GLOBAL(call_do_softirq)
        mflr    r0
        std     r0,16(r1)
diff --git a/arch/powerpc/mm/book3s64/pgtable.c 
b/arch/powerpc/mm/book3s64/pgtable.c
index 5b3a3bae21aa..aad0e2059305 100644
--- a/arch/powerpc/mm/book3s64/pgtable.c
+++ b/arch/powerpc/mm/book3s64/pgtable.c
@@ -474,6 +474,54 @@ int pmd_move_must_withdraw(struct spinlock *new_pmd_ptl,
        return true;
 }
 
+/*
+ * Executing C code in real-mode in general Book3S-64 code can only be done
+ * via this function that switches the stack to one inside the real-mode-area,
+ * which may cover only a small first part of real memory on hash guest LPARs.
+ * fn must be NOKPROBES, must not access vmalloc or anything outside the RMA,
+ * probably shouldn't enable the MMU or interrupts, etc, and be very careful
+ * about calling other generic kernel or powerpc functions.
+ */
+int call_realmode(int (*fn)(void *arg), void *arg)
+{
+       unsigned long flags;
+       void *cursp, *emsp;
+       int ret;
+
+       if (WARN_ON_ONCE(!(mfmsr() & MSR_DR)))
+               return -EINVAL;
+       if (WARN_ON_ONCE(!(mfmsr() & MSR_IR)))
+               return -EINVAL;
+
+       /*
+        * The switch to emergency stack is only really required for HPT LPAR,
+        * but do it for all to help test coverage of tricky code.
+        */
+       cursp = (void *)(current_stack_pointer & ~(THREAD_SIZE - 1));
+       emsp = (void *)(local_paca->emergency_sp - THREAD_SIZE);
+
+       /*
+        * It's probably okay to go to real-mode and call directly in case we
+        * are already on the emergency stack, so allow it. But we may want to
+        * prevent callers from doing this in future though, so warn.
+        */
+       WARN_ON_ONCE(cursp == emsp);
+
+       check_stack_overflow();
+
+       local_irq_save(flags);
+       hard_irq_disable();
+
+       if (cursp == emsp)
+               ret = fn(arg);
+       else
+               ret = __call_realmode(fn, arg, emsp);
+
+       local_irq_restore(flags);
+
+       return ret;
+}
+
 /*
  * Does the CPU support tlbie?
  */
-- 
2.23.0

Reply via email to