[PATCH 01/21] irq: add devres version of OF IRQ mapping routines

From: nyushchenko
Date: Wed Jun 04 2014 - 07:14:39 EST


From: Nikita Yushchenko <nyushchenko@xxxxxxxxxxxxx>

Many drivers use devres to manage their resources, and at the same time
use irq_of_parse_and_map() / irq_dispose_mapping(). This creates problem
on driver unload paths and on error paths:
- it is invalid to call irq_dispose_mapping() while IRQ handler is still
installed,
- devres moves removal of IRQ handler out of driver,
- without explicit devres support for IRQ mapping, irq_dispose_mapping()
stays in driver and thus gets called while IRQ handler is still
installed.

This patch adds devm_irq_create_of_mapping() and devm_irq_of_parse_and_map()
routines to be used by drivers for correct release of resources.

Signed-off-by: Nikita Yushchenko <nyushchenko@xxxxxxxxxxxxx>
---
drivers/of/irq.c | 24 +++++++++++++++++++++++
include/linux/irqdomain.h | 3 +++
include/linux/of_irq.h | 12 ++++++++++++
kernel/irq/irqdomain.c | 47 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 86 insertions(+)

diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index 5aeb894..30b5010 100644
--- a/drivers/of/irq.c
+++ b/drivers/of/irq.c
@@ -46,6 +46,30 @@ unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);

/**
+ * devm_irq_of_parse_and_map - Parse and map an interrupt into linux virq space
+ * @dev: Device interrupt will be used for
+ * @dn: Device node of the device whose interrupt is to be mapped
+ * @index: Index of the interrupt to map
+ *
+ * This function does the same as irq_of_parse_and_map(), but ensures that
+ * irq_dispose_mapping() will be called automatically at driver detatch.
+ *
+ * If IRQ mapping created by this function needs to be removed manually,
+ * devm_irq_dispose_mapping() must be called instead of irq_dispose_mapping().
+ */
+int devm_irq_of_parse_and_map(struct device *dev, struct device_node *dn,
+ int index)
+{
+ struct of_phandle_args oirq;
+
+ if (of_irq_parse_one(dn, index, &oirq))
+ return 0;
+
+ return devm_irq_create_of_mapping(dev, &oirq);
+}
+EXPORT_SYMBOL_GPL(devm_irq_of_parse_and_map);
+
+/**
* of_irq_find_parent - Given a device node, find its interrupt parent node
* @child: pointer to device node
*
diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
index c983ed1..44e6261 100644
--- a/include/linux/irqdomain.h
+++ b/include/linux/irqdomain.h
@@ -176,6 +176,7 @@ extern void irq_domain_associate_many(struct irq_domain *domain,
extern unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq);
extern void irq_dispose_mapping(unsigned int virq);
+extern void devm_irq_dispose_mapping(struct device *dev, unsigned int virq);

/**
* irq_linear_revmap() - Find a linux irq from a hw irq number.
@@ -220,6 +221,8 @@ int irq_domain_xlate_onetwocell(struct irq_domain *d, struct device_node *ctrlr,

#else /* CONFIG_IRQ_DOMAIN */
static inline void irq_dispose_mapping(unsigned int virq) { }
+static inline void devm_irq_dispose_mapping(struct device *dev,
+ unsigned int virq) { }
#endif /* !CONFIG_IRQ_DOMAIN */

#endif /* _LINUX_IRQDOMAIN_H */
diff --git a/include/linux/of_irq.h b/include/linux/of_irq.h
index 6404253..4ac7138 100644
--- a/include/linux/of_irq.h
+++ b/include/linux/of_irq.h
@@ -35,6 +35,8 @@ extern int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq)
extern int of_irq_parse_one(struct device_node *device, int index,
struct of_phandle_args *out_irq);
extern unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data);
+extern int devm_irq_create_of_mapping(struct device *dev,
+ struct of_phandle_args *irq_data);
extern int of_irq_to_resource(struct device_node *dev, int index,
struct resource *r);
extern int of_irq_to_resource_table(struct device_node *dev,
@@ -63,6 +65,9 @@ static inline int of_irq_get(struct device_node *dev, int index)
* so declare it here regardless of the CONFIG_OF_IRQ setting.
*/
extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
+extern int devm_irq_of_parse_and_map(struct device *dev,
+ struct device_node *node,
+ int index);
extern struct device_node *of_irq_find_parent(struct device_node *child);

#else /* !CONFIG_OF */
@@ -72,6 +77,13 @@ static inline unsigned int irq_of_parse_and_map(struct device_node *dev,
return 0;
}

+static inline int devm_irq_of_parse_and_map(struct device *dev,
+ struct device_node *node,
+ int index)
+{
+ return 0;
+}
+
static inline void *of_irq_find_parent(struct device_node *child)
{
return NULL;
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index f140337..c8705de 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -16,6 +16,7 @@
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/fs.h>
+#include <linux/device.h>

static LIST_HEAD(irq_domain_list);
static DEFINE_MUTEX(irq_domain_mutex);
@@ -502,6 +503,34 @@ unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
}
EXPORT_SYMBOL_GPL(irq_create_of_mapping);

+static void devm_release_irq_mapping(void *p)
+{
+ unsigned int virq = (unsigned int)((unsigned long)p);
+
+ if (virq)
+ irq_dispose_mapping(virq);
+}
+
+int devm_irq_create_of_mapping(struct device *dev,
+ struct of_phandle_args *irq_data)
+{
+ unsigned int virq;
+ int ret;
+
+ virq = irq_create_of_mapping(irq_data);
+ if (virq) {
+ ret = devm_add_action(dev, devm_release_irq_mapping,
+ (void *)((unsigned long)virq));
+ if (ret) {
+ irq_dispose_mapping(virq);
+ return ret;
+ }
+ }
+
+ return virq;
+}
+EXPORT_SYMBOL_GPL(devm_irq_create_of_mapping);
+
/**
* irq_dispose_mapping() - Unmap an interrupt
* @virq: linux irq number of the interrupt to unmap
@@ -524,6 +553,24 @@ void irq_dispose_mapping(unsigned int virq)
EXPORT_SYMBOL_GPL(irq_dispose_mapping);

/**
+ * devm_irq_dispose_mapping() - Unmap an interrupt
+ * @dev: device irq was used for
+ * @virq: linux irq number of the interrupt to unmap
+ *
+ * This should be used instead of irq_dispose_mapping() if mapping was created
+ * with devm_irq_create_of_mapping()
+ */
+void devm_irq_dispose_mapping(struct device *dev, unsigned int virq)
+{
+ if (virq) {
+ devm_remove_action(dev, devm_release_irq_mapping,
+ (void *)((unsigned long)virq));
+ irq_dispose_mapping(virq);
+ }
+}
+EXPORT_SYMBOL_GPL(devm_irq_dispose_mapping);
+
+/**
* irq_find_mapping() - Find a linux irq from an hw irq number.
* @domain: domain owning this hardware interrupt
* @hwirq: hardware irq number in that domain space
--
1.7.10.4

--
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/