Skip to content

Commit 8707898

Browse files
tpfaff6130KAGA-KOKO
authored andcommitted
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 519cc86, 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: 519cc86 ("genirq: Synchronize only with single thread on free_irq()") Signed-off-by: Thomas Pfaff <tpfaff@pcs.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Marc Zyngier <maz@kernel.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/552fe7b4-9224-b183-bb87-a8f36d335690@pcs.com
1 parent 672c0c5 commit 8707898

File tree

3 files changed

+33
-10
lines changed

3 files changed

+33
-10
lines changed

kernel/irq/internals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ extern struct irqaction chained_action;
2929
* IRQTF_WARNED - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed
3030
* IRQTF_AFFINITY - irq thread is requested to adjust affinity
3131
* IRQTF_FORCED_THREAD - irq action is force threaded
32+
* IRQTF_READY - signals that irq thread is ready
3233
*/
3334
enum {
3435
IRQTF_RUNTHREAD,
3536
IRQTF_WARNED,
3637
IRQTF_AFFINITY,
3738
IRQTF_FORCED_THREAD,
39+
IRQTF_READY,
3840
};
3941

4042
/*

kernel/irq/irqdesc.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
407407
lockdep_set_class(&desc->lock, &irq_desc_lock_class);
408408
mutex_init(&desc->request_mutex);
409409
init_rcu_head(&desc->rcu);
410+
init_waitqueue_head(&desc->wait_for_threads);
410411

411412
desc_set_defaults(irq, desc, node, affinity, owner);
412413
irqd_set(&desc->irq_data, flags);
@@ -575,6 +576,7 @@ int __init early_irq_init(void)
575576
raw_spin_lock_init(&desc[i].lock);
576577
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
577578
mutex_init(&desc[i].request_mutex);
579+
init_waitqueue_head(&desc[i].wait_for_threads);
578580
desc_set_defaults(i, &desc[i], node, NULL, NULL);
579581
}
580582
return arch_early_irq_init();

kernel/irq/manage.c

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,31 @@ static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action)
12481248
raw_spin_unlock_irq(&desc->lock);
12491249
}
12501250

1251+
/*
1252+
* Internal function to notify that a interrupt thread is ready.
1253+
*/
1254+
static void irq_thread_set_ready(struct irq_desc *desc,
1255+
struct irqaction *action)
1256+
{
1257+
set_bit(IRQTF_READY, &action->thread_flags);
1258+
wake_up(&desc->wait_for_threads);
1259+
}
1260+
1261+
/*
1262+
* Internal function to wake up a interrupt thread and wait until it is
1263+
* ready.
1264+
*/
1265+
static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
1266+
struct irqaction *action)
1267+
{
1268+
if (!action || !action->thread)
1269+
return;
1270+
1271+
wake_up_process(action->thread);
1272+
wait_event(desc->wait_for_threads,
1273+
test_bit(IRQTF_READY, &action->thread_flags));
1274+
}
1275+
12511276
/*
12521277
* Interrupt handler thread
12531278
*/
@@ -1259,6 +1284,8 @@ static int irq_thread(void *data)
12591284
irqreturn_t (*handler_fn)(struct irq_desc *desc,
12601285
struct irqaction *action);
12611286

1287+
irq_thread_set_ready(desc, action);
1288+
12621289
sched_set_fifo(current);
12631290

12641291
if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
@@ -1683,8 +1710,6 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
16831710
}
16841711

16851712
if (!shared) {
1686-
init_waitqueue_head(&desc->wait_for_threads);
1687-
16881713
/* Setup the type (level, edge polarity) if configured: */
16891714
if (new->flags & IRQF_TRIGGER_MASK) {
16901715
ret = __irq_set_trigger(desc,
@@ -1780,14 +1805,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
17801805

17811806
irq_setup_timings(desc, new);
17821807

1783-
/*
1784-
* Strictly no need to wake it up, but hung_task complains
1785-
* when no hard interrupt wakes the thread up.
1786-
*/
1787-
if (new->thread)
1788-
wake_up_process(new->thread);
1789-
if (new->secondary)
1790-
wake_up_process(new->secondary->thread);
1808+
wake_up_and_wait_for_irq_thread_ready(desc, new);
1809+
wake_up_and_wait_for_irq_thread_ready(desc, new->secondary);
17911810

17921811
register_irq_proc(irq, desc);
17931812
new->dir = NULL;

0 commit comments

Comments
 (0)