| From b26b3d7e3cb934148b64b1276087761b30a5b1be Mon Sep 17 00:00:00 2001 |
| From: Sebastian Andrzej Siewior <sebastian@breakpoint.cc> |
| Date: Mon, 11 Jul 2011 12:17:31 +0200 |
| Subject: irq: Track the owner of irq descriptor |
| |
| Interrupt descriptors can be allocated from modules. The interrupts |
| are used by other modules, but we have no refcount on the module which |
| provides the interrupts and there is no way to establish one on the |
| device level as the interrupt using module is agnostic to the fact |
| that the interrupt is provided by a module rather than by some builtin |
| interrupt controller. |
| |
| To prevent removal of the interrupt providing module, we can track the |
| owner of the interrupt descriptor, which also provides the relevant |
| irq chip functions in the irq descriptor. |
| |
| request/setup_irq() can now acquire a refcount on the owner module to |
| prevent unloading. free_irq() drops the refcount. |
| |
| Signed-off-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc> |
| Link: http://lkml.kernel.org/r/20110711101731.GA13804@Chamillionaire.breakpoint.cc |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| (cherry picked from commit b6873807a7143b7d6d8b06809295e559d07d7deb) |
| |
| Signed-off-by: Simon Horman <horms@verge.net.au> |
| --- |
| include/linux/irq.h | 11 ++++++++++- |
| include/linux/irqdesc.h | 1 + |
| kernel/irq/irqdesc.c | 36 ++++++++++++++++++++++++------------ |
| kernel/irq/manage.c | 17 +++++++++++++---- |
| 4 files changed, 48 insertions(+), 17 deletions(-) |
| |
| --- a/include/linux/irq.h |
| +++ b/include/linux/irq.h |
| @@ -23,6 +23,7 @@ |
| #include <linux/errno.h> |
| #include <linux/topology.h> |
| #include <linux/wait.h> |
| +#include <linux/module.h> |
| |
| #include <asm/irq.h> |
| #include <asm/ptrace.h> |
| @@ -548,7 +549,15 @@ static inline struct msi_desc *irq_data_ |
| return d->msi_desc; |
| } |
| |
| -int irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node); |
| +int __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node, |
| + struct module *owner); |
| + |
| +static inline int irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, |
| + int node) |
| +{ |
| + return __irq_alloc_descs(irq, from, cnt, node, THIS_MODULE); |
| +} |
| + |
| void irq_free_descs(unsigned int irq, unsigned int cnt); |
| int irq_reserve_irqs(unsigned int from, unsigned int cnt); |
| |
| --- a/include/linux/irqdesc.h |
| +++ b/include/linux/irqdesc.h |
| @@ -65,6 +65,7 @@ struct irq_desc { |
| #ifdef CONFIG_PROC_FS |
| struct proc_dir_entry *dir; |
| #endif |
| + struct module *owner; |
| const char *name; |
| } ____cacheline_internodealigned_in_smp; |
| |
| --- a/kernel/irq/irqdesc.c |
| +++ b/kernel/irq/irqdesc.c |
| @@ -70,7 +70,8 @@ static inline void desc_smp_init(struct |
| static inline int desc_node(struct irq_desc *desc) { return 0; } |
| #endif |
| |
| -static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node) |
| +static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node, |
| + struct module *owner) |
| { |
| int cpu; |
| |
| @@ -86,6 +87,7 @@ static void desc_set_defaults(unsigned i |
| desc->irq_count = 0; |
| desc->irqs_unhandled = 0; |
| desc->name = NULL; |
| + desc->owner = owner; |
| for_each_possible_cpu(cpu) |
| *per_cpu_ptr(desc->kstat_irqs, cpu) = 0; |
| desc_smp_init(desc, node); |
| @@ -128,7 +130,7 @@ static void free_masks(struct irq_desc * |
| static inline void free_masks(struct irq_desc *desc) { } |
| #endif |
| |
| -static struct irq_desc *alloc_desc(int irq, int node) |
| +static struct irq_desc *alloc_desc(int irq, int node, struct module *owner) |
| { |
| struct irq_desc *desc; |
| gfp_t gfp = GFP_KERNEL; |
| @@ -147,7 +149,7 @@ static struct irq_desc *alloc_desc(int i |
| raw_spin_lock_init(&desc->lock); |
| lockdep_set_class(&desc->lock, &irq_desc_lock_class); |
| |
| - desc_set_defaults(irq, desc, node); |
| + desc_set_defaults(irq, desc, node, owner); |
| |
| return desc; |
| |
| @@ -173,13 +175,14 @@ static void free_desc(unsigned int irq) |
| kfree(desc); |
| } |
| |
| -static int alloc_descs(unsigned int start, unsigned int cnt, int node) |
| +static int alloc_descs(unsigned int start, unsigned int cnt, int node, |
| + struct module *owner) |
| { |
| struct irq_desc *desc; |
| int i; |
| |
| for (i = 0; i < cnt; i++) { |
| - desc = alloc_desc(start + i, node); |
| + desc = alloc_desc(start + i, node, owner); |
| if (!desc) |
| goto err; |
| mutex_lock(&sparse_irq_lock); |
| @@ -227,7 +230,7 @@ int __init early_irq_init(void) |
| nr_irqs = initcnt; |
| |
| for (i = 0; i < initcnt; i++) { |
| - desc = alloc_desc(i, node); |
| + desc = alloc_desc(i, node, NULL); |
| set_bit(i, allocated_irqs); |
| irq_insert_desc(i, desc); |
| } |
| @@ -261,7 +264,7 @@ int __init early_irq_init(void) |
| alloc_masks(&desc[i], GFP_KERNEL, node); |
| raw_spin_lock_init(&desc[i].lock); |
| lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); |
| - desc_set_defaults(i, &desc[i], node); |
| + desc_set_defaults(i, &desc[i], node, NULL); |
| } |
| return arch_early_irq_init(); |
| } |
| @@ -276,8 +279,16 @@ static void free_desc(unsigned int irq) |
| dynamic_irq_cleanup(irq); |
| } |
| |
| -static inline int alloc_descs(unsigned int start, unsigned int cnt, int node) |
| +static inline int alloc_descs(unsigned int start, unsigned int cnt, int node, |
| + struct module *owner) |
| { |
| + u32 i; |
| + |
| + for (i = 0; i < cnt; i++) { |
| + struct irq_desc *desc = irq_to_desc(start + i); |
| + |
| + desc->owner = owner; |
| + } |
| return start; |
| } |
| |
| @@ -337,7 +348,8 @@ EXPORT_SYMBOL_GPL(irq_free_descs); |
| * Returns the first irq number or error code |
| */ |
| int __ref |
| -irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node) |
| +__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node, |
| + struct module *owner) |
| { |
| int start, ret; |
| |
| @@ -366,13 +378,13 @@ irq_alloc_descs(int irq, unsigned int fr |
| |
| bitmap_set(allocated_irqs, start, cnt); |
| mutex_unlock(&sparse_irq_lock); |
| - return alloc_descs(start, cnt, node); |
| + return alloc_descs(start, cnt, node, owner); |
| |
| err: |
| mutex_unlock(&sparse_irq_lock); |
| return ret; |
| } |
| -EXPORT_SYMBOL_GPL(irq_alloc_descs); |
| +EXPORT_SYMBOL_GPL(__irq_alloc_descs); |
| |
| /** |
| * irq_reserve_irqs - mark irqs allocated |
| @@ -440,7 +452,7 @@ void dynamic_irq_cleanup(unsigned int ir |
| unsigned long flags; |
| |
| raw_spin_lock_irqsave(&desc->lock, flags); |
| - desc_set_defaults(irq, desc, desc_node(desc)); |
| + desc_set_defaults(irq, desc, desc_node(desc), NULL); |
| raw_spin_unlock_irqrestore(&desc->lock, flags); |
| } |
| |
| --- a/kernel/irq/manage.c |
| +++ b/kernel/irq/manage.c |
| @@ -898,6 +898,8 @@ __setup_irq(unsigned int irq, struct irq |
| |
| if (desc->irq_data.chip == &no_irq_chip) |
| return -ENOSYS; |
| + if (!try_module_get(desc->owner)) |
| + return -ENODEV; |
| |
| /* |
| * Check whether the interrupt nests into another interrupt |
| @@ -905,8 +907,10 @@ __setup_irq(unsigned int irq, struct irq |
| */ |
| nested = irq_settings_is_nested_thread(desc); |
| if (nested) { |
| - if (!new->thread_fn) |
| - return -EINVAL; |
| + if (!new->thread_fn) { |
| + ret = -EINVAL; |
| + goto out_mput; |
| + } |
| /* |
| * Replace the primary handler which was provided from |
| * the driver for non nested interrupt handling by the |
| @@ -928,8 +932,10 @@ __setup_irq(unsigned int irq, struct irq |
| |
| t = kthread_create(irq_thread, new, "irq/%d-%s", irq, |
| new->name); |
| - if (IS_ERR(t)) |
| - return PTR_ERR(t); |
| + if (IS_ERR(t)) { |
| + ret = PTR_ERR(t); |
| + goto out_mput; |
| + } |
| /* |
| * We keep the reference to the task struct even if |
| * the thread dies to avoid that the interrupt code |
| @@ -1136,6 +1142,8 @@ out_thread: |
| kthread_stop(t); |
| put_task_struct(t); |
| } |
| +out_mput: |
| + module_put(desc->owner); |
| return ret; |
| } |
| |
| @@ -1244,6 +1252,7 @@ static struct irqaction *__free_irq(unsi |
| put_task_struct(action->thread); |
| } |
| |
| + module_put(desc->owner); |
| return action; |
| } |
| |