Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 2/2] irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver
@ 2026-08-17 10:52 Tushar Nimkar
  2026-08-19 19:33 ` Thomas Gleixner
  0 siblings, 1 reply; 2+ messages in thread
From: Tushar Nimkar @ 2026-08-17 10:52 UTC (permalink / raw)
  To: Thomas Gleixner, linux-kernel, linux-arm-kernel
  Cc: Michal Simek, Anirudha Sarangi, git-dev, Tushar Nimkar

From: Anirudha Sarangi <anirudha.sarangi@amd.com>

The Xilinx Versal NET SoC implements a Control and Status Register (CSR)
block in front of the ARM SMMUv3 to manage EVENTQ, PRIQ and GLOBAL
interrupts. Interrupts are enabled, cleared, and acknowledged
through this block by writing to the relevant registers. Once an
interrupt is acknowledged, it is forwarded to the parent interrupt
controller for further processing.

Add an irqchip driver for the CSR block. The driver registers as an
interrupt controller and chains to the upstream SMMUv3 driver, so
that the standard SMMUv3 driver does not require modification while still
supporting Xilinx SoCs.

The CSR block can also gate the SMMUv3 CMDQ_SYNC completion interrupt
(bit 1), but it is intentionally left unsupported. The Linux arm-smmu-v3
driver signals CMDQ_SYNC completion via polling or MSI and never requests
a wired CMD_SYNC interrupt, so there is no in-kernel consumer for it.
Exposing it as a child line would only risk spurious, never-acked
interrupts, and it is omitted from the DT binding for the same reason.

This driver is intended to operate in conjunction with the ARM SMMUv3
driver and the kernel's IOMMU DMA infrastructure. These dependencies
are not selected directly in Kconfig to avoid introducing dependency
recursion.

Signed-off-by: Anirudha Sarangi <anirudha.sarangi@amd.com>
Co-developed-by: Tushar Nimkar <tushar.nimkar@amd.com>
Signed-off-by: Tushar Nimkar <tushar.nimkar@amd.com>
---
 MAINTAINERS                           |  10 ++
 drivers/irqchip/Kconfig               |  10 ++
 drivers/irqchip/Makefile              |   1 +
 drivers/irqchip/irq-xilinx-smmu-csr.c | 225 ++++++++++++++++++++++++++
 4 files changed, 246 insertions(+)
 create mode 100644 drivers/irqchip/irq-xilinx-smmu-csr.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5c9c9b5cf44a..bf790567e756 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29881,6 +29881,16 @@ F:	Documentation/misc-devices/xilinx_sdfec.rst
 F:	drivers/misc/xilinx_sdfec.c
 F:	include/uapi/misc/xilinx_sdfec.h
 
+XILINX SMMU CSR IRQ DRIVER
+M:	Anirudha Sarangi <anirudha.sarangi@amd.com>
+M:	Tushar Nimkar <tushar.nimkar@amd.com>
+L:	git@amd.com
+L:	linux-arm-kernel@lists.infradead.org
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
+F:	drivers/irqchip/irq-xilinx-smmu-csr.c
+
 XILINX TRNG DRIVER
 M:	Mounika Botcha <mounika.botcha@amd.com>
 M:	Harsh Jain <h.jain@amd.com>
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 20b77fbc51ee..a08bda407a63 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -840,4 +840,14 @@ config SUNPLUS_SP7021_INTC
 	  chained controller, routing all interrupt source in P-Chip to
 	  the primary controller on C-Chip.
 
+config IRQCHIP_XILINX_SMMU_CSR
+	bool "Xilinx SMMU CSR Interrupt Driver"
+	depends on ARCH_ZYNQMP
+	select IRQ_DOMAIN
+	help
+	  This driver does the initial handling of SMMU interrupts before
+	  forwarding them to the ARM SMMU v3 driver.  It is intended for
+	  ZynqMP-class platforms where a vendor CSR block mediates SMMU
+	  interrupts.
+
 endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index ab33cccd8471..16a5fd126c9a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -135,3 +135,4 @@ obj-$(CONFIG_APPLE_AIC)			+= irq-apple-aic.o
 obj-$(CONFIG_MCHP_EIC)			+= irq-mchp-eic.o
 obj-$(CONFIG_SOPHGO_SG2042_MSI)		+= irq-sg2042-msi.o
 obj-$(CONFIG_SUNPLUS_SP7021_INTC)	+= irq-sp7021-intc.o
+obj-$(CONFIG_IRQCHIP_XILINX_SMMU_CSR)	+= irq-xilinx-smmu-csr.o
diff --git a/drivers/irqchip/irq-xilinx-smmu-csr.c b/drivers/irqchip/irq-xilinx-smmu-csr.c
new file mode 100644
index 000000000000..189185d89746
--- /dev/null
+++ b/drivers/irqchip/irq-xilinx-smmu-csr.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Versal Net SMMU CSR interrupt controller driver
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * Vendor CSR block that gates the ARM SMMUv3 wired interrupts. Chains
+ * off the single GIC parent line and demultiplexes the CSR status into
+ * three children (eventq, gerror, priq), each enabled/acked
+ * via SMMU_CSR_IER/IDR/ISR that the ARM SMMUv3 driver requests individually.
+ */
+
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+/* Interrupt types */
+#define SMMU_INTR_EVENT		BIT(0)
+#define SMMU_INTR_GLOBAL	BIT(2)
+#define SMMU_INTR_PRI		BIT(3)
+
+/* Mask for all SMMU interrupts */
+#define SMMU_INTR_ALL		(SMMU_INTR_EVENT | \
+				 SMMU_INTR_GLOBAL | SMMU_INTR_PRI)
+
+#define SMMU_CSR_ISR		0x24	/* Interrupt Status */
+#define SMMU_CSR_IER		0x2c	/* Interrupt Enable */
+#define SMMU_CSR_IDR		0x30	/* Interrupt Disable */
+
+/**
+ * struct xilinx_smmu_csr - SMMU CSR interrupt controller context
+ * @base: MMIO base address of the CSR registers
+ * @domain: IRQ domain for the child interrupts
+ * @parent_irq: parent (GIC) IRQ this block is chained to
+ * @lock: protects the SMMU_CSR_IER/IDR/ISR read and writes
+ */
+struct xilinx_smmu_csr {
+	void __iomem		*base;
+	struct irq_domain	*domain;
+	int			parent_irq;
+	raw_spinlock_t		lock;
+};
+
+enum xilinx_smmu_csr_irq {
+	SMMU_CSR_IRQ_EVENTQ = 0,
+	SMMU_CSR_IRQ_GERROR = 2,
+	SMMU_CSR_IRQ_PRIQ = 3,
+	SMMU_CSR_IRQ_NR,
+};
+
+static u32 xilinx_smmu_csr_hwirq_mask(irq_hw_number_t hwirq)
+{
+	if (hwirq >= SMMU_CSR_IRQ_NR)
+		return 0;
+
+	return BIT(hwirq) & SMMU_INTR_ALL;
+}
+
+static void xilinx_smmu_csr_irq_mask(struct irq_data *d)
+{
+	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+	if (!mask)
+		return;
+
+	raw_spin_lock(&csr->lock);
+	writel(mask, csr->base + SMMU_CSR_IDR);
+	raw_spin_unlock(&csr->lock);
+}
+
+static void xilinx_smmu_csr_irq_unmask(struct irq_data *d)
+{
+	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+	if (!mask)
+		return;
+
+	raw_spin_lock(&csr->lock);
+	writel(mask, csr->base + SMMU_CSR_IER);
+	raw_spin_unlock(&csr->lock);
+}
+
+static void xilinx_smmu_csr_irq_ack(struct irq_data *d)
+{
+	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+	if (!mask)
+		return;
+
+	raw_spin_lock(&csr->lock);
+	writel(mask, csr->base + SMMU_CSR_ISR);
+	raw_spin_unlock(&csr->lock);
+}
+
+static struct irq_chip xilinx_smmu_csr_chip = {
+	.name		= "xlnx-smmu-csr",
+	.irq_mask	= xilinx_smmu_csr_irq_mask,
+	.irq_unmask	= xilinx_smmu_csr_irq_unmask,
+	.irq_ack	= xilinx_smmu_csr_irq_ack,
+};
+
+static void xilinx_smmu_csr_irq_handler(struct irq_desc *desc)
+{
+	struct xilinx_smmu_csr *csr = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	u32 status, pending;
+
+	chained_irq_enter(chip, desc);
+	raw_spin_lock(&csr->lock);
+	status = readl(csr->base + SMMU_CSR_ISR);
+	raw_spin_unlock(&csr->lock);
+
+	/* Only service sources we support; SMMU_CSR_ISR latches raw status */
+	pending = status & SMMU_INTR_ALL;
+
+	while (pending) {
+		irq_hw_number_t hwirq = __ffs(pending);
+		int ret;
+
+		ret = generic_handle_domain_irq(csr->domain, hwirq);
+		if (ret) {
+			raw_spin_lock(&csr->lock);
+			writel(BIT(hwirq), csr->base + SMMU_CSR_ISR);
+			raw_spin_unlock(&csr->lock);
+			pr_err_ratelimited("xilinx-smmu-csr: Failed to handle domain IRQ %lu: %d\n",
+					   hwirq, ret);
+		}
+
+		pending &= ~BIT(hwirq);
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static int xilinx_smmu_csr_domain_map(struct irq_domain *d, unsigned int virq,
+				      irq_hw_number_t hwirq)
+{
+	struct xilinx_smmu_csr *csr = d->host_data;
+
+	if (!xilinx_smmu_csr_hwirq_mask(hwirq))
+		return -EINVAL;
+
+	irq_set_chip_and_handler(virq, &xilinx_smmu_csr_chip, handle_level_irq);
+	irq_set_chip_data(virq, csr);
+	irq_set_status_flags(virq, IRQ_LEVEL);
+
+	return 0;
+}
+
+static const struct irq_domain_ops xilinx_smmu_csr_domain_ops = {
+	.map	= xilinx_smmu_csr_domain_map,
+	.xlate	= irq_domain_xlate_onecell,
+};
+
+static int __init xilinx_smmu_csr_init(struct device_node *node,
+				       struct device_node *parent)
+{
+	struct xilinx_smmu_csr *csr;
+	int ret;
+
+	if (WARN_ON_ONCE(!parent))
+		return -EINVAL;
+
+	if (irq_find_matching_fwnode(of_fwnode_handle(node),
+				     DOMAIN_BUS_ANY))
+		return -ENODEV;
+
+	csr = kzalloc(sizeof(*csr), GFP_KERNEL);
+	if (!csr)
+		return -ENOMEM;
+
+	raw_spin_lock_init(&csr->lock);
+
+	csr->base = of_iomap(node, 0);
+	if (!csr->base) {
+		ret = -ENOMEM;
+		goto free;
+	}
+
+	/* Start from a known state: all sources disabled, latches cleared. */
+	writel(SMMU_INTR_ALL, csr->base + SMMU_CSR_IDR);
+	writel(SMMU_INTR_ALL, csr->base + SMMU_CSR_ISR);
+
+	csr->domain = irq_domain_create_linear(of_fwnode_handle(node), SMMU_CSR_IRQ_NR,
+					       &xilinx_smmu_csr_domain_ops,
+					       csr);
+	if (!csr->domain) {
+		pr_err("%pOF: failed to create irq domain\n", node);
+		ret = -ENOMEM;
+		goto unmap;
+	}
+
+	csr->parent_irq = irq_of_parse_and_map(node, 0);
+	if (!csr->parent_irq) {
+		pr_err("%pOF: failed to map parent irq\n", node);
+		ret = -EINVAL;
+		goto remove_domain;
+	}
+
+	irq_set_chained_handler_and_data(csr->parent_irq,
+					 xilinx_smmu_csr_irq_handler, csr);
+
+	pr_debug("%pOF: Xilinx SMMU CSR interrupt controller registered\n", node);
+
+	return 0;
+
+remove_domain:
+	irq_domain_remove(csr->domain);
+unmap:
+	iounmap(csr->base);
+free:
+	kfree(csr);
+	return ret;
+}
+
+IRQCHIP_DECLARE(xilinx_smmu_csr, "xlnx,versal-net-smmu-csr",
+		xilinx_smmu_csr_init);
-- 
2.34.1



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

* Re: [PATCH 2/2] irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver
  2026-08-17 10:52 [PATCH 2/2] irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver Tushar Nimkar
@ 2026-08-19 19:33 ` Thomas Gleixner
  0 siblings, 0 replies; 2+ messages in thread
From: Thomas Gleixner @ 2026-08-19 19:33 UTC (permalink / raw)
  To: Tushar Nimkar, linux-kernel, linux-arm-kernel
  Cc: Michal Simek, Anirudha Sarangi, git-dev, Tushar Nimkar

On Mon, Aug 17 2026 at 16:22, Tushar Nimkar wrote:
> +/**
> + * struct xilinx_smmu_csr - SMMU CSR interrupt controller context
> + * @base: MMIO base address of the CSR registers
> + * @domain: IRQ domain for the child interrupts
> + * @parent_irq: parent (GIC) IRQ this block is chained to
> + * @lock: protects the SMMU_CSR_IER/IDR/ISR read and writes

Please make the member descriptions tabular aligned

    @base:	MMIO ...
    @domain:	Interrupt domain

And yes, use interrupt and not IRQ. This is not twitter.

> +static void xilinx_smmu_csr_irq_mask(struct irq_data *d)
> +{
> +	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
> +	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
> +
> +	if (!mask)
> +		return;
> +
> +	raw_spin_lock(&csr->lock);

  guard(raw_spinlock)(&crs->lock);

> +	writel(mask, csr->base + SMMU_CSR_IDR);
> +	raw_spin_unlock(&csr->lock);
> +}
> +
> +static void xilinx_smmu_csr_irq_unmask(struct irq_data *d)
> +{
> +	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
> +	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
> +
> +	if (!mask)
> +		return;
> +
> +	raw_spin_lock(&csr->lock);

Ditto

> +	writel(mask, csr->base + SMMU_CSR_IER);
> +	raw_spin_unlock(&csr->lock);
> +}
> +
> +static void xilinx_smmu_csr_irq_ack(struct irq_data *d)
> +{
> +	struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
> +	u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
> +
> +	if (!mask)
> +		return;
> +
> +	raw_spin_lock(&csr->lock);

Ditto.

> +	writel(mask, csr->base + SMMU_CSR_ISR);
> +	raw_spin_unlock(&csr->lock);
> +}
> +
> +static struct irq_chip xilinx_smmu_csr_chip = {
> +	.name		= "xlnx-smmu-csr",
> +	.irq_mask	= xilinx_smmu_csr_irq_mask,
> +	.irq_unmask	= xilinx_smmu_csr_irq_unmask,
> +	.irq_ack	= xilinx_smmu_csr_irq_ack,
> +};
> +
> +static void xilinx_smmu_csr_irq_handler(struct irq_desc *desc)
> +{
> +	struct xilinx_smmu_csr *csr = irq_desc_get_handler_data(desc);
> +	struct irq_chip *chip = irq_desc_get_chip(desc);
> +	u32 status, pending;
> +
> +	chained_irq_enter(chip, desc);
> +	raw_spin_lock(&csr->lock);

scoped_guard() if you can explain what the lock is actually protecting
here ...

> +	status = readl(csr->base + SMMU_CSR_ISR);
> +	raw_spin_unlock(&csr->lock);
> +
> +	/* Only service sources we support; SMMU_CSR_ISR latches raw status */
> +	pending = status & SMMU_INTR_ALL;
> +
> +	while (pending) {
> +		irq_hw_number_t hwirq = __ffs(pending);
> +		int ret;
> +
> +		ret = generic_handle_domain_irq(csr->domain, hwirq);
> +		if (ret) {
> +			raw_spin_lock(&csr->lock);
> +			writel(BIT(hwirq), csr->base + SMMU_CSR_ISR);
> +			raw_spin_unlock(&csr->lock);

... and here. There is _ONE_ chained demultiplex handler per chip, so where
is the concurrency vs. the read and write from/to SMMU_CSR_ISR?

The irq_ack() callback of the demultiplexed interrupts cannot happen
concurrently because that happens in the context of the demultiplexed
handler invoked by generic_handle_domain_irq(). No?

Not that I care about the performance of your code, but I care about
code clarity. If there is a reason for this magic lock voodoo here, then
please explain it in a comment.

Also this write here wants a comment. Why is the pending bit written
back in the failure case? I assume to acknowlegde the interrupt. How are
the interrupts which are handled acknowledged?

Also if this happens, then this code should make sure to mask this
interrupt line because if something left it unmasked it will come back
forever.

> +			pr_err_ratelimited("xilinx-smmu-csr: Failed to handle domain IRQ %lu: %d\n",
> +					   hwirq, ret);
> +		}
> +
> +		pending &= ~BIT(hwirq);
> +	}
> +static int __init xilinx_smmu_csr_init(struct device_node *node,
> +				       struct device_node *parent)

