@@ -55,6 +55,7 @@
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
+ * IRQF_ADAPTIVE - Request notification about upcoming interrupt line sharing
*
*/
#define IRQF_DISABLED 0x00000020
@@ -67,6 +68,7 @@
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
+#define IRQF_ADAPTIVE 0x00008000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND)
@@ -126,6 +128,14 @@ struct irqaction {
extern irqreturn_t no_action(int cpl, void *dev_id);
+/*
+ * Driver-readable IRQ line status flags:
+ * IRQS_SHARED - line is shared between multiple handlers
+ * IRQS_MAKE_SHAREABLE - in the process of making an exclusive line shareable
+ */
+#define IRQS_SHARED 0x00000001
+#define IRQS_MAKE_SHAREABLE 0x00000002
+
extern unsigned long get_irq_status(unsigned int irq);
#ifdef CONFIG_GENERIC_HARDIRQS
@@ -14,9 +14,12 @@
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/sched.h>
+#include <linux/mutex.h>
#include "internals.h"
+static DEFINE_MUTEX(register_lock);
+
/**
* synchronize_irq - wait for pending IRQ handlers (on other CPUs)
* @irq: interrupt number to wait for
@@ -754,6 +757,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
old = *old_ptr;
} while (old);
shared = 1;
+
+ desc->irq_data.drv_status |= IRQS_SHARED;
}
if (!shared) {
@@ -883,6 +888,7 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc = irq_to_desc(irq);
struct irqaction *action, **action_ptr;
+ bool single_handler = false;
unsigned long flags;
WARN(in_interrupt(), "Trying to free IRQ %d from IRQ context!\n", irq);
@@ -928,7 +934,8 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
desc->irq_data.chip->irq_shutdown(&desc->irq_data);
else
desc->irq_data.chip->irq_disable(&desc->irq_data);
- }
+ } else if (!desc->action->next)
+ single_handler = true;
#ifdef CONFIG_SMP
/* make sure affinity_hint is cleaned up */
@@ -943,6 +950,9 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
/* Make sure it's not being used on another CPU: */
synchronize_irq(irq);
+ if (single_handler)
+ desc->irq_data.drv_status &= ~IRQS_SHARED;
+
#ifdef CONFIG_DEBUG_SHIRQ
/*
* It's a shared IRQ -- the driver ought to be prepared for an IRQ
@@ -1002,9 +1012,13 @@ void free_irq(unsigned int irq, void *dev_id)
if (!desc)
return;
+ mutex_lock(®ister_lock);
+
chip_bus_lock(desc);
kfree(__free_irq(irq, dev_id));
chip_bus_sync_unlock(desc);
+
+ mutex_unlock(®ister_lock);
}
EXPORT_SYMBOL(free_irq);
@@ -1055,7 +1069,7 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
- struct irqaction *action;
+ struct irqaction *action, *old_action;
struct irq_desc *desc;
int retval;
@@ -1091,12 +1105,39 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
action->name = devname;
action->dev_id = dev_id;
+ mutex_lock(®ister_lock);
+
+ old_action = desc->action;
+ if (old_action && (old_action->flags & IRQF_ADAPTIVE) &&
+ !(desc->irq_data.drv_status & IRQS_SHARED)) {
+ /*
+ * Signal the old handler that is has to switch to shareable
+ * handling mode. Disable the line to avoid any conflict with
+ * a real IRQ.
+ */
+ disable_irq(irq);
+ local_irq_disable();
+
+ desc->irq_data.drv_status |= IRQS_SHARED | IRQS_MAKE_SHAREABLE;
+ old_action->handler(irq, old_action->dev_id);
+ desc->irq_data.drv_status &= ~IRQS_MAKE_SHAREABLE;
+
+ local_irq_enable();
+ enable_irq(irq);
+
+ }
+
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
- if (retval)
+ if (retval) {
+ if (desc->action && !desc->action->next)
+ desc->irq_data.drv_status &= ~IRQS_SHARED;
kfree(action);
+ }
+
+ mutex_unlock(®ister_lock);
#ifdef CONFIG_DEBUG_SHIRQ
if (!retval && (irqflags & IRQF_SHARED)) {