* [PATCH 1/2] powerpc/64/interrupt: avoid BUG/WARN recursion in interrupt entry
2022-09-07 12:03 [PATCH 0/2] powerpc/64: more soft-mask improvements Nicholas Piggin
@ 2022-09-07 12:03 ` Nicholas Piggin
2022-09-07 12:03 ` [PATCH 2/2] powerpc/64/irq: tidy soft-masked irq replay and improve documentation Nicholas Piggin
1 sibling, 0 replies; 3+ messages in thread
From: Nicholas Piggin @ 2022-09-07 12:03 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Nicholas Piggin
BUG/WARN are handled with a program interrupt which can turn into an
infinite recursion when there are bugs in interrupt handler entry
(which can be irritated by bugs in other parts of the code).
There is one feeble attempt to avoid this recursion, but it misses
several cases. Make a tidier macro for this and switch most bugs in
the interrupt entry wrapper over to use it.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/include/asm/interrupt.h | 33 +++++++++++++++++-----------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/arch/powerpc/include/asm/interrupt.h b/arch/powerpc/include/asm/interrupt.h
index 8069dbc4b8d1..690ca27d8dd1 100644
--- a/arch/powerpc/include/asm/interrupt.h
+++ b/arch/powerpc/include/asm/interrupt.h
@@ -74,6 +74,19 @@
#include <asm/kprobes.h>
#include <asm/runlatch.h>
+#ifdef CONFIG_PPC64
+/*
+ * WARN/BUG is handled with a program interrupt so minimise checks here to
+ * avoid recursion and maximise the chance of getting the first oops handled.
+ */
+#define INT_SOFT_MASK_BUG_ON(regs, cond) \
+do { \
+ if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG) && \
+ (user_mode(regs) || (TRAP(regs) != INTERRUPT_PROGRAM))) \
+ BUG_ON(cond); \
+} while (0)
+#endif
+
#ifdef CONFIG_PPC_BOOK3S_64
extern char __end_soft_masked[];
bool search_kernel_soft_mask_table(unsigned long addr);
@@ -170,8 +183,7 @@ static inline void interrupt_enter_prepare(struct pt_regs *regs)
* context.
*/
if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS)) {
- if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
- BUG_ON(!(regs->msr & MSR_EE));
+ INT_SOFT_MASK_BUG_ON(regs, !(regs->msr & MSR_EE));
__hard_irq_enable();
} else {
__hard_RI_enable();
@@ -194,19 +206,14 @@ static inline void interrupt_enter_prepare(struct pt_regs *regs)
* CT_WARN_ON comes here via program_check_exception,
* so avoid recursion.
*/
- if (TRAP(regs) != INTERRUPT_PROGRAM) {
+ if (TRAP(regs) != INTERRUPT_PROGRAM)
CT_WARN_ON(ct_state() != CONTEXT_KERNEL);
- if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
- BUG_ON(is_implicit_soft_masked(regs));
- }
-
- /* Move this under a debugging check */
- if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG) &&
- arch_irq_disabled_regs(regs))
- BUG_ON(search_kernel_restart_table(regs->nip));
+ INT_SOFT_MASK_BUG_ON(regs, is_implicit_soft_masked(regs));
+ INT_SOFT_MASK_BUG_ON(regs, arch_irq_disabled_regs(regs) &&
+ search_kernel_restart_table(regs->nip));
}
- if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
- BUG_ON(!arch_irq_disabled_regs(regs) && !(regs->msr & MSR_EE));
+ INT_SOFT_MASK_BUG_ON(regs, !arch_irq_disabled_regs(regs) &&
+ !(regs->msr & MSR_EE));
#endif
booke_restore_dbcr0();
--
2.37.2
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH 2/2] powerpc/64/irq: tidy soft-masked irq replay and improve documentation
2022-09-07 12:03 [PATCH 0/2] powerpc/64: more soft-mask improvements Nicholas Piggin
2022-09-07 12:03 ` [PATCH 1/2] powerpc/64/interrupt: avoid BUG/WARN recursion in interrupt entry Nicholas Piggin
@ 2022-09-07 12:03 ` Nicholas Piggin
1 sibling, 0 replies; 3+ messages in thread
From: Nicholas Piggin @ 2022-09-07 12:03 UTC (permalink / raw)
To: linuxppc-dev; +Cc: Nicholas Piggin
irq replay is quite complicated because of softirq processing which
itself enables and disables irqs. Several considerations need to be
accounted for due to this, and they are not clearly documented.
Refactor the irq replay code a bit to tidy and deduplicate some common
functions. Add comments, debug checks.
This has a minor functional change that irq tracing enable/disable is
done after each interrupt replayed, rather than after a batch. It also
re-sets state to IRQS_ALL_DISABLED after an interrupt, which doesn't
matter much because interrupts are hard disabled at this point, but it
is more consistent with how interrupt handlers are called.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/kernel/irq_64.c | 93 +++++++++++++++++++++++-------------
1 file changed, 61 insertions(+), 32 deletions(-)
diff --git a/arch/powerpc/kernel/irq_64.c b/arch/powerpc/kernel/irq_64.c
index 01645e03e9f0..eb2b380e52a0 100644
--- a/arch/powerpc/kernel/irq_64.c
+++ b/arch/powerpc/kernel/irq_64.c
@@ -68,6 +68,35 @@
int distribute_irqs = 1;
+static inline void next_interrupt(struct pt_regs *regs)
+{
+ /*
+ * Softirq processing can enable/disable irqs, which will leave
+ * MSR[EE] enabled and the soft mask set to IRQS_DISABLED. Fix
+ * this up.
+ */
+ if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
+ hard_irq_disable();
+ else
+ irq_soft_mask_set(IRQS_ALL_DISABLED);
+
+ /*
+ * We are responding to the next interrupt, so interrupt-off
+ * latencies should be reset here.
+ */
+ trace_hardirqs_on();
+ trace_hardirqs_off();
+}
+
+static inline bool irq_happened_test_and_clear(u8 irq)
+{
+ if (local_paca->irq_happened & irq) {
+ local_paca->irq_happened &= ~irq;
+ return true;
+ }
+ return false;
+}
+
void replay_soft_interrupts(void)
{
struct pt_regs regs;
@@ -79,18 +108,25 @@ void replay_soft_interrupts(void)
* recurse into this function. Don't keep any state across
* interrupt handler calls which may change underneath us.
*
+ * Softirqs can not be disabled over replay to stop this recursion
+ * because interrupts taken in idle code may require RCU softirq
+ * to run in the irq RCU tracking context. This is a hard problem
+ * to fix without changes to the softirq or idle layer.
+ *
* We use local_paca rather than get_paca() to avoid all the
* debug_smp_processor_id() business in this low level function.
*/
+ if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG)) {
+ WARN_ON_ONCE(mfmsr() & MSR_EE);
+ WARN_ON(!(local_paca->irq_happened & PACA_IRQ_HARD_DIS));
+ }
+
ppc_save_regs(®s);
regs.softe = IRQS_ENABLED;
regs.msr |= MSR_EE;
again:
- if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
- WARN_ON_ONCE(mfmsr() & MSR_EE);
-
/*
* Force the delivery of pending soft-disabled interrupts on PS3.
* Any HV call will have this side effect.
@@ -105,56 +141,47 @@ void replay_soft_interrupts(void)
* This is a higher priority interrupt than the others, so
* replay it first.
*/
- if (IS_ENABLED(CONFIG_PPC_BOOK3S) && (local_paca->irq_happened & PACA_IRQ_HMI)) {
- local_paca->irq_happened &= ~PACA_IRQ_HMI;
+ if (IS_ENABLED(CONFIG_PPC_BOOK3S) &&
+ irq_happened_test_and_clear(PACA_IRQ_HMI)) {
regs.trap = INTERRUPT_HMI;
handle_hmi_exception(®s);
- if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
- hard_irq_disable();
+ next_interrupt(®s);
}
- if (local_paca->irq_happened & PACA_IRQ_DEC) {
- local_paca->irq_happened &= ~PACA_IRQ_DEC;
+ if (irq_happened_test_and_clear(PACA_IRQ_DEC)) {
regs.trap = INTERRUPT_DECREMENTER;
timer_interrupt(®s);
- if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
- hard_irq_disable();
+ next_interrupt(®s);
}
- if (local_paca->irq_happened & PACA_IRQ_EE) {
- local_paca->irq_happened &= ~PACA_IRQ_EE;
+ if (irq_happened_test_and_clear(PACA_IRQ_EE)) {
regs.trap = INTERRUPT_EXTERNAL;
do_IRQ(®s);
- if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
- hard_irq_disable();
+ next_interrupt(®s);
}
- if (IS_ENABLED(CONFIG_PPC_DOORBELL) && (local_paca->irq_happened & PACA_IRQ_DBELL)) {
- local_paca->irq_happened &= ~PACA_IRQ_DBELL;
+ if (IS_ENABLED(CONFIG_PPC_DOORBELL) &&
+ irq_happened_test_and_clear(PACA_IRQ_DBELL)) {
regs.trap = INTERRUPT_DOORBELL;
doorbell_exception(®s);
- if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
- hard_irq_disable();
+ next_interrupt(®s);
}
/* Book3E does not support soft-masking PMI interrupts */
- if (IS_ENABLED(CONFIG_PPC_BOOK3S) && (local_paca->irq_happened & PACA_IRQ_PMI)) {
- local_paca->irq_happened &= ~PACA_IRQ_PMI;
+ if (IS_ENABLED(CONFIG_PPC_BOOK3S) &&
+ irq_happened_test_and_clear(PACA_IRQ_PMI)) {
regs.trap = INTERRUPT_PERFMON;
performance_monitor_exception(®s);
- if (!(local_paca->irq_happened & PACA_IRQ_HARD_DIS))
- hard_irq_disable();
+ next_interrupt(®s);
}
- if (local_paca->irq_happened & ~PACA_IRQ_HARD_DIS) {
- /*
- * We are responding to the next interrupt, so interrupt-off
- * latencies should be reset here.
- */
- trace_hardirqs_on();
- trace_hardirqs_off();
+ /*
+ * Softirq processing can enable and disable interrupts, which can
+ * result in new irqs becoming pending. Must keep looping until we
+ * have cleared out all pending interrupts.
+ */
+ if (local_paca->irq_happened & ~PACA_IRQ_HARD_DIS)
goto again;
- }
}
#if defined(CONFIG_PPC_BOOK3S_64) && defined(CONFIG_PPC_KUAP)
@@ -270,10 +297,12 @@ notrace void arch_local_irq_restore(unsigned long mask)
trace_hardirqs_off();
replay_soft_interrupts_irqrestore();
- local_paca->irq_happened = 0;
trace_hardirqs_on();
irq_soft_mask_set(IRQS_ENABLED);
+ if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
+ WARN_ON(local_paca->irq_happened != PACA_IRQ_HARD_DIS);
+ local_paca->irq_happened = 0;
__hard_irq_enable();
preempt_enable();
}
--
2.37.2
^ permalink raw reply related [flat|nested] 3+ messages in thread