No line break required. You have 100 characters. Please fix that up all over the place.

> +{
> +	struct xilinx_smmu_csr *csr;
> +	int ret;
> +
> +	if (WARN_ON_ONCE(!parent))
> +		return -EINVAL;
> +
> +	if (irq_find_matching_fwnode(of_fwnode_handle(node),
> +				     DOMAIN_BUS_ANY))
> +		return -ENODEV;
> +
> +	csr = kzalloc(sizeof(*csr), GFP_KERNEL);

devm_kzalloc()

> +	if (!csr)
> +		return -ENOMEM;
> +
> +	raw_spin_lock_init(&csr->lock);
> +
> +	csr->base = of_iomap(node, 0);

devm_of_iomap()

> +	if (!csr->base) {
> +		ret = -ENOMEM;
> +		goto free;
> +	}
> +
> +	/* Start from a known state: all sources disabled, latches cleared. */
> +	writel(SMMU_INTR_ALL, csr->base + SMMU_CSR_IDR);
> +	writel(SMMU_INTR_ALL, csr->base + SMMU_CSR_ISR);
> +
> +	csr->domain = irq_domain_create_linear(of_fwnode_handle(node), SMMU_CSR_IRQ_NR,
> +					       &xilinx_smmu_csr_domain_ops,
> +					       csr);

devm_irq_domain_instantiate() or use this one:

 https://lore.kernel.org/lkml/20260819090543.585131-2-Zhipeng.wang_1@oss.nxp.com/

It's not merged into tip yet, but it will be.

> +	if (!csr->domain) {
> +		pr_err("%pOF: failed to create irq domain\n", node);
> +		ret = -ENOMEM;
> +		goto unmap;

with that all these 'ret = -ERROR; goto foo;' go away.

Thanks,

        tglx


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

end of thread, other threads:[~2026-08-19 19:33 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 10:52 [PATCH 2/2] irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver Tushar Nimkar
2026-08-19 19:33 ` Thomas Gleixner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox