[PATCH] irq: add disable_irq_handler()/enable_irq_handler() functions

From: Boris Brezillon
Date: Mon Aug 03 2015 - 15:59:29 EST


Sometime we need to disable a specific handler on a shared irq line.
Currently, the only way this can be achieved is by freeing the irq handler
when we want to disable the irq and creating a new one when we want to
enable.
This is not only adding some overhead to the disable/enable operations, but
the request_irq function cannot be called in atomic context, which means
it prevents disabling the interrupt in such situation.

This patch introduces three new functions: disable_irq_handler(),
disable_irq_handler_nosync() and enable_irq_handler() to allow disabling
a specific handler on a shared irq line.

Signed-off-by: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxxxxxxx>
---
include/linux/irqdesc.h | 1 +
kernel/irq/manage.c | 143 ++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 126 insertions(+), 18 deletions(-)

diff --git a/include/linux/irqdesc.h b/include/linux/irqdesc.h
index fcea4e4..c8bd055 100644
--- a/include/linux/irqdesc.h
+++ b/include/linux/irqdesc.h
@@ -52,6 +52,7 @@ struct irq_desc {
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
+ struct irqaction *disabled_actions; /* IRQ disabled action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index f974485..0e7432b 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -458,6 +458,47 @@ void disable_irq_nosync(unsigned int irq)
}
EXPORT_SYMBOL(disable_irq_nosync);

+static int __disable_irq_handler_nosync(unsigned int irq, void *dev_id)
+{
+ unsigned long flags;
+ struct irqaction *action, **prev;
+ struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
+ int ret = 0;
+
+ if (!desc)
+ return -EINVAL;
+
+ for (action = desc->action, prev = &desc->action; action; action = action->next) {
+ if (action->dev_id == dev_id)
+ break;
+
+ prev = &action->next;
+ }
+
+ if (!action)
+ goto out;
+
+ *prev = action->next;
+
+ action->next = desc->disabled_actions;
+ desc->disabled_actions = action;
+ if (!desc->action) {
+ __disable_irq(desc, irq);
+ ret = 1;
+ }
+
+out:
+ irq_put_desc_busunlock(desc, flags);
+ return ret;
+}
+
+static void disable_irq_handler_nosync(unsigned int irq, void *dev_id)
+{
+ __disable_irq_handler_nosync(irq, dev_id);
+}
+EXPORT_SYMBOL(disable_irq_handler_nosync);
+
+
/**
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
@@ -477,6 +518,13 @@ void disable_irq(unsigned int irq)
}
EXPORT_SYMBOL(disable_irq);

+void disable_irq_handler(unsigned int irq, )
+{
+ if (__disable_irq_handler_nosync(irq) > 0)
+ synchronize_irq(irq);
+}
+EXPORT_SYMBOL(disable_irq);
+
/**
* disable_hardirq - disables an irq and waits for hardirq completion
* @irq: Interrupt to disable
@@ -552,6 +600,40 @@ out:
}
EXPORT_SYMBOL(enable_irq);

+void enable_irq_handler(unsigned int irq, void *dev_id)
+{
+ unsigned long flags;
+ struct irqaction *action, **prev;
+ struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
+
+ if (!desc)
+ return -EINVAL;
+
+ for (action = desc->disabled_actions, prev = &desc->action; action;
+ action = action->next) {
+ if (action->dev_id == dev_id)
+ break;
+
+ prev = &action->next;
+ }
+
+ if (!action)
+ goto out;
+
+ *prev = action->next;
+
+ action->next = desc->action;
+ desc->action = action;
+
+ if (!action->next)
+ __enable_irq(desc, irq);
+
+out:
+ irq_put_desc_busunlock(desc, flags);
+
+}
+EXPORT_SYMBOL(enable_irq_handler);
+
static int set_irq_wake_real(unsigned int irq, unsigned int on)
{
struct irq_desc *desc = irq_to_desc(irq);
@@ -1118,6 +1200,12 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
+ if (!old) {
+ old_ptr = &desc->disabled_actions;
+ old = *old_ptr;
+ }
+
+ old = desc->action ? : desc->disabled_actions;
if (old) {
/*
* Can't share interrupts unless both agree to and are
@@ -1136,20 +1224,27 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
(new->flags & IRQF_PERCPU))
goto mismatch;

- /* add new interrupt at end of irq queue */
- do {
- /*
- * Or all existing action->thread_mask bits,
- * so we can find the next zero bit for this
- * new action.
- */
- thread_mask |= old->thread_mask;
- old_ptr = &old->next;
- old = *old_ptr;
- } while (old);
shared = 1;
}

+ if (irq_settings_can_autoenable(desc))
+ old_ptr = &desc->action;
+ else
+ old_ptr = &desc->disabled_actions;
+ old = *old_ptr;
+
+ /* add new interrupt at end of irq queue */
+ while (old) {
+ /*
+ * Or all existing action->thread_mask bits,
+ * so we can find the next zero bit for this
+ * new action.
+ */
+ thread_mask |= old->thread_mask;
+ old_ptr = &old->next;
+ old = *old_ptr;
+ }
+
/*
* Setup the thread mask for this irqaction for ONESHOT. For
* !ONESHOT irqs the thread mask is 0 so we can avoid a
@@ -1373,25 +1468,37 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
for (;;) {
action = *action_ptr;

- if (!action) {
- WARN(1, "Trying to free already-free IRQ %d\n", irq);
- raw_spin_unlock_irqrestore(&desc->lock, flags);
-
- return NULL;
- }

if (action->dev_id == dev_id)
break;
action_ptr = &action->next;
}

+ if (!action) {
+ action_ptr = &desc->disabled_actions;
+ for (;;) {
+ action = *action_ptr;
+
+ if (action->dev_id == dev_id)
+ break;
+ action_ptr = &action->next;
+ }
+ }
+
+ if (!action) {
+ WARN(1, "Trying to free already-free IRQ %d\n", irq);
+ raw_spin_unlock_irqrestore(&desc->lock, flags);
+
+ return NULL;
+ }
+
/* Found it - now remove it from the list of entries: */
*action_ptr = action->next;

irq_pm_remove_action(desc, action);

/* If this was the last handler, shut down the IRQ line: */
- if (!desc->action) {
+ if (!desc->action && !desc->disabled_actions) {
irq_shutdown(desc);
irq_release_resources(desc);
}
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/