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] [day] [month] [year] [list]
Message-ID: <20250923232905.1510547-2-rrendec@redhat.com>
Date: Tue, 23 Sep 2025 19:29:05 -0400
From: Radu Rendec <rrendec@...hat.com>
To: Thomas Gleixner <tglx@...utronix.de>
Cc: linux-kernel@...r.kernel.org,
	Brian Masney <bmasney@...hat.com>,
	Eric Chanudet <echanude@...hat.com>
Subject: [PATCH 1/1] samples: Add irqchip test driver

Add a test driver that demonstrates implementation of multiple IRQ chip
types and their domains. The driver implements:

- Root IRQ chip simulating direct CPU interrupt lines
- Hierarchical IRQ domain with 1:1 parent mapping
- Interrupt multiplexer domain with chained interrupts

The driver supports simulating interrupt handling by triggering fake
interrupts via sysfs parameter, with CPU affinity simulation using
targeted work items.

This serves as a reference implementation and testing sandbox for IRQ
chip driver development and debugging.

Signed-off-by: Radu Rendec <rrendec@...hat.com>
---
 samples/Kconfig             |  10 +
 samples/Makefile            |   1 +
 samples/irqchip/Makefile    |   1 +
 samples/irqchip/irqc_test.c | 576 ++++++++++++++++++++++++++++++++++++
 4 files changed, 588 insertions(+)
 create mode 100644 samples/irqchip/Makefile
 create mode 100644 samples/irqchip/irqc_test.c

diff --git a/samples/Kconfig b/samples/Kconfig
index 6e072a5f1ed86..6a4f696ca4311 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -320,6 +320,16 @@ config SAMPLE_HUNG_TASK
 	  Reading these files with multiple processes triggers hung task
 	  detection by holding locks for a long time (256 seconds).
 
+config SAMPLE_IRQCHIP
+	bool "Test IRQ chip driver"
+	help
+	  Build a driver that demonstrates the implementation of multiple IRQ
+	  chip types (root, hierarchical, multiplexing). It serves as a
+	  reference implementation for IRQ chip driver development, showcasing
+	  different domain management patterns and interrupt routing mechanisms.
+	  It supports simulation of interrupt handling with CPU affinity through
+	  sysfs parameters.
+
 source "samples/rust/Kconfig"
 
 source "samples/damon/Kconfig"
diff --git a/samples/Makefile b/samples/Makefile
index 07641e177bd8b..380168e6eae55 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_SAMPLE_CONNECTOR)		+= connector/
 obj-$(CONFIG_SAMPLE_FANOTIFY_ERROR)	+= fanotify/
 subdir-$(CONFIG_SAMPLE_HIDRAW)		+= hidraw
 obj-$(CONFIG_SAMPLE_HW_BREAKPOINT)	+= hw_breakpoint/
+obj-$(CONFIG_SAMPLE_IRQCHIP)		+= irqchip/
 obj-$(CONFIG_SAMPLE_KDB)		+= kdb/
 obj-$(CONFIG_SAMPLE_KFIFO)		+= kfifo/
 obj-$(CONFIG_SAMPLE_KOBJECT)		+= kobject/
