All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/1] irqchip test driver/sandbox
@ 2025-09-23 23:29 Radu Rendec
  2025-09-23 23:29 ` [PATCH 1/1] samples: Add irqchip test driver Radu Rendec
  0 siblings, 1 reply; 3+ messages in thread
From: Radu Rendec @ 2025-09-23 23:29 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: linux-kernel, Brian Masney, Eric Chanudet

This is a dummy irqchip driver that implements three different types of
IRQ chips, requests interrupts like a real device driver would do, and
provides the ability to manually trigger any of these interrupts. The
main reason why I wrote it was to be able to hack on the kernel IRQ core
without depending on any hardware (even virtual, inside a VM). But it
can also be used as an example for implementing irqchip drivers.

The irqchip setup/layout is described in detail in a comment block at
the top of the driver's source file, so I'm not repeating it here. Two
"leaf" IRQ domains are implemented: hierarchical and multiplexing. These
are attached to an additional "root" domain, which is similar to APIC
(x86_64) or GIC (arm64). Interrupts are simulated by calling the generic
interrupt handling function on the "root" domain, in a workqueue context
and with local interrupts disabled.

A few notes on the implementation:
- The driver cannot be compiled as a loadable module, and the only
  reason is because it uses irq_move_irq(), which is not exported. I
  could not find a better way to make IRQ affinity control for the
  "root" domain work on x86. This is also documented in the source code.
- The driver uses a write-only module parameter with a custom handler as
  the interface to trigger simulated interrupts. While this slightly
  abuses the module parameter API, it's very simple to implement and
  also keeps the sysfs file confined in the module's own "namespace" at
  /sys/module/irqc_test. Of course, setting the parameter on the kernel
  command line makes no sense but doesn't have any side effect either.

Radu Rendec (1):
  samples: Add irqchip test driver

 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

-- 
2.51.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/1] samples: Add irqchip test driver
  2025-09-23 23:29 [PATCH 0/1] irqchip test driver/sandbox Radu Rendec
@ 2025-09-23 23:29 ` Radu Rendec
  2025-09-25 23:27   ` kernel test robot
  0 siblings, 1 reply; 3+ messages in thread
From: Radu Rendec @ 2025-09-23 23:29 UTC (permalink / raw)
  To: Thomas Gleixner; +Cc: linux-kernel, Brian Masney, Eric Chanudet

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@redhat.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@redhat.com>");
+MODULE_DESCRIPTION("IRQ chip driver sandbox");
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH 1/1] samples: Add irqchip test driver
  2025-09-23 23:29 ` [PATCH 1/1] samples: Add irqchip test driver Radu Rendec
