aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/irq/manage.c
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@linutronix.de>2010-03-09 13:45:54 -0500
committerThomas Gleixner <tglx@linutronix.de>2010-03-10 11:45:14 -0500
commit0b1adaa031a55e44f5dd942f234bf09d28e8a0d6 (patch)
tree354aa6cbfbcd856226c543b9f263f87864245065 /kernel/irq/manage.c
parent522dba7134d6b2e5821d3457f7941ec34f668e6d (diff)
genirq: Prevent oneshot irq thread race
Lars-Peter pointed out that the oneshot threaded interrupt handler code has the following race: CPU0 CPU1 hande_level_irq(irq X) mask_ack_irq(irq X) handle_IRQ_event(irq X) wake_up(thread_handler) thread handler(irq X) runs finalize_oneshot(irq X) does not unmask due to !(desc->status & IRQ_MASKED) return from irq does not unmask due to (desc->status & IRQ_ONESHOT) This leaves the interrupt line masked forever. The reason for this is the inconsistent handling of the IRQ_MASKED flag. Instead of setting it in the mask function the oneshot support sets the flag after waking up the irq thread. The solution for this is to set/clear the IRQ_MASKED status whenever we mask/unmask an interrupt line. That's the easy part, but that cleanup opens another race: CPU0 CPU1 hande_level_irq(irq) mask_ack_irq(irq) handle_IRQ_event(irq) wake_up(thread_handler) thread handler(irq) runs finalize_oneshot_irq(irq) unmask(irq) irq triggers again handle_level_irq(irq) mask_ack_irq(irq) return from irq due to IRQ_INPROGRESS return from irq does not unmask due to (desc->status & IRQ_ONESHOT) This requires that we synchronize finalize_oneshot_irq() with the primary handler. If IRQ_INPROGESS is set we wait until the primary handler on the other CPU has returned before unmasking the interrupt line again. We probably have never seen that problem because it does not happen on UP and on SMP the irqbalancer protects us by pinning the primary handler and the thread to the same CPU. Reported-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: stable@kernel.org
Diffstat (limited to 'kernel/irq/manage.c')
-rw-r--r--kernel/irq/manage.c18
1 files changed, 18 insertions, 0 deletions
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index eb6078ca60c7..69a3d7b9414c 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -483,8 +483,26 @@ static int irq_wait_for_interrupt(struct irqaction *action)
483 */ 483 */
484static void irq_finalize_oneshot(unsigned int irq, struct irq_desc *desc) 484static void irq_finalize_oneshot(unsigned int irq, struct irq_desc *desc)
485{ 485{
486again:
486 chip_bus_lock(irq, desc); 487 chip_bus_lock(irq, desc);
487 raw_spin_lock_irq(&desc->lock); 488 raw_spin_lock_irq(&desc->lock);
489
490 /*
491 * Implausible though it may be we need to protect us against
492 * the following scenario:
493 *
494 * The thread is faster done than the hard interrupt handler
495 * on the other CPU. If we unmask the irq line then the
496 * interrupt can come in again and masks the line, leaves due
497 * to IRQ_INPROGRESS and the irq line is masked forever.
498 */
499 if (unlikely(desc->status & IRQ_INPROGRESS)) {
500 raw_spin_unlock_irq(&desc->lock);
501 chip_bus_sync_unlock(irq, desc);
502 cpu_relax();
503 goto again;
504 }
505
488 if (!(desc->status & IRQ_DISABLED) && (desc->status & IRQ_MASKED)) { 506 if (!(desc->status & IRQ_DISABLED) && (desc->status & IRQ_MASKED)) {
489 desc->status &= ~IRQ_MASKED; 507 desc->status &= ~IRQ_MASKED;
490 desc->chip->unmask(irq); 508 desc->chip->unmask(irq);