diff --git a/samples/irqchip/Makefile b/samples/irqchip/Makefile
new file mode 100644
index 0000000000000..fb8600ee15e67
--- /dev/null
+++ b/samples/irqchip/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SAMPLE_IRQCHIP) += irqc_test.o
diff --git a/samples/irqchip/irqc_test.c b/samples/irqchip/irqc_test.c
new file mode 100644
index 0000000000000..bd897f6a3b2fa
--- /dev/null
+++ b/samples/irqchip/irqc_test.c
@@ -0,0 +1,576 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * IRQ chip driver sandbox
+ *
+ * This driver implements three different IRQ chips, each with its own IRQ
+ * domain. The following chips/domains are implemented:
+ *   - "root" - simulation of direct IRQ lines to the CPU. This is similar to
+ *     the APIC on x86 systems or GIC on ARM systems.
+ *   - "hier" - a hierarchical domain sitting on top of "root". This is similar
+ *     to a GPIO driver that implements the hierarchical IRQ API and where each
+ *     GPIO pin has a (distinct) parent IRQ.
+ *   - "imux" - an interrupt multiplexing domain sitting on top of "root". This
+ *     domain has a single parent interrupt and multiple downstream interrupts.
+ *     It uses the legacy IRQ API and chained interrupts.
+ *
+ * +------------+                  +------------+
+ * |            |  hwirq 0         |            |  hwirq 0
+ * |            +<-----------------+            +<----------------- virq #2
+ * |            |  hwirq 1         |    hier    |  hwirq 1
+ * |            +<-----------------+            +<----------------- virq #3
+ * |            |                  |            |
+ * |            |                  +------------+
+ * |    root    |
+ * |            |                  +------------+
+ * |            |                  |            |  hwirq 0
+ * |            |  hwirq 2         |            +<----------------- virq #4
+ * |            +<-----------------+    imux    |  hwirq 1
+ * |            |         virq #1  |            +<----------------- virq #5
+ * |            |                  |            |
+ * +------------+                  +------------+
+ *
+ * The arrows indicate how interrupt signals are routed from a (virtual)
+ * hardware perspective. In software, interrupt handlers are called in the
+ * opposite direction. With real hardware, each interrupt in "root" would
+ * have a CPU interrupt vector.
+ *
+ * The important thing to note is that virqs #2 and #3 are mapped end to end
+ * (the same virq is mapped in both "hier" and "root"), while virqs #4 and #5
+ * are terminated in "imux" and share a common chained interrupt (virq #1).
+ * Chained interrupts are still assigned a virq but do not show up in
+ * /proc/interrupts.
+ *
+ * The driver also supports simulating the interrupt handling path. Fake
+ * interrupts can be triggered by writing the corresponding virq number to a
+ * sysfs file. That simulates the corresponding hwirq occurring in the "root"
+ * domain. CPU affinity is simulated by running the interrupt handlers in a
+ * (CPU targeted) work item context, with local interrupts disabled. When
+ * the affinity mask contains multiple CPUs, the interrupt always runs on the
+ * first CPU in the mask.
+ */
+#define pr_fmt(fmt) "irqc_test: " fmt
+
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/cpuhplock.h>
+
+/* Number of IRQs in the root IRQ domain */
+#define NUM_TOTAL_IRQS_ROOT	4
+
+/* Number of IRQs in the hierarchical IRQ domain */
+#define NUM_MAPPED_IRQS_HIER	2
+#define NUM_TOTAL_IRQS_HIER	8
+
+/* Number of IRQs in the multiplexer IRQ domain */
+#define NUM_MAPPED_IRQS_IMUX	2
+#define NUM_TOTAL_IRQS_IMUX	8
+
+/* Total number of mapped IRQs */
+#define NUM_MAPPED_IRQS (NUM_MAPPED_IRQS_HIER + NUM_MAPPED_IRQS_IMUX)
+
+static struct irq_domain *root_irqd;
+static struct irq_domain *hier_irqd;
+static struct irq_domain *imux_irqd;
+static int mapped_virq[NUM_MAPPED_IRQS];
+static int dummydev[NUM_MAPPED_IRQS];
+static int imux_parent_irq;
+static unsigned long imux_pending_hwirq;
+
+/*
+ * In case CONFIG_GENERIC_PENDING_IRQ is enabled, we rely on this callback to
+ * effectively set the affinity when the next IRQ occurs. This handler is
+ * called automatically by the IRQ subsystem. The irq_move_irq() function checks
+ * if the affinity move is pending and eventually calls our .irq_set_affinity
+ * handler.
+ *
+ * Note: CONFIG_GENERIC_PENDING_IRQ is not configurable directly by the user
+ *       and is enabled automatically on x86. It is NOT enabled on arm64.
+ *
+ * When CONFIG_GENERIC_PENDING_IRQ is not enabled, irq_can_move_pcntxt() is
+ * a stub that always returns true, and then irq_set_affinity_locked() calls
+ * the .irq_set_affinity handler directly.
+ */
+static void root_ack(struct irq_data *data)
+{
+	irq_move_irq(data);
+}
+
+/*
+ * This function exists for the sole purpose of keeping chained IRQ handlers
+ * happy. A chained IRQ handler typically calls chained_irq_enter() and
+ * chained_irq_exit(), and so does our imux_irq_dispatch() handler. These
+ * functions assume either .irq_eoi or .irq_mask/.irq_unmask are implemented.
+ * If none of those is implemented, there will be a NULL pointer dereference
+ * attempting to call .irq_mask/.irq_unmask.
+ *
+ * Keep in mind that the handler is called for the *parent* interrupt. In our
+ * case, `desc` describes the root domain virq, and irq_desc_get_chip(desc) is
+ * `root_chip`.
+ */
+static void root_eoi(struct irq_data *data)
+{
+}
+
+/*
+ * The effective affinity is not set automatically by the IRQ subsystem because
+ * only the irqchip driver knows when the affinity has been configured in the
+ * underlying hardware.
+ *
+ * The .irq_set_affinity handler is called automatically by the IRQ subsystem
+ * when a new IRQ is set up (and the affinity is set by default to all CPUs).
+ * If we don't update the effective affinity here, we may raise the pr_warn in
+ * irq_validate_effective_affinity() (called through irq_do_set_affinity())
+ * during that initial call to the .irq_set_affinity handler.
+ */
+static int root_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
+{
+	pr_info("%s: set virq %u (irq %lu) affinity to 0x%*pb\n",
+		data->chip->name, data->irq, data->hwirq,
+		cpumask_pr_args(dest));
+	irq_data_update_effective_affinity(data, dest);
+	return IRQ_SET_MASK_OK;
+}
+
+static const struct irq_chip root_chip = {
+	.name = "TEST-ROOT-IC",
+	.irq_ack = root_ack,
+	.irq_set_affinity = root_set_affinity,
+	.irq_eoi = root_eoi,
+};
+
+static int root_alloc(struct irq_domain *irqd, unsigned int virq,
+		      unsigned int nr_irqs, void *data)
+{
+	struct irq_fwspec *fwspec = data;
+	irq_hw_number_t hwirq;
+	unsigned int type;
+	int ret, i;
+
+	pr_info("[%s] irq = %u, nr_irqs = %u\n", irqd->name, virq, nr_irqs);
+
+	ret = irq_domain_translate_onecell(irqd, fwspec, &hwirq, &type);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < nr_irqs; i++) {
+		pr_info("\tvirq = %u, hwirq = %lu\n", virq + i, hwirq + i);
+		irq_domain_set_info(irqd, virq, hwirq, &root_chip, NULL,
+				    handle_level_irq, NULL, NULL);
+	}
+
+	return 0;
+}
+
+/*
+ * Parent IRQ domains *must* use the new API (i.e. alloc/free vs. map) to
+ * support hierarchy, for the following reasons:
+ *   - The child .alloc() function calls irq_domain_alloc_irqs_parent().
+ *   - The irq_domain_alloc_irqs_hierarchy() is just a thin wrapper around
+ *     irq_domain_alloc_irqs_hierarchy().
+ *   - Finally, irq_domain_alloc_irqs_hierarchy() explicitly checks that
+ *     the (parent) domain implements the alloc() function and returns an
+ *     error otherwise.
+ *
+ * Furthermore, Documentation/core-api/irq/irq-domain.rst clearly states that
+ * the new API must be used for all domains to support hierarchy.
+ *
+ * Note: The .free() function is also part of the new API and is mandatory but
+ *       there is a default implementation that we can use. If we do not
+ *       implement this function and .alloc() fails for whatever reason, we get
+ *       an oops as the IRQ framework tries to rollback whatever has been done.
+ */
+static const struct irq_domain_ops root_ops = {
+	.alloc = root_alloc,
+	.free = irq_domain_free_irqs_common,
+	.translate = irq_domain_translate_onecell,
+};
+
+/*
+ * Since hierarchical interrupts are mapped 1:1 in all domains along the path,
+ * CPU affinity is not shared with other interrupts. Use the parent handlers to
+ * set the affinity in the root domain.
+ */
+static const struct irq_chip hier_chip = {
+	.name = "TEST-HIER-IC",
+	.irq_ack = irq_chip_ack_parent,
+	.irq_set_affinity = irq_chip_set_affinity_parent,
+};
+
+/*
+ * For a GPIO IRQ chip, this is gpiochip_hierarchy_irq_domain_alloc(), and the
+ * ops pointer is set in gpiochip_hierarchy_setup_domain_ops(), called from
+ * gpiochip_hierarchy_add_domain(). See full stack sample below in testirq_init().
+ *
+ * This function is called indirectly by irq_create_fwspec_mapping() through
+ * irq_domain_alloc_irqs_locked().
+ *
+ * How to search for other examples:
+ *   grep -rn '\.alloc\>' drivers/gpio/
+ *   grep -rn '\<irq_domain_alloc_irqs_parent\>' drivers/irqchip/
+ */
+static int hier_alloc(struct irq_domain *irqd, unsigned int virq,
+		      unsigned int nr_irqs, void *data)
+{
+	struct irq_fwspec *fwspec = data;
+	struct irq_fwspec parent_fwspec = {
+		.fwnode = irqd->parent->fwnode,
+		.param_count = 1,
+	};
+	irq_hw_number_t hwirq;
+	unsigned int type;
+	int ret, i;
+
+	pr_info("[%s] virq = %u, nr_irqs = %u\n", irqd->name, virq, nr_irqs);
+
+	ret = irq_domain_translate_onecell(irqd, fwspec, &hwirq, &type);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < nr_irqs; i++) {
+		struct irq_data *d = irq_get_irq_data(virq + i);
+
+		pr_info("\tvirq = %u, data = %pS, parent = %pS\n",
+			virq + i, d, d->parent_data);
+		irq_domain_set_hwirq_and_chip(irqd, virq + i, hwirq + i,
+					      &hier_chip, NULL);
+	}
+
+	/*
+	 * We're not done yet, we still need to allocate interrupts in the
+	 * parent domain. If we just "return 0" at this point, the upstream
+	 * call to irq_create_fwspec_mapping() fails with -EINVAL.
+	 *
+	 * By now, the framework has already allocated a struct irq_data for
+	 * each virq, for both our domain (hier) and the parent domain (root).
+	 * So, irq_get_irq_data(virq) and irq_get_irq_data(virq)->parent_data
+	 * are both non-null. The problem is that parent_data->chip is NULL,
+	 * and this fails the sanity checks in irq_domain_trim_hierarchy()
+	 * (called from irq_domain_alloc_irqs_locked()).
+	 */
+
+	parent_fwspec.param[0] = hwirq;
+
+	return irq_domain_alloc_irqs_parent(irqd, virq, nr_irqs, &parent_fwspec);
+}
+
+/*
+ * For a GPIO IRQ chip, the .free and .translate methods may be overridden by
+ * the driver and defaults are provided in gpiochip_hierarchy_setup_domain_ops(),
+ * gpiochip_hierarchy_add_domain(). See full stack example below in testirq_init().
+ *
+ * In the drivers/pinctrl/qcom/pinctrl-spmi-gpio.c driver, the .translate method
+ * is overridden in pmic_gpio_probe(), by setting child_irq_domain_ops.translate.
+ */
+static const struct irq_domain_ops hier_ops = {
+	.alloc = hier_alloc,
+	.free = irq_domain_free_irqs_common,
+	.translate = irq_domain_translate_onecell,
+};
+
+/*
+ * This function is needed in case another IRQ handler is chained below this
+ * irq_chip. See the note above for root_eoi().
+ */
+static void imux_eoi(struct irq_data *data)
+{
+}
+
+static struct irq_chip imux_chip = {
+	.name = "TEST-IMUX-IC",
+	.irq_eoi = imux_eoi,
+};
+
+static int imux_map(struct irq_domain *irqd, unsigned int virq, irq_hw_number_t hwirq)
+{
+	pr_info("[%s] virq = %u, hwirq = %lu\n", irqd->name, virq, hwirq);
+	irq_set_chip_data(virq, irqd->host_data);
+	irq_set_chip_and_handler(virq, &imux_chip, handle_level_irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops imux_ops = {
+	.map = imux_map,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+static void imux_irq_dispatch(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct irq_domain *domain;
+	unsigned int hwirq;
+
+	chained_irq_enter(chip, desc);
+
+	if (!imux_pending_hwirq) {
+		pr_err("imux spurious interrupt\n");
+		goto out;
+	}
+
+	domain = irq_desc_get_handler_data(desc);
+	for_each_set_bit(hwirq, &imux_pending_hwirq, NUM_MAPPED_IRQS_IMUX) {
+		generic_handle_domain_irq(domain, hwirq);
+		clear_bit(hwirq, &imux_pending_hwirq);
+	}
+
+out:
+	chained_irq_exit(chip, desc);
+}
+
+
+static irqreturn_t consum_hdlr(int irq, void *data)
+{
+	struct irq_data *d = irq_get_irq_data(irq);
+
+	pr_info("IRQ handler: virq %u (hwirq %lu on chip %s)\n",
+		d->irq, d->hwirq, d->chip->name);
+	return IRQ_HANDLED;
+}
+
+static long trigger_irq(void *arg)
+{
+	unsigned long hwirq = (unsigned long)arg;
+
+	pr_info("trigger hwirq %lu on cpu %u\n", hwirq, smp_processor_id());
+	generic_handle_domain_irq_safe(root_irqd, hwirq);
+
+	return 0;
+}
+
+static int trigger_set(const char *val, const struct kernel_param *kp)
+{
+	unsigned int virq, cpu;
+	struct irq_data *data;
+	const struct cpumask *mask;
+	int ret;
+
+	ret = kstrtouint(val, 0, &virq);
+	if (ret)
+		return ret;
+
+	data = irq_domain_get_irq_data(imux_irqd, virq);
+	if (data) {
+		set_bit(data->hwirq, &imux_pending_hwirq);
+		virq = imux_parent_irq;
+	}
+
+	data = irq_domain_get_irq_data(root_irqd, virq);
+	if (!data)
+		return -ENOENT;
+
+	mask = irq_get_effective_affinity_mask(virq);
+
+	/*
+	 * For hierarchical IRQs, the affinity is not configured implicitly
+	 * when the IRQ is set up. In that case, use the first online CPU.
+	 *
+	 * Note: The affinity is configured implicitly only via request_irq()
+	 *       when the IRQ is mapped in the root domain directly or via
+	 *       irq_set_chained_handler_and_data() for chained IRQs.
+	 */
+	if (cpumask_empty(mask)) {
+		pr_warn("effective affinity not set for virq %u\n", virq);
+		mask = cpu_online_mask;
+	}
+
+	cpus_read_lock();
+	cpu = cpumask_first_and(mask, cpu_online_mask);
+	if (cpu >= nr_cpu_ids)
+		pr_warn("no target CPU available for virq %u\n", virq);
+	else
+		work_on_cpu(cpu, trigger_irq, (void *)data->hwirq);
+	cpus_read_unlock();
+
+	return 0;
+}
+
+static const struct kernel_param_ops trigger_ops = { .set = trigger_set };
+
+
+/*
+ * Dummy fwnode structures for each IRQ chip. We need them for irq_create_fwspec_mapping()
+ * below, because otherwise it can't look up the IRQ domain.
+ *
+ * The function that looks up the IRQ domain by the fwspec is irq_find_matching_fwspec().
+ * As a last resort, it compares the pointers to struct fwnode_handle.
+ */
+static struct fwnode_handle *root_fwnode;
+static struct fwnode_handle *hier_fwnode;
+static struct fwnode_handle *imux_fwnode;
+
+static int __init testirq_init(void)
+{
+	struct irq_fwspec fwspec = {.param_count = 1};
+	unsigned int i, j;
+	int ret = -EINVAL;
+
+	root_fwnode = irq_domain_alloc_named_fwnode("testirq-root");
+	if (!root_fwnode) {
+		pr_err("irq_domain_alloc_named_fwnode(root) failed\n");
+		return ret;
+	}
+
+	root_irqd = irq_domain_create_linear(root_fwnode, NUM_TOTAL_IRQS_ROOT,
+					     &root_ops, NULL);
+	if (!root_irqd) {
+		pr_err("irq_domain_add_simple(root) failed\n");
+		goto out_free_root_fwnode;
+	}
+
+	hier_fwnode = irq_domain_alloc_named_fwnode("testirq-hier");
+	if (!hier_fwnode) {
+		pr_err("irq_domain_alloc_named_fwnode(hier) failed\n");
+		goto out_root_domain_remove;
+	}
+
+	/*
+	 * For a GPIO IRQ chip, the domain hierarchy is created like so:
+	 * pmic_gpio_probe			drivers/pinctrl/qcom/pinctrl-spmi-gpio.c
+	 * gpiochip_add_data			include/linux/gpio/driver.h
+	 * gpiochip_add_data_with_key		drivers/gpio/gpiolib.c
+	 * gpiochip_add_irqchip			drivers/gpio/gpiolib.c
+	 * gpiochip_hierarchy_add_domain	drivers/gpio/gpiolib.c
+	 * irq_domain_create_hierarchy		kernel/irq/irqdomain.c
+	 */
+	hier_irqd = irq_domain_create_hierarchy(root_irqd, 0, NUM_TOTAL_IRQS_HIER,
+						hier_fwnode, &hier_ops, NULL);
+	if (!hier_irqd) {
+		pr_err("irq_domain_create_hierarchy(hier) failed\n");
+		goto out_free_hier_fwnode;
+	}
+
+	/*
+	 * Create an IRQ mapping in the parent (root) domain. Hierarchy domain
+	 * IRQs are mapped 1:1 in each domain along the chain. For that reason,
+	 * the first NUM_MAPPED_IRQS_HIER hwirqs in the root domain are used by
+	 * hierarchy IRQs, and the first available is NUM_MAPPED_IRQS_HIER.
+	 */
+	fwspec.fwnode = root_fwnode;
+	fwspec.param[0] = NUM_MAPPED_IRQS_HIER;
+	imux_parent_irq = irq_create_fwspec_mapping(&fwspec);
+	if (!imux_parent_irq) {
+		pr_err("irq_create_mapping(root) failed\n");
+		goto out_hier_domain_remove;
+	}
+
+	imux_fwnode = irq_domain_alloc_named_fwnode("testirq-imux");
+	if (!imux_fwnode) {
+		pr_err("irq_domain_alloc_named_fwnode(imux) failed\n");
+		goto out_remove_imux_mapping;
+	}
+
+	imux_irqd = irq_domain_create_linear(imux_fwnode, NUM_TOTAL_IRQS_IMUX,
+					     &imux_ops, NULL);
+	if (!imux_irqd) {
+		pr_err("irq_domain_add_simple(imux) failed\n");
+		goto out_free_imux_fwnode;
+	}
+
+	irq_set_chained_handler_and_data(imux_parent_irq, imux_irq_dispatch, imux_irqd);
+
+	/*
+	 * From this point on, this is what a consumer of the GPIO chip would do
+	 * to set up an IRQ through the GPIO chip. If the GPIO chip doesn't
+	 * provide irqchip support, a consumer can still call gpiod_to_irq(),
+	 * which in turn calls (struct gpio_chip).to_irq(). By default, this is
+	 * set to gpiochip_to_irq() (in gpiochip_add_irqchip()).
+	 *
+	 * In gpiochip_to_irq(), either irq_create_fwspec_mapping() is called if
+	 * the IRQ domain is hierarchical, or irq_create_mapping() otherwise.
+	 *
+	 * When the GPIO chip provides irqchip support, the IRQ is mapped in the
+	 * device tree, and the consumer can call irq_create_of_mapping().
+	 * Internally, this still ends up calling irq_create_fwspec_mapping().
+	 */
+	for (i = 0; i < NUM_MAPPED_IRQS; i++) {
+		if (i < NUM_MAPPED_IRQS_HIER) {
+			/* map this IRQ to the "hier" chip */
+			fwspec.fwnode = hier_fwnode;
+			fwspec.param[0] = i; // HW IRQ# at the leaf level
+		} else {
+			/* map this IRQ to the "imux" chip */
+			fwspec.fwnode = imux_fwnode;
+			fwspec.param[0] = i - NUM_MAPPED_IRQS_HIER;
+		};
+
+		mapped_virq[i] = irq_create_fwspec_mapping(&fwspec);
+		if (!mapped_virq[i]) {
+			pr_err("irq_create_mapping(hier, %u) failed\n", i);
+			goto out_dispose_and_free;
+		}
+
+		pr_info("mapped consumer virq %d\n", mapped_virq[i]);
+
+		/*
+		 * Don't use the IRQF_SHARED flag because it would trigger a
+		 * dummy call of the IRQ handler when the module is unloaded
+		 * in case CONFIG_DEBUG_SHIRQ is enabled, via free_irq() ->
+		 * __free_irq() -> action->handler().
+		 */
+		ret = request_irq(mapped_virq[i], consum_hdlr, 0, "test", &dummydev[i]);
+		if (ret < 0) {
+			pr_err("request_irq(%d) failed\n", mapped_virq[i]);
+			irq_dispose_mapping(mapped_virq[i]);
+			goto out_dispose_and_free;
+		}
+
+		pr_info("registered consumer virq %d\n", mapped_virq[i]);
+	}
+
+	return 0;
+
+out_dispose_and_free:
+	/*
+	 * The IRQ mapping must be removed explicitly. This is NOT done by
+	 * irq_domain_remove() automatically and that's documented in the
+	 * function comment block.
+	 */
+	for (j = 0; j < i; j++) {
+		irq_dispose_mapping(mapped_virq[j]);
+		free_irq(mapped_virq[j], &dummydev[j]);
+	}
+	irq_domain_remove(imux_irqd);
+out_free_imux_fwnode:
+	irq_domain_free_fwnode(imux_fwnode);
+out_remove_imux_mapping:
+	irq_dispose_mapping(imux_parent_irq);
+out_hier_domain_remove:
+	irq_domain_remove(hier_irqd);
+out_free_hier_fwnode:
+	irq_domain_free_fwnode(hier_fwnode);
+out_root_domain_remove:
+	irq_domain_remove(root_irqd);
+out_free_root_fwnode:
+	irq_domain_free_fwnode(root_fwnode);
+	return ret;
+}
+
+static void __exit testirq_exit(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUM_MAPPED_IRQS; i++) {
+		free_irq(mapped_virq[i], &dummydev[i]);
+		irq_dispose_mapping(mapped_virq[i]);
+	}
+	irq_domain_remove(imux_irqd);
+	irq_domain_free_fwnode(imux_fwnode);
+	irq_dispose_mapping(imux_parent_irq);
+	irq_domain_remove(hier_irqd);
+	irq_domain_free_fwnode(hier_fwnode);
+	irq_domain_remove(root_irqd);
+	irq_domain_free_fwnode(root_fwnode);
+}
+
+module_param_cb(trigger, &trigger_ops, NULL, 0440);
+MODULE_PARM_DESC(trigger, "Manually trigger the corresponding virq");
+
+module_init(testirq_init);
+module_exit(testirq_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Radu Rendec <rrendec@...hat.com>");
+MODULE_DESCRIPTION("IRQ chip driver sandbox");
-- 
2.51.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