Commit 8707898e authored by Thomas Pfaff's avatar Thomas Pfaff Committed by Thomas Gleixner

genirq: Synchronize interrupt thread startup

A kernel hang can be observed when running setserial in a loop on a kernel
with force threaded interrupts. The sequence of events is:

   setserial
     open("/dev/ttyXXX")
       request_irq()
     do_stuff()
      -> serial interrupt
         -> wake(irq_thread)
	      desc->threads_active++;
     close()
       free_irq()
         kthread_stop(irq_thread)
     synchronize_irq() <- hangs because desc->threads_active != 0

The thread is created in request_irq() and woken up, but does not get on a
CPU to reach the actual thread function, which would handle the pending
wake-up. kthread_stop() sets the should stop condition which makes the
thread immediately exit, which in turn leaves the stale threads_active
count around.

This problem was introduced with commit 519cc865, which addressed a
interrupt sharing issue in the PCIe code.

Before that commit free_irq() invoked synchronize_irq(), which waits for
the hard interrupt handler and also for associated threads to complete.

To address the PCIe issue synchronize_irq() was replaced with
__synchronize_hardirq(), which only waits for the hard interrupt handler to
complete, but not for threaded handlers.

This was done under the assumption, that the interrupt thread already
reached the thread function and waits for a wake-up, which is guaranteed to
be handled before acting on the stop condition. The problematic case, that
the thread would not reach the thread function, was obviously overlooked.

Make sure that the interrupt thread is really started and reaches
thread_fn() before returning from __setup_irq().

This utilizes the existing wait queue in the interrupt descriptor. The
wait queue is unused for non-shared interrupts. For shared interrupts the
usage might cause a spurious wake-up of a waiter in synchronize_irq() or the
completion of a threaded handler might cause a spurious wake-up of the
waiter for the ready flag. Both are harmless and have no functional impact.

[ tglx: Amended changelog ]

Fixes: 519cc865 ("genirq: Synchronize only with single thread on free_irq()")
Signed-off-by: default avatarThomas Pfaff <tpfaff@pcs.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/552fe7b4-9224-b183-bb87-a8f36d335690@pcs.com
parent 672c0c51
...@@ -29,12 +29,14 @@ extern struct irqaction chained_action; ...@@ -29,12 +29,14 @@ extern struct irqaction chained_action;
* IRQTF_WARNED - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed * IRQTF_WARNED - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed
* IRQTF_AFFINITY - irq thread is requested to adjust affinity * IRQTF_AFFINITY - irq thread is requested to adjust affinity
* IRQTF_FORCED_THREAD - irq action is force threaded * IRQTF_FORCED_THREAD - irq action is force threaded
* IRQTF_READY - signals that irq thread is ready
*/ */
enum { enum {
IRQTF_RUNTHREAD, IRQTF_RUNTHREAD,
IRQTF_WARNED, IRQTF_WARNED,
IRQTF_AFFINITY, IRQTF_AFFINITY,
IRQTF_FORCED_THREAD, IRQTF_FORCED_THREAD,
IRQTF_READY,
}; };
/* /*
......
...@@ -407,6 +407,7 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags, ...@@ -407,6 +407,7 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
lockdep_set_class(&desc->lock, &irq_desc_lock_class); lockdep_set_class(&desc->lock, &irq_desc_lock_class);
mutex_init(&desc->request_mutex); mutex_init(&desc->request_mutex);
init_rcu_head(&desc->rcu); init_rcu_head(&desc->rcu);
init_waitqueue_head(&desc->wait_for_threads);
desc_set_defaults(irq, desc, node, affinity, owner); desc_set_defaults(irq, desc, node, affinity, owner);
irqd_set(&desc->irq_data, flags); irqd_set(&desc->irq_data, flags);
...@@ -575,6 +576,7 @@ int __init early_irq_init(void) ...@@ -575,6 +576,7 @@ int __init early_irq_init(void)
raw_spin_lock_init(&desc[i].lock); raw_spin_lock_init(&desc[i].lock);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
mutex_init(&desc[i].request_mutex); mutex_init(&desc[i].request_mutex);
init_waitqueue_head(&desc[i].wait_for_threads);
desc_set_defaults(i, &desc[i], node, NULL, NULL); desc_set_defaults(i, &desc[i], node, NULL, NULL);
} }
return arch_early_irq_init(); return arch_early_irq_init();
......
...@@ -1248,6 +1248,31 @@ static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action) ...@@ -1248,6 +1248,31 @@ static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action)
raw_spin_unlock_irq(&desc->lock); raw_spin_unlock_irq(&desc->lock);
} }
/*
* Internal function to notify that a interrupt thread is ready.
*/
static void irq_thread_set_ready(struct irq_desc *desc,
struct irqaction *action)
{
set_bit(IRQTF_READY, &action->thread_flags);
wake_up(&desc->wait_for_threads);
}
/*
* Internal function to wake up a interrupt thread and wait until it is
* ready.
*/
static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
struct irqaction *action)
{
if (!action || !action->thread)
return;
wake_up_process(action->thread);
wait_event(desc->wait_for_threads,
test_bit(IRQTF_READY, &action->thread_flags));
}
/* /*
* Interrupt handler thread * Interrupt handler thread
*/ */
...@@ -1259,6 +1284,8 @@ static int irq_thread(void *data) ...@@ -1259,6 +1284,8 @@ static int irq_thread(void *data)
irqreturn_t (*handler_fn)(struct irq_desc *desc, irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action); struct irqaction *action);
irq_thread_set_ready(desc, action);
sched_set_fifo(current); sched_set_fifo(current);
if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD, if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
...@@ -1683,8 +1710,6 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) ...@@ -1683,8 +1710,6 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
} }
if (!shared) { if (!shared) {
init_waitqueue_head(&desc->wait_for_threads);
/* Setup the type (level, edge polarity) if configured: */ /* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) { if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc, ret = __irq_set_trigger(desc,
...@@ -1780,14 +1805,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) ...@@ -1780,14 +1805,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
irq_setup_timings(desc, new); irq_setup_timings(desc, new);
/* wake_up_and_wait_for_irq_thread_ready(desc, new);
* Strictly no need to wake it up, but hung_task complains wake_up_and_wait_for_irq_thread_ready(desc, new->secondary);
* when no hard interrupt wakes the thread up.
*/
if (new->thread)
wake_up_process(new->thread);
if (new->secondary)
wake_up_process(new->secondary->thread);
register_irq_proc(irq, desc); register_irq_proc(irq, desc);
new->dir = NULL; new->dir = NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment