lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Fri, 27 Jul 2012 13:01:56 -0600
From:	Stephen Warren <swarren@...dotorg.org>
To:	Mark Brown <broonie@...nsource.wolfsonmicro.com>,
	Liam Girdwood <lrg@...com>
Cc:	linux-kernel@...r.kernel.org, Samuel Ortiz <sameo@...ux.intel.com>,
	Stephen Warren <swarren@...dia.com>
Subject: [PATCH 3/3] regmap: enhance regmap-irq to handle 1 IRQ feeding n chips

From: Stephen Warren <swarren@...dia.com>

Some devices contain a single interrupt output, and multiple separate
interrupt controllers that all trigger that interrupt output, yet provide
no top-level interrupt controller/registers to allow determination of
which child interrupt controller caused the interrupt.

In this case, a regmap irq_chip can be created for each of the individual
"child" interrupt controllers, alongside some infra-structure to hook the
interrupt and distribute it to each "child". This patch introduces
regmap_add_irq_chips() which sets up such infra-structure, and creates
a number of child regmap irq_chips.

Signed-off-by: Stephen Warren <swarren@...dia.com>
---
 drivers/base/regmap/regmap-irq.c |  187 ++++++++++++++++++++++++++++++++++++++
 include/linux/regmap.h           |    9 ++
 2 files changed, 196 insertions(+), 0 deletions(-)

diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c
index 6bb4364..9d7894c 100644
--- a/drivers/base/regmap/regmap-irq.c
+++ b/drivers/base/regmap/regmap-irq.c
@@ -2,6 +2,7 @@
  * regmap based irq_chip
  *
  * Copyright 2011 Wolfson Microelectronics plc
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
  *
  * Author: Mark Brown <broonie@...nsource.wolfsonmicro.com>
  *
@@ -16,6 +17,7 @@
 #include <linux/irq.h>
 #include <linux/interrupt.h>
 #include <linux/irqdomain.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
 #include "internal.h"
@@ -40,6 +42,14 @@ struct regmap_irq_chip_data {
 	unsigned int irq_reg_stride;
 };
 
+struct regmap_irq_chips_data {
+	struct device *dev;
+	int irq;
+	int nchips;
+	struct irq_domain *irqdom;
+	struct regmap_irq_chip_data **datas;
+};
+
 static inline const
 struct regmap_irq *irq_to_regmap_irq(struct regmap_irq_chip_data *data,
 				     int irq)
@@ -463,3 +473,180 @@ int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq)
 	return irq_create_mapping(data->domain, irq);
 }
 EXPORT_SYMBOL_GPL(regmap_irq_get_virq);
+
+static void regmaps_irq_enable(struct irq_data *data)
+{
+}
+
+static void regmaps_irq_disable(struct irq_data *data)
+{
+}
+
+static struct irq_chip regmaps_irq_chip = {
+	.name		= "regmaps",
+	.irq_disable	= regmaps_irq_disable,
+	.irq_enable	= regmaps_irq_enable,
+};
+
+static int regmaps_irq_map(struct irq_domain *h, unsigned int virq,
+			   irq_hw_number_t hw)
+{
+	struct regmap_irq_chips_data *data = h->host_data;
+
+	irq_set_chip_data(virq, data);
+	irq_set_chip_and_handler(virq, &regmaps_irq_chip, handle_edge_irq);
+	irq_set_nested_thread(virq, 1);
+
+	/* ARM needs us to explicitly flag the IRQ as valid
+	 * and will set them noprobe when we do so. */
+#ifdef CONFIG_ARM
+	set_irq_flags(virq, IRQF_VALID);
+#else
+	irq_set_noprobe(virq);
+#endif
+
+	return 0;
+}
+
+static struct irq_domain_ops regmaps_domain_ops = {
+	.map	= regmaps_irq_map,
+};
+
+static irqreturn_t regmaps_irq_thread(int irq, void *data)
+{
+	struct regmap_irq_chips_data *d = data;
+	int ret, i;
+
+	ret = pm_runtime_get_sync(d->dev);
+	if (ret < 0) {
+		dev_err(d->dev, "Failed to resume device: %d\n", ret);
+		return IRQ_NONE;
+	}
+
+	for (i = 0; i < d->nchips; i++)
+		handle_nested_irq(irq_find_mapping(d->irqdom, i));
+
+	pm_runtime_mark_last_busy(d->dev);
+	pm_runtime_put_autosuspend(d->dev);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * regmap_add_irq_chips(): Call regmap_add_irq_chip for n chips on one IRQ
+ *
+ * @irq:       Primary IRQ for the device
+ * @irq_flags: The IRQF_ flags to use for the primary interrupt.
+ * @nchips:    The number of IRQ chips attached to the interrupt.
+ * @maps:      The regmap for each IRQ chip.
+ * @irq_bases  The base Linux IRQ number for each chip, or NULL.
+ * @chips:     Configuration for each interrupt controller.
+ * @data:      Runtime data structure set of controllers, allocated on success
+ *
+ * Some devices contain a single interrupt output, and multiple separate
+ * interrupt controllers that all trigger that interrupt output, yet provide
+ * no top-level interrupt controller to allow determination of which child
+ * interrupt controller caused the interrupt. regmap_add_irq_chips() creates
+ * the required N regmap irq_chips, and handles demultiplexing this virtual
+ * top-level interrupt.
+ *
+ * Returns 0 on success or an errno on failure.
+ *
+ * In order for this to be efficient the chip really should use a
+ * register cache.  The chip driver is responsible for restoring the
+ * register values used by the IRQ controller over suspend and resume.
+ */
+int regmap_add_irq_chips(int irq, int irq_flags, int nchips,
+			 struct regmap **maps, int *irq_bases,
+			 const struct regmap_irq_chip **chips,
+			 struct regmap_irq_chips_data **data)
+{
+	int ret = -ENOMEM;
+	struct regmap_irq_chips_data *d;
+	int i;
+
+	d = kzalloc(sizeof(*d), GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	d->dev = maps[0]->dev;
+	d->irq = irq;
+	d->nchips = nchips;
+
+	d->datas = kzalloc(nchips * sizeof(*(d->datas)), GFP_KERNEL);
+	if (!d->datas)
+		goto err_alloc;
+
+	d->irqdom = irq_domain_add_linear(NULL, nchips, &regmaps_domain_ops, d);
+	if (!d->irqdom) {
+		ret = -EINVAL;
+		goto err_alloc;
+	}
+
+	for (i = 0; i < nchips; i++) {
+		ret = regmap_add_irq_chip(maps[i],
+					  irq_create_mapping(d->irqdom, i),
+					  IRQF_ONESHOT,
+					  irq_bases ? irq_bases[i] : 0,
+					  chips[i], &d->datas[i]);
+		if (ret != 0) {
+			dev_err(maps[i]->dev,
+				"Failed to add chip %d IRQs: %d\n", i, ret);
+			goto err_chips;
+		}
+	}
+
+	ret = request_threaded_irq(irq, NULL, regmaps_irq_thread,
+				   irq_flags, dev_name(d->dev), d);
+	if (ret != 0) {
+		dev_err(d->dev, "Failed to request IRQ %d: %d\n", irq, ret);
+		goto err_chips;
+	}
+
+	*data = d;
+
+	return 0;
+
+err_chips:
+	for (i--; i >= 0; i--)
+		regmap_del_irq_chip(irq_create_mapping(d->irqdom, i),
+				    d->datas[i]);
+	/* We should unmap the domain but... */
+err_alloc:
+	kfree(d->datas);
+	kfree(d);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(regmap_add_irq_chips);
+
+/**
+ * regmap_del_irq_chips(): Undo regmap_add_irq_chips()
+ *
+ * @irq:    Primary IRQ for the device
+ * @d:      regmap_irq_chips_data allocated by regmap_add_irq_chips()
+ */
+void regmap_del_irq_chips(struct regmap_irq_chips_data *d)
+{
+	int i;
+
+	free_irq(d->irq, d);
+
+	for (i = d->nchips - 1; i >= 0; i--)
+		regmap_del_irq_chip(irq_create_mapping(d->irqdom, i),
+				    d->datas[i]);
+
+	/* We should unmap the domain but... */
+
+	kfree(d->datas);
+	kfree(d);
+}
+EXPORT_SYMBOL_GPL(regmap_del_irq_chips);
+
+struct regmap_irq_chip_data *regmap_irq_chips_get_chip(
+				struct regmap_irq_chips_data *d, int chip)
+{
+	if (chip >= d->nchips)
+		return NULL;
+	return d->datas[chip];
+}
+EXPORT_SYMBOL(regmap_irq_chips_get_chip);
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 7f7e00d..b5d9699 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -307,6 +307,7 @@ struct regmap_irq_chip {
 };
 
 struct regmap_irq_chip_data;
+struct regmap_irq_chips_data;
 
 int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
 			int irq_base, const struct regmap_irq_chip *chip,
@@ -315,6 +316,14 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *data);
 int regmap_irq_chip_get_base(struct regmap_irq_chip_data *data);
 int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq);
 
+int regmap_add_irq_chips(int irq, int irq_flags, int nchips,
+			 struct regmap **maps, int *irq_bases,
+			 const struct regmap_irq_chip **chips,
+			 struct regmap_irq_chips_data **data);
+void regmap_del_irq_chips(struct regmap_irq_chips_data *d);
+struct regmap_irq_chip_data *regmap_irq_chips_get_chip(
+				struct regmap_irq_chips_data *d, int chip);
+
 #else
 
 /*
-- 
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