]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
genirq/manage: Reduce priority of forced secondary interrupt handler
authorLukas Wunner <lukas@wunner.de>
Mon, 27 Oct 2025 12:59:31 +0000 (13:59 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Sat, 1 Nov 2025 20:30:02 +0000 (21:30 +0100)
Crystal reports that the PCIe Advanced Error Reporting driver gets stuck
in an infinite loop on PREEMPT_RT:

Both the primary interrupt handler aer_irq() as well as the secondary
handler aer_isr() are forced into threads with identical priority.
Crystal writes that on the ARM system in question, the primary handler
has to clear an error in the Root Error Status register...

   "before the next error happens, or else the hardware will set the
    Multiple ERR_COR Received bit.  If that bit is set, then aer_isr()
    can't rely on the Error Source Identification register, so it scans
    through all devices looking for errors -- and for some reason, on
    this system, accessing the AER registers (or any Config Space above
    0x400, even though there are capabilities located there) generates
    an Unsupported Request Error (but returns valid data).  Since this
    happens more than once, without aer_irq() preempting, it causes
    another multi error and we get stuck in a loop."

The issue does not show on non-PREEMPT_RT because the primary handler
runs in hardirq context and thus can preempt the threaded secondary
handler, clear the Root Error Status register and prevent the secondary
handler from getting stuck.

Emulate the same behavior on PREEMPT_RT by assigning a lower default
priority to the secondary handler if the primary handler is forced into
a thread.

Reported-by: Crystal Wood <crwood@redhat.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Crystal Wood <crwood@redhat.com>
Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Link: https://patch.msgid.link/f6dcdb41be2694886b8dbf4fe7b3ab89e9d5114c.1761569303.git.lukas@wunner.de
Closes: https://lore.kernel.org/r/20250902224441.368483-1-crwood@redhat.com/
include/linux/sched.h
kernel/irq/manage.c
kernel/sched/syscalls.c

index cbb7340c5866fba8ba64305e44a535c6adb54c69..cd6be74d87b8a3fea2018c2a01785aecd9aec7e2 100644 (file)
@@ -1901,6 +1901,7 @@ extern int sched_setscheduler(struct task_struct *, int, const struct sched_para
 extern int sched_setscheduler_nocheck(struct task_struct *, int, const struct sched_param *);
 extern void sched_set_fifo(struct task_struct *p);
 extern void sched_set_fifo_low(struct task_struct *p);
+extern void sched_set_fifo_secondary(struct task_struct *p);
 extern void sched_set_normal(struct task_struct *p, int nice);
 extern int sched_setattr(struct task_struct *, const struct sched_attr *);
 extern int sched_setattr_nocheck(struct task_struct *, const struct sched_attr *);
index 7a09d96f4d2d20e051ac9b0ee9c25d53fad539bc..c812b6f48f2b002c701e959bd7a8831c7ab00cd8 100644 (file)
@@ -1239,7 +1239,10 @@ static int irq_thread(void *data)
 
        irq_thread_set_ready(desc, action);
 
-       sched_set_fifo(current);
+       if (action->handler == irq_forced_secondary_handler)
+               sched_set_fifo_secondary(current);
+       else
+               sched_set_fifo(current);
 
        if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
                                           &action->thread_flags))
index 77ae87f36e841296c13266910c34a1d9a79659de..48347950ac4875f4dd6597048f30fc58f8e8bfe6 100644 (file)
@@ -856,6 +856,19 @@ void sched_set_fifo_low(struct task_struct *p)
 }
 EXPORT_SYMBOL_GPL(sched_set_fifo_low);
 
+/*
+ * Used when the primary interrupt handler is forced into a thread, in addition
+ * to the (always threaded) secondary handler.  The secondary handler gets a
+ * slightly lower priority so that the primary handler can preempt it, thereby
+ * emulating the behavior of a non-PREEMPT_RT system where the primary handler
+ * runs in hard interrupt context.
+ */
+void sched_set_fifo_secondary(struct task_struct *p)
+{
+       struct sched_param sp = { .sched_priority = MAX_RT_PRIO / 2 - 1 };
+       WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0);
+}
+
 void sched_set_normal(struct task_struct *p, int nice)
 {
        struct sched_attr attr = {