Skip to content

Commit

Permalink
genirq: reenable a nobody cared disabled irq when a new driver arrives
Browse files Browse the repository at this point in the history
Uwe Kleine-Koenig has some strange hardware where one of the shared
interrupts can be asserted during boot before the appropriate driver
loads. Requesting the shared irq line from another driver result in a
spurious interrupt storm which finally disables the interrupt line.

I have seen similar behaviour on resume before (the hardware does not
work anymore so I can not verify).

Change the spurious disable logic to increment the disable depth and
mark the interrupt with an extra flag which allows us to reenable the
interrupt when a new driver arrives which requests the same irq
line. In the worst case this will disable the irq again via the
spurious trap, but there is a decent chance that the new driver is the
one which can handle the already asserted interrupt and makes the box
usable again.

Eric Biederman said further: This case also happens on a regular basis
in kdump kernels where we deliberately don't shutdown the hardware
before starting the new kernel.  This patch should reduce the need for
using irqpoll in that situation by a small amount.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-and-Acked-by: Uwe Kleine-König <Uwe.Kleine-Koenig@digi.com>
  • Loading branch information
Thomas Gleixner committed May 2, 2008
1 parent 886c35f commit 1adb085
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 19 deletions.
1 change: 1 addition & 0 deletions include/linux/irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ typedef void (*irq_flow_handler_t)(unsigned int irq,
#define IRQ_WAKEUP 0x00100000 /* IRQ triggers system wakeup */
#define IRQ_MOVE_PENDING 0x00200000 /* need to re-target IRQ destination */
#define IRQ_NO_BALANCING 0x00400000 /* IRQ is excluded from balancing */
#define IRQ_SPURIOUS_DISABLED 0x00800000 /* IRQ was disabled by the spurious trap */

#ifdef CONFIG_IRQ_PER_CPU
# define CHECK_IRQ_PER_CPU(var) ((var) & IRQ_PER_CPU)
Expand Down
49 changes: 32 additions & 17 deletions kernel/irq/manage.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ void disable_irq(unsigned int irq)
}
EXPORT_SYMBOL(disable_irq);

static void __enable_irq(struct irq_desc *desc, unsigned int irq)
{
switch (desc->depth) {
case 0:
printk(KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
WARN_ON(1);
break;
case 1: {
unsigned int status = desc->status & ~IRQ_DISABLED;

/* Prevent probing on this irq: */
desc->status = status | IRQ_NOPROBE;
check_irq_resend(desc, irq);
/* fall-through */
}
default:
desc->depth--;
}
}

/**
* enable_irq - enable handling of an irq
* @irq: Interrupt to enable
Expand All @@ -169,22 +189,7 @@ void enable_irq(unsigned int irq)
return;

spin_lock_irqsave(&desc->lock, flags);
switch (desc->depth) {
case 0:
printk(KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
WARN_ON(1);
break;
case 1: {
unsigned int status = desc->status & ~IRQ_DISABLED;

/* Prevent probing on this irq: */
desc->status = status | IRQ_NOPROBE;
check_irq_resend(desc, irq);
/* fall-through */
}
default:
desc->depth--;
}
__enable_irq(desc, irq);
spin_unlock_irqrestore(&desc->lock, flags);
}
EXPORT_SYMBOL(enable_irq);
Expand Down Expand Up @@ -365,7 +370,7 @@ int setup_irq(unsigned int irq, struct irqaction *new)
compat_irq_chip_set_default_handler(desc);

desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);
IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);

if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = 0;
Expand All @@ -381,6 +386,16 @@ int setup_irq(unsigned int irq, struct irqaction *new)
/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;

/*
* Check whether we disabled the irq via the spurious handler
* before. Reenable it and give it another chance.
*/
if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
desc->status &= ~IRQ_SPURIOUS_DISABLED;
__enable_irq(desc, irq);
}

spin_unlock_irqrestore(&desc->lock, flags);

new->irq = irq;
Expand Down
4 changes: 2 additions & 2 deletions kernel/irq/spurious.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ void note_interrupt(unsigned int irq, struct irq_desc *desc,
* Now kill the IRQ
*/
printk(KERN_EMERG "Disabling IRQ #%d\n", irq);
desc->status |= IRQ_DISABLED;
desc->depth = 1;
desc->status |= IRQ_DISABLED | IRQ_SPURIOUS_DISABLED;
desc->depth++;
desc->chip->disable(irq);
}
desc->irqs_unhandled = 0;
Expand Down

0 comments on commit 1adb085

Please sign in to comment.