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