@ 2025-09-25 23:27   ` kernel test robot
  0 siblings, 0 replies; 3+ messages in thread
From: kernel test robot @ 2025-09-25 23:27 UTC (permalink / raw)
  To: Radu Rendec, Thomas Gleixner
  Cc: llvm, oe-kbuild-all, linux-kernel, Brian Masney, Eric Chanudet

Hi Radu,

kernel test robot noticed the following build errors:

[auto build test ERROR on linus/master]
[also build test ERROR on v6.17-rc7 next-20250925]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Radu-Rendec/samples-Add-irqchip-test-driver/20250924-073245
base:   linus/master
patch link:    https://lore.kernel.org/r/20250923232905.1510547-2-rrendec%40redhat.com
patch subject: [PATCH 1/1] samples: Add irqchip test driver
config: i386-randconfig-015-20250926 (https://download.01.org/0day-ci/archive/20250926/202509260730.o2TJUGJS-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250926/202509260730.o2TJUGJS-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202509260730.o2TJUGJS-lkp@intel.com/

All errors (new ones prefixed by >>):

>> samples/irqchip/irqc_test.c:187:10: error: use of undeclared identifier 'irq_domain_free_irqs_common'; did you mean 'irq_domain_free_irqs'?
     187 |         .free = irq_domain_free_irqs_common,
         |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
         |                 irq_domain_free_irqs
   include/linux/irqdomain.h:651:20: note: 'irq_domain_free_irqs' declared here
     651 | static inline void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs) { }
         |                    ^
>> samples/irqchip/irqc_test.c:186:3: error: field designator 'alloc' does not refer to any field in type 'const struct irq_domain_ops'
     186 |         .alloc = root_alloc,
         |         ~^~~~~~~~~~~~~~~~~~
>> samples/irqchip/irqc_test.c:187:3: error: field designator 'free' does not refer to any field in type 'const struct irq_domain_ops'
     187 |         .free = irq_domain_free_irqs_common,
         |         ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> samples/irqchip/irqc_test.c:188:3: error: field designator 'translate' does not refer to any field in type 'const struct irq_domain_ops'
     188 |         .translate = irq_domain_translate_onecell,
         |         ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> samples/irqchip/irqc_test.c:198:13: error: use of undeclared identifier 'irq_chip_ack_parent'
     198 |         .irq_ack = irq_chip_ack_parent,
         |                    ^
>> samples/irqchip/irqc_test.c:199:22: error: use of undeclared identifier 'irq_chip_set_affinity_parent'; did you mean 'irq_set_affinity_hint'?
     199 |         .irq_set_affinity = irq_chip_set_affinity_parent,
         |                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
         |                             irq_set_affinity_hint
   include/linux/interrupt.h:404:19: note: 'irq_set_affinity_hint' declared here
     404 | static inline int irq_set_affinity_hint(unsigned int irq,
         |                   ^
>> samples/irqchip/irqc_test.c:219:19: error: no member named 'parent' in 'struct irq_domain'
     219 |                 .fwnode = irqd->parent->fwnode,
         |                           ~~~~  ^
>> samples/irqchip/irqc_test.c:236:20: error: no member named 'parent_data' in 'struct irq_data'
     236 |                         virq + i, d, d->parent_data);
         |                                      ~  ^
   include/linux/printk.h:587:34: note: expanded from macro 'pr_info'
     587 |         printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
         |                                         ^~~~~~~~~~~
   include/linux/printk.h:514:60: note: expanded from macro 'printk'
     514 | #define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__)
         |                                                            ^~~~~~~~~~~
   include/linux/printk.h:486:19: note: expanded from macro 'printk_index_wrap'
     486 |                 _p_func(_fmt, ##__VA_ARGS__);                           \
         |                                 ^~~~~~~~~~~
>> samples/irqchip/irqc_test.c:237:3: error: call to undeclared function 'irq_domain_set_hwirq_and_chip'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     237 |                 irq_domain_set_hwirq_and_chip(irqd, virq + i, hwirq + i,
         |                 ^
>> samples/irqchip/irqc_test.c:256:9: error: call to undeclared function 'irq_domain_alloc_irqs_parent'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     256 |         return irq_domain_alloc_irqs_parent(irqd, virq, nr_irqs, &parent_fwspec);
         |                ^
   samples/irqchip/irqc_test.c:256:9: note: did you mean 'irq_domain_alloc_irqs'?
   include/linux/irqdomain.h:645:19: note: 'irq_domain_alloc_irqs' declared here
     645 | static inline int irq_domain_alloc_irqs(struct irq_domain *domain, unsigned int nr_irqs,
         |                   ^
   samples/irqchip/irqc_test.c:269:10: error: use of undeclared identifier 'irq_domain_free_irqs_common'; did you mean 'irq_domain_free_irqs'?
     269 |         .free = irq_domain_free_irqs_common,
         |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
         |                 irq_domain_free_irqs
   include/linux/irqdomain.h:651:20: note: 'irq_domain_free_irqs' declared here
     651 | static inline void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs) { }
         |                    ^
   samples/irqchip/irqc_test.c:268:3: error: field designator 'alloc' does not refer to any field in type 'const struct irq_domain_ops'
     268 |         .alloc = hier_alloc,
         |         ~^~~~~~~~~~~~~~~~~~
   samples/irqchip/irqc_test.c:269:3: error: field designator 'free' does not refer to any field in type 'const struct irq_domain_ops'
     269 |         .free = irq_domain_free_irqs_common,
         |         ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   samples/irqchip/irqc_test.c:270:3: error: field designator 'translate' does not refer to any field in type 'const struct irq_domain_ops'
     270 |         .translate = irq_domain_translate_onecell,
         |         ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> samples/irqchip/irqc_test.c:438:14: error: call to undeclared function 'irq_domain_create_hierarchy'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     438 |         hier_irqd = irq_domain_create_hierarchy(root_irqd, 0, NUM_TOTAL_IRQS_HIER,
         |                     ^
   samples/irqchip/irqc_test.c:438:14: note: did you mean 'irq_domain_create_legacy'?
   include/linux/irqdomain.h:324:20: note: 'irq_domain_create_legacy' declared here
     324 | struct irq_domain *irq_domain_create_legacy(struct fwnode_handle *fwnode, unsigned int size,
         |                    ^
>> samples/irqchip/irqc_test.c:438:12: error: incompatible integer to pointer conversion assigning to 'struct irq_domain *' from 'int' [-Wint-conversion]
     438 |         hier_irqd = irq_domain_create_hierarchy(root_irqd, 0, NUM_TOTAL_IRQS_HIER,
         |                   ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     439 |                                                 hier_fwnode, &hier_ops, NULL);
         |                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   16 errors generated.


vim +187 samples/irqchip/irqc_test.c

   166	
   167	/*
   168	 * Parent IRQ domains *must* use the new API (i.e. alloc/free vs. map) to
   169	 * support hierarchy, for the following reasons:
   170	 *   - The child .alloc() function calls irq_domain_alloc_irqs_parent().
   171	 *   - The irq_domain_alloc_irqs_hierarchy() is just a thin wrapper around
   172	 *     irq_domain_alloc_irqs_hierarchy().
   173	 *   - Finally, irq_domain_alloc_irqs_hierarchy() explicitly checks that
   174	 *     the (parent) domain implements the alloc() function and returns an
   175	 *     error otherwise.
   176	 *
   177	 * Furthermore, Documentation/core-api/irq/irq-domain.rst clearly states that
   178	 * the new API must be used for all domains to support hierarchy.
   179	 *
   180	 * Note: The .free() function is also part of the new API and is mandatory but
   181	 *       there is a default implementation that we can use. If we do not
   182	 *       implement this function and .alloc() fails for whatever reason, we get
   183	 *       an oops as the IRQ framework tries to rollback whatever has been done.
   184	 */
   185	static const struct irq_domain_ops root_ops = {
 > 186		.alloc = root_alloc,
 > 187		.free = irq_domain_free_irqs_common,
 > 188		.translate = irq_domain_translate_onecell,
   189	};
   190	
   191	/*
   192	 * Since hierarchical interrupts are mapped 1:1 in all domains along the path,
   193	 * CPU affinity is not shared with other interrupts. Use the parent handlers to
   194	 * set the affinity in the root domain.
   195	 */
   196	static const struct irq_chip hier_chip = {
   197		.name = "TEST-HIER-IC",
 > 198		.irq_ack = irq_chip_ack_parent,
 > 199		.irq_set_affinity = irq_chip_set_affinity_parent,
   200	};
   201	
   202	/*
   203	 * For a GPIO IRQ chip, this is gpiochip_hierarchy_irq_domain_alloc(), and the
   204	 * ops pointer is set in gpiochip_hierarchy_setup_domain_ops(), called from
   205	 * gpiochip_hierarchy_add_domain(). See full stack sample below in testirq_init().
   206	 *
   207	 * This function is called indirectly by irq_create_fwspec_mapping() through
   208	 * irq_domain_alloc_irqs_locked().
   209	 *
   210	 * How to search for other examples:
   211	 *   grep -rn '\.alloc\>' drivers/gpio/
   212	 *   grep -rn '\<irq_domain_alloc_irqs_parent\>' drivers/irqchip/
   213	 */
   214	static int hier_alloc(struct irq_domain *irqd, unsigned int virq,
   215			      unsigned int nr_irqs, void *data)
   216	{
   217		struct irq_fwspec *fwspec = data;
   218		struct irq_fwspec parent_fwspec = {
 > 219			.fwnode = irqd->parent->fwnode,
   220			.param_count = 1,
   221		};
   222		irq_hw_number_t hwirq;
   223		unsigned int type;
   224		int ret, i;
   225	
   226		pr_info("[%s] virq = %u, nr_irqs = %u\n", irqd->name, virq, nr_irqs);
   227	
   228		ret = irq_domain_translate_onecell(irqd, fwspec, &hwirq, &type);
   229		if (ret)
   230			return ret;
   231	
   232		for (i = 0; i < nr_irqs; i++) {
   233			struct irq_data *d = irq_get_irq_data(virq + i);
   234	
   235			pr_info("\tvirq = %u, data = %pS, parent = %pS\n",
 > 236				virq + i, d, d->parent_data);
 > 237			irq_domain_set_hwirq_and_chip(irqd, virq + i, hwirq + i,
   238						      &hier_chip, NULL);
   239		}
   240	
   241		/*
   242		 * We're not done yet, we still need to allocate interrupts in the
   243		 * parent domain. If we just "return 0" at this point, the upstream
   244		 * call to irq_create_fwspec_mapping() fails with -EINVAL.
   245		 *
   246		 * By now, the framework has already allocated a struct irq_data for
   247		 * each virq, for both our domain (hier) and the parent domain (root).
   248		 * So, irq_get_irq_data(virq) and irq_get_irq_data(virq)->parent_data
   249		 * are both non-null. The problem is that parent_data->chip is NULL,
   250		 * and this fails the sanity checks in irq_domain_trim_hierarchy()
   251		 * (called from irq_domain_alloc_irqs_locked()).
   252		 */
   253	
   254		parent_fwspec.param[0] = hwirq;
   255	
 > 256		return irq_domain_alloc_irqs_parent(irqd, virq, nr_irqs, &parent_fwspec);
   257	}
   258	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2025-09-25 23:27 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-23 23:29 [PATCH 0/1] irqchip test driver/sandbox Radu Rendec
2025-09-23 23:29 ` [PATCH 1/1] samples: Add irqchip test driver Radu Rendec
2025-09-25 23:27   ` kernel test robot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